求Pandas中一系列范畴数据任意长度的重复区间



有没有办法在Pandas中获得任意长度重复区间的开始和结束?目前,我正在使用一种破解shift()的方法,我想知道是否有更好的方法

例如,我有一个DataFrame,如下所示:

index   category
0       blue
1       blue
2       blue
3       green
4       green
5       red
6       red
7       red
8       red
9       red
10      blue
11      blue
12      blue
13      blue
14      blue
15      blue
16      green
17      green
18      green
19      green

我想得到这个(或者至少这个信息(:

category    start   end
blue        0       2
green       3       4
red         5       9
blue        10      15
green       16      19

谢谢

试试这个:

df.groupby((df['category'] != df['category'].shift()).cumsum(), 
as_index=False)[['category', 'index']]
.agg(category=('category','first'),
first=('index','first'),
last=('index','last'))

输出:

category  first  last
0     blue      0     2
1    green      3     4
2      red      5     9
3     blue     10    15
4    green     16    19

详细信息:

通过检查类别的下一个值是否不等于当前类别,并使用cumsum在数据中创建组,创建一个辅助序列。聚合这些组以获得第一个和最后一个索引以及类别。

虽然cumsum+agg解决方案运行良好,但它的扩展性不太好,也不适用于DatetimeIndex,所以我使用掩码方法对其进行了测试,并获得了显著的加速。张贴在这里为未来的访客:

累计法

def get_interval_start_end_cumsum(df, col):
if df.index.name:
idx = df.index
df = df.reset_index()
else:
idx = df.reset_index().index
df = df.reset_index().groupby((df[col] != df[col].shift()).cumsum(), as_index = False) 
.agg(category = (col, 'first'), first = ('index', 'first'), last=('index', 'last')
).rename(columns = {'category': col, 'first': 'start', 'last': 'end'})
for c in ['start', 'end']:
df[c] = df[c].apply(lambda x: idx[x])
return df

掩码方法

def get_interval_start_end_mask(df, col):
idx_name = df.index.name if df.index.name else 'index'
mask = (df[col] != df[col].shift()) | (df[col] != df[col].shift(-1))
df = deepcopy(df[mask].reset_index())
return pd.concat([
df.loc[df.index % 2 == 0].reset_index(drop = True).rename(columns = {idx_name: 'start'}),
df.loc[df.index % 2 != 0].reset_index(drop = True).rename(columns = {idx_name: 'end'}).end
], axis = 1)[[col, 'start', 'end']]

结果

问题中的示例DataFrame

%timeit get_interval_start_end_cumsum(df, 'colors')
>> 10.8 ms ± 547 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
%timeit get_interval_start_end_mask(df, 'colors')
>> 4.84 ms ± 57.8 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

350万行的真实数据

%timeit get_interval_start_end_cumsum(df, 'a_col')
>> 29.6 s ± 475 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit get_interval_start_end_mask(df, 'a_col')
>> 349 ms ± 9.64 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

正如你所看到的,mask方法可以很好地扩展,当处理大量数据时,它的运行时间提高了98.8%。

希望能有所帮助:(

最新更新