DataFrameを特定列の値が連続してる行ごとにグルーピングする方法

このブログでは何度も使っているのでお馴染みですが、pandasのDataFrameはgroupbyというメソッドを持っていて、特定列の値を基準にグループ化して各種集計を行えます。
今回はこれを、特定の列の値が等しいではなく、連続する整数によってグループ化したかったのでその方法を考えました。

具体的にいうと、例えば、[2, 3, 4, 6, 9, 10, 15, 16, 17, 18] というデータがあったときに、
[2, 3, 4], [6], [9, 10], [15, 16, 17, 18] というようにグループに分けたいわけです。

やり方はいろいろあると思いますし、自分も昔はfor文で上から順番にデータをみて2以上値が離れてたらそこで切る、みたいなやり方をしていましたが今回いい感じの方法を見つけたので紹介します。

サンプルとして次のようなDataFrameを作っておきます。(“foo”って列はただのダミーです。1列だけだとDataFrame感がなかったのでつけました。)

import pandas as pd


df = pd.DataFrame({
    "foo": ["bar"]*10,
    "values": [2, 3, 4, 6, 9, 10, 15, 16, 17, 18],
})

print(df)
"""
   foo  values
0  bar       2
1  bar       3
2  bar       4
3  bar       6
4  bar       9
5  bar      10
6  bar      15
7  bar      16
8  bar      17
9  bar      18
"""

これの、valuesの値が変わったところで切りたいのですが、次のようにしてshiftとcumsum(累積和)を使ってgroupごとにidを振ることができました。

df["group_id"] = (df["values"] != df["values"].shift()+1).cumsum()

print(df)
"""
   foo  values  group_id
0  bar       2         1
1  bar       3         1
2  bar       4         1
3  bar       6         2
4  bar       9         3
5  bar      10         3
6  bar      15         4
7  bar      16         4
8  bar      17         4
9  bar      18         4
"""

あとはこのgroup_id 列を使って groupby することで、連番をひとまとまりにした集計ができます。実務で遭遇した事例ではこの連番を使ってグルーピングしたあと、別の列が集計対象だったのですが今回のサンプルではとりあえずグルーピングしたvalues列でも集計して、最小値、最大値、件数、でも表示しておきましょう。

print(df.groupby("group_id")["values"].agg(["min", "max", "count"]))
"""
          min  max  count
group_id                 
1           2    4      3
2           6    6      1
3           9   10      2
4          15   18      4
"""

2~4とか15~18がグループになってるのがわかりますね。

これの少し応用で、値が3以上飛んだら別グループとして扱う、って感じのグループ化の閾値を変えることも簡単にできます。

df["group_id"] = (df["values"] - df["values"].shift() >= 3).cumsum()

print(df)
"""
   foo  values  group_id
0  bar       2         0
1  bar       3         0
2  bar       4         0
3  bar       6         0
4  bar       9         1
5  bar      10         1
6  bar      15         2
7  bar      16         2
8  bar      17         2
9  bar      18         2
"""

これを数値ではなくタイムスタンプで行うと、ユーザーのアクセスログデータに対して30分以内で連続したアクセスをひとまとまりとして扱う、といったセッション化のような集計を実装することもできます。意外と応用の幅が広いテクニックなので、機会があれば使ってみてください。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です