使用 Groupby 识别 Pandas Dataframe 中的连续相同值

Posted

技术标签:

【中文标题】使用 Groupby 识别 Pandas Dataframe 中的连续相同值【英文标题】:Identify consecutive same values in Pandas Dataframe, with a Groupby 【发布时间】:2018-02-03 19:20:50 【问题描述】:

我有以下数据框df:

data='id':[1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2],
      'value':[2,2,3,2,2,2,3,3,3,3,1,4,1,1,1,4,4,1,1,1,1,1]
df=pd.DataFrame.from_dict(data)
df
Out[8]: 
    id  value
0    1      2
1    1      2
2    1      3
3    1      2
4    1      2
5    1      2
6    1      3
7    1      3
8    1      3
9    1      3
10   2      1
11   2      4
12   2      1
13   2      1
14   2      1
15   2      4
16   2      4
17   2      1
18   2      1
19   2      1
20   2      1
21   2      1

我需要做的是在 id 级别 (df.groupby['id']) 识别该值连续显示相同数字 3 次或更多次。

我想得到以下结果:

df
Out[12]: 
    id  value  flag
0    1      2     0
1    1      2     0
2    1      3     0
3    1      2     1
4    1      2     1
5    1      2     1
6    1      3     1
7    1      3     1
8    1      3     1
9    1      3     1
10   2      1     0
11   2      4     0
12   2      1     1
13   2      1     1
14   2      1     1
15   2      4     0
16   2      4     0
17   2      1     1
18   2      1     1
19   2      1     1
20   2      1     1
21   2      1     1

我使用 pandas rolling.mean 尝试了 groupby 和 lambda 的变体,以确定滚动周期的平均值与“值”相比的位置,以及它们相同的位置,这表示一个标志。但这有几个问题,包括您可能有不同的值,这些值将平均为您尝试标记的值。另外,我不知道如何“标记”创建初始标志的滚动平均值的所有值。请参见此处,这标识了标志的“右侧”,但随后我需要填充滚动平均长度的先前值。在这里查看我的代码:

test=df.copy()
test['rma']=test.groupby('id')['value'].transform(lambda x: x.rolling(min_periods=3,window=3).mean())
test['flag']=np.where(test.rma==test.value,1,0)

这里的结果:

test
Out[61]: 
    id  value       rma  flag
0    1      2       NaN     0
1    1      2       NaN     0
2    1      3  2.333333     0
3    1      2  2.333333     0
4    1      2  2.333333     0
5    1      2  2.000000     1
6    1      3  2.333333     0
7    1      3  2.666667     0
8    1      3  3.000000     1
9    1      3  3.000000     1
10   2      1       NaN     0
11   2      4       NaN     0
12   2      1  2.000000     0
13   2      1  2.000000     0
14   2      1  1.000000     1
15   2      4  2.000000     0
16   2      4  3.000000     0
17   2      1  3.000000     0
18   2      1  2.000000     0
19   2      1  1.000000     1
20   2      1  1.000000     1
21   2      1  1.000000     1

迫不及待地想看看我错过了什么!谢谢

【问题讨论】:

你确定你的输出吗? 【参考方案1】:

你可以试试这个; 1)用df.value.diff().ne(0).cumsum()创建一个额外的组变量来表示值的变化; 2) 使用transform('size')计算分组大小并与三个进行比较,得到您需要的flag列:

df['flag'] = df.value.groupby([df.id, df.value.diff().ne(0).cumsum()]).transform('size').ge(3).astype(int) 
df


故障

1) diff 不等于零(这就是df.value.diff().ne(0) 的字面意思)在值发生变化时给出条件True

df.value.diff().ne(0)
#0      True
#1     False
#2      True
#3      True
#4     False
#5     False
#6      True
#7     False
#8     False
#9     False
#10     True
#11     True
#12     True
#13    False
#14    False
#15     True
#16    False
#17     True
#18    False
#19    False
#20    False
#21    False
#Name: value, dtype: bool

2) 然后cumsum 给出一个非降序的 id 序列,其中每个 id 表示具有相同值的连续块,注意在对布尔值求和时,True 被认为是一,而False 被认为是零:

df.value.diff().ne(0).cumsum()
#0     1
#1     1
#2     2
#3     3
#4     3
#5     3
#6     4
#7     4
#8     4
#9     4
#10    5
#11    6
#12    7
#13    7
#14    7
#15    8
#16    8
#17    9
#18    9
#19    9
#20    9
#21    9
#Name: value, dtype: int64

3)结合id列,可以对数据框进行分组,计算分组大小,得到flag列。

【讨论】:

惊人的速度~+1 @Wen 谢谢。 这绝对是一个绝妙的答案。你怎么回答得这么快?完美运行。你有机会解释一下吗?我理解 .diff 但 .ne(0) 之后会发生什么? @clg4 提示:大多数时候新问题根本不是新问题,尝试将其转换为旧问题,例如这个问题你只需要找到获取 groupid 的方法。 @Psidom:不错的方法!由于 OP 要求效率,我将您的解决方案与我自己的解决方案混合在一起并获得了不错的加速。仅供参考。【参考方案2】:

请参阅 EDIT2 以获得更强大的解决方案

同样的结果,但要快一点:

labels = (df.value != df.value.shift()).cumsum()
df['flag'] = (labels.map(labels.value_counts()) >= 3).astype(int)

    id  value  flag
0    1      2     0
1    1      2     0
2    1      3     0
3    1      2     1
4    1      2     1
5    1      2     1
6    1      3     1
7    1      3     1
8    1      3     1
9    1      3     1
10   2      1     0
11   2      4     0
12   2      1     1
13   2      1     1
14   2      1     1
15   2      4     0
16   2      4     0
17   2      1     1
18   2      1     1
19   2      1     1
20   2      1     1
21   2      1     1

地点:

    df.value != df.value.shift() 给出值变化 cumsum() 为每组相同值创建“标签” labels.value_counts() 统计每个标签的出现次数 labels.map(...) 用上面计算的计数替换标签 >= 3 在计数值上创建一个布尔掩码 astype(int) 将布尔值转换为 int

在我的手中,它在您的 df 上提供了 1.03 毫秒,而 Psidoms 的方法为 2.1 毫秒。 但我的不是单线。


编辑:

混合使用这两种方法会更快

labels = df.value.diff().ne(0).cumsum()
df['flag'] = (labels.map(labels.value_counts()) >= 3).astype(int)

样品 df 为 911µs。


EDIT2:解决 id 变化的正确解决方案,正如@clg4 所指出的那样

labels = (df.value.diff().ne(0) | df.id.diff().ne(0)).cumsum()
df['flag'] = (labels.map(labels.value_counts()) >= 3).astype(int)

... | df.id.diff().ne(0) 在哪里增加 id 变化的标签

即使 id 更改的值相同(在索引 10 上使用值 3 测试),这也可以工作,并且需要 1.28 毫秒

EDIT3:更好的解释

以索引 10 的值为 3 为例。df.id.diff().ne(0)

data='id':[1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2],
      'value':[2,2,3,2,2,2,3,3,3,3,3,4,1,1,1,4,4,1,1,1,1,1]
df=pd.DataFrame.from_dict(data)

df['id_diff'] = df.id.diff().ne(0).astype(int)
df['val_diff'] = df.value.diff().ne(0).astype(int)
df['diff_or'] = (df.id.diff().ne(0) | df.value.diff().ne(0)).astype(int)
df['labels'] = df['diff_or'].cumsum()

     id  value  id_diff  val_diff  diff_or  labels
 0    1      2        1         1        1       1
 1    1      2        0         0        0       1
 2    1      3        0         1        1       2
 3    1      2        0         1        1       3
 4    1      2        0         0        0       3
 5    1      2        0         0        0       3
 6    1      3        0         1        1       4
 7    1      3        0         0        0       4
 8    1      3        0         0        0       4
 9    1      3        0         0        0       4
>10   2      3        1    |    0    =   1       5 <== label increment
 11   2      4        0         1        1       6
 12   2      1        0         1        1       7
 13   2      1        0         0        0       7
 14   2      1        0         0        0       7
 15   2      4        0         1        1       8
 16   2      4        0         0        0       8
 17   2      1        0         1        1       9
 18   2      1        0         0        0       9
 19   2      1        0         0        0       9
 20   2      1        0         0        0       9
 21   2      1        0         0        0       9

| 是运算符“按位或”,只要其中一个元素是 True,它就会给出 True。因此,如果 id 更改的值没有差异,| 会反映 id 更改。否则它什么也改变不了。 当执行.cumsum() 时,标签在id 发生变化的地方递增,因此索引10 处的值3 不会与索引6-9 中的值3 分组。

【讨论】:

我不相信这会起作用,没有分组。如果索引 10 处的值为 3,这将不起作用...我认为... @clg4:你完全正确。感谢您指出了这一点!编辑答案以获得有效的解决方案。 我会被诅咒的......管道是做什么的?巫术...很难理解为什么如果 ID 更改会增加。 .. @clg4:我添加了更多关于管道技巧如何工作的解释。 这很有帮助。您的流程要求对 id 进行排序。我知道根据我的输入我不需要这样做,但是 groupby 也处理这个问题。为了证明您的解决方案,您首先需要一个排序 ID。对吗?【参考方案3】:
#try this simpler version
a= pd.Series([1,1,1,2,3,4,5,5,5,7,8,0,0,0])
b= a.groupby([a.ne(0), a]).transform('size').ge(3).astype('int')
#ge(x) <- x is the number of consecutive repeated values 
print b

【讨论】:

【参考方案4】:
df=pd.DataFrame.from_dict(
        'id':[1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2],
         'value':[2,2,3,2,2,2,3,3,3,3,1,4,1,1,1,4,4,1,1,1,1,1])

df2 = df.groupby((df['value'].shift() != df['value']).\
                cumsum()).filter(lambda x: len(x) >= 3)

df['flag'] = np.where(df.index.isin(df2.index),1,0)

【讨论】:

莫特元组! - muhahahaaaa :) - 抱歉

以上是关于使用 Groupby 识别 Pandas Dataframe 中的连续相同值的主要内容,如果未能解决你的问题,请参考以下文章

迭代 Pandas 分组数据框

Pandas 一次缩放多列并使用 groupby() 进行逆变换

python 可打印的pandas组(来自http://stackoverflow.com/questions/14734533/how-to-access-pandas-groupby-datafr

数据分析——python,pandas:DataFrame对象(groupby函数的使用)排序

使用 Groupby 的 Python Pandas 条件和

在 pandas 中使用 groupby 命令后使用 seaborn 进行绘图