Python 使用Groupby标识数据帧中连续的相同值

Python 使用Groupby标识数据帧中连续的相同值,python,pandas,numpy,lambda,Python,Pandas,Numpy,Lambda,我有以下数据帧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

我有以下数据帧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) 使用
df.value.diff().ne(0.cumsum()
创建一个额外的组变量来表示值的更改;2) 使用
transform('size')
计算组大小并与三个值进行比较,然后得到所需的
标志列:

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
列相结合,您可以对数据帧进行分组,计算组大小并获得
标志
列。

有关更可靠的解决方案,请参见EDIT2

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

labels=(df.value!=df.value.shift()).cumsum()
df['flag']=(labels.map(labels.value_counts())>=3.astype(int)
id值标志
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\u 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.28ms

    编辑3:更好的解释

    假设索引10的值为3<代码>df.id.diff().ne(0)

    data={'id':[1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2],
    “值”:[2,2,3,2,2,3,3,3,3,4,1,1,1,4,4,1,1,1,1]}
    df=pd.DataFrame.from_dict(数据)
    df['id_diff']=df.id.diff().ne(0).astype(int)
    df['val_diff']=df.value.diff().ne(0).astype(int)
    df['diff_或']=(df.id.diff().ne(0)| df.value.diff().ne(0)).astype(int)
    df['labels']=df['diff_或'].cumsum()
    id值id_diff val_diff diff_或标签
    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
    #试试这个更简单的版本
    a=pd.系列([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)惊人的速度~+1@Wen谢谢你,这是一个非常好的回答。你怎么回答得这么快?工作完美。你能解释一下吗?我理解.diff但是.ne(0)之后会发生什么?@clg4提示:大多数时候新问题根本不是新问题,试着将它转换为旧问题,例如,这个问题你只需要找到获取groupid的方法。@Psidom:很好的方法!由于OP要求效率,我将您的解决方案与我自己的解决方案混合在一起,并获得了很好的加速。仅供参考。你确定你的产出吗?我不相信这会奏效,没有分组。如果索引10处的值为3,这将不起作用。。。我想…@clg4:你说得很对。谢谢你指出这一点!编辑答案以得到有效的解决方案。我会被诅咒的。。。这管子是干什么的?巫术。。。很难理解,如果ID改变,为什么会增加@clg4:我添加了更多关于管道技巧工作原理的解释
    #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
    
    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)