Python 在数据库中查找任意长度的一系列分类数据的重复间隔

Python 在数据库中查找任意长度的一系列分类数据的重复间隔,python,python-3.x,pandas,Python,Python 3.x,Pandas,在Pandas中,是否有方法获取具有任意长度的重复间隔的开始和结束?目前,我正在使用一种黑客方式处理shift(),我想知道是否有更好的方法 例如,我有一个数据帧,如下所示: 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

Pandas
中,是否有方法获取具有任意长度的重复间隔的开始和结束?目前,我正在使用一种黑客方式处理
shift()
,我想知道是否有更好的方法

例如,我有一个
数据帧
,如下所示:

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
我想得到这个(或者至少是这个信息):

谢谢

试试这个:

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
详情:


通过检查category的下一个值是否不等于当前类别,并使用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']]
屏蔽方法

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

带350万行的实际数据

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


希望有帮助:)

谢谢!实际上我也在做类似的事情,我只是希望有一个更优雅的内置熊猫功能(尽管这仍然比我做的好看:)@Zach我不这么认为。您需要使用cumsum在不同的组中保持相同的category值。祝你好运。快乐的编码。我很感激——对你也是如此!
%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)