使用Groupby

时间:2017-08-25 17:15:43

标签: 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      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

不能等着看我错过了什么!感谢

4 个答案:

答案 0 :(得分:17)

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

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

enter image description here

分解

1) diff不等于零(字面意思是df.value.diff().ne(0)的含义)只要值发生变化就会给出条件True:< / p>

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 :(得分:2)

#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

答案 2 :(得分:1)

请参阅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

其中:

  1. df.value != df.value.shift()给出了值更改
  2. cumsum()为每个具有相同值的组创建“标签”
  3. labels.value_counts()计算每个标签的出现次数
  4. labels.map(...)用上面计算的计数替换标签
  5. >= 3在计数值
  6. 上创建一个布尔掩码
  7. astype(int)将布尔值转换为int
  8. 在我的手中,你的df为1.03ms,而Psidoms的方法为2.1ms。 但我的不是单行。

    编辑:

    两种方法之间的混合更快

    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

    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更改的值没有diff,则|会反映id更改。否则它什么都没改变。 执行.cumsum()时,标签会在id更改的位置递增,因此索引10处的值3不会与索引6-9中的值3分组。

答案 3 :(得分:0)

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)