web-dev-qa-db-ja.com

Pandas Dataframe、Groupbyで連続する同じ値を特定する

次のデータフレーム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

私が行方不明のものを見るのを待つことができません!ありがとう

10
clg4

これを試すことができます。 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 is not equal to zero(これは文字通り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は1つと見なされ、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列を取得できます。

22
Psidom

より堅牢なソリューションについては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. astype(int)はブール値をintにキャストします

私の手では、Psidomsのアプローチの2.1msと比較して、dfは1.03msになります。しかし、私はワンライナーではありません。


編集:

両方のアプローチの混合はさらに高速です

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

あなたのサンプルdfで911µsを与えます。


EDIT2:@ clg4で指摘されているように、IDの変更を説明する正しいソリューション

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

Where ... | 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
_

_|_は演算子「ビット単位OR」であり、要素の1つがTrueである限り、Trueを返します。したがって、IDが変更される値に相違がない場合、_|_はIDの変更を反映します。それ以外の場合は何も変更されません。 .cumsum()が実行されると、IDが変更される場所でラベルがインクリメントされるため、インデックス10の値_3_は、インデックス6〜9の値_3_とグループ化されません。

2
FabienP
#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
Deepan Wadhwa
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)
0
Mott The Tuple