Pandas:条件滚动计数

60

我有一个Series,长这个样子:

   col
0  B
1  B
2  A
3  A
4  A
5  B

它是一个时间序列,因此索引按时间排序。

对于每一行,我想计算值连续出现的次数,即:

输出:

   col count
0  B   1
1  B   2
2  A   1 # Value does not match previous row => reset counter to 1
3  A   2
4  A   3
5  B   1 # Value does not match previous row => reset counter to 1

我找到了2个相关的问题,但我不知道如何将那些信息“写入”DataFrame中作为每行的一个新列(如上所述)。使用rolling_apply并不起作用。

相关:

通过索引计算 Pandas DataFrame 上连续事件的数量

在 Pandas 数据框中查找连续段

5个回答

76

我认为有一种不错的方法可以将@chrisb和@CodeShaman的解决方案结合起来(正如所指出的,CodeShaman的解决方案计算的是总值而不是连续值)。

  df['count'] = df.groupby((df['col'] != df['col'].shift(1)).cumsum()).cumcount()+1

  col  count
0   B      1
1   B      2
2   A      1
3   A      2
4   A      3
5   B      1

4
如果你需要按多列进行分组,该怎么办? - DataPlankton
这是一个非常优雅的解决方案,谢谢! - gilzero
浏览了很多页面,只是想找到这个简单的计数器。非常感谢! - Wicked Gummy Bear

26

一句话描述:

df['count'] = df.groupby('col').cumcount()
或者
df['count'] = df.groupby('col').cumcount() + 1

如果您想让计数从1开始。


2
如果您想从某个计数器开始,最好使用以下方式: df['count'] = df.groupby('col').cumcount() + 1 - Gecko
45
这不是问题的答案。它不计算连续值,而是计算总值。 - Gabriel
此解决方案将把最后一个B视为3,而不是1。 - asimo

24

根据你提供的第二个答案,假设s是你的序列。

df = pd.DataFrame(s)
df['block'] = (df['col'] != df['col'].shift(1)).astype(int).cumsum()
df['count'] = df.groupby('block').transform(lambda x: range(1, len(x) + 1))


In [88]: df
Out[88]: 
  col  block  count
0   B      1      1
1   B      1      2
2   A      2      1
3   A      2      2
4   A      2      3
5   B      3      1

2
真的很喜欢你的方法。但是,如果要在多个列上进行分组,该怎么办? - Garrick

13

我喜欢@chrisb的答案,但想分享我的解决方案,因为有些人可能会发现它更易读并且在处理类似问题时使用更加容易....

1)创建一个使用静态变量的函数

def rolling_count(val):
    if val == rolling_count.previous:
        rolling_count.count +=1
    else:
        rolling_count.previous = val
        rolling_count.count = 1
    return rolling_count.count
rolling_count.count = 0 #static variable
rolling_count.previous = None #static variable

2) 将其应用于将Series转换为DataFrame后的数据

df  = pd.DataFrame(s)
df['count'] = df['col'].apply(rolling_count) #new column in dataframe

df命令的输出结果

  col  count
0   B      1
1   B      2
2   A      1
3   A      2
4   A      3
5   B      1

这是一个很好的答案... 如果滚动计数基于两个字段 - 例如,'cola'和'colb',您将如何修改它以使其工作?在这种情况下,如果任何一个字段更改,我希望看到计数器重置。谢谢 - 这将为我节省大量工作。 - Stumbling Through Data Science
不错!这适用于具有任意数量其他列的数据框。@chrisb的解决方案仅适用于数据框中的一列(当我在我的数据框上运行它时,会出现“ValueError:传递了4个错误的项目数,放置意味着1” 的错误)。 - horcle_buzz

4
如果您想在两个列上进行过滤,请使用以下方法。
def count_consecutive_items_n_cols(df, col_name_list, output_col):
    cum_sum_list = [
        (df[col_name] != df[col_name].shift(1)).cumsum().tolist() for col_name in col_name_list
    ]
    df[output_col] = df.groupby(
        ["_".join(map(str, x)) for x in zip(*cum_sum_list)]
    ).cumcount() + 1
    return df

col_a col_b count
0   1     B     1
1   1     B     2
2   1     A     1
3   2     A     1
4   2     A     2
5   2     B     1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接