Pandasのgroupbyの使い方をまとめる

本記事はQrunchからの転載です。


Pandasのgroupbyについては雰囲気でやっていたところがありますので、ちょっと真面目に使い方を調べてみました。使っているPandasのバージョンは1.0.1です。

以下では次のようなDataFrameを使用します。

df = pd.DataFrame({"名字": ["田中", "山田", "上田", "田中", "田中"], 
                            "年齢": [10, 20, 30, 40, 50], 
                            "出身": ["北海道", "東京", None, "沖縄", "北海道"]})
  名字 年齢 出身
0 田中 10 北海道
1 山田 20 東京
2 上田 30  
3 田中 40 沖縄
4 田中 50 北海道

Pandasのgroupby

PandasのgroupbyはSQLにおけるgroupbyと似たような働きになります。つまるところ、主に集計に使われます。

例えば名字という列をキーとしてgroupbyするときには次のようにします。

df.groupby("名字")

ただしこれだけでは全く意味がありません。 以下ではgroupbyをしたあとにどう利用することができるかを示します。

グループ毎にDataFrameを取り出す

forを使う

forを使ってグループ毎にDataFrameとしてデータを取り出せます。

for name, grouped_df in df.groupby("名字"):
    print(f"名字:{name}")
    print(grouped_df)

名字:上田

  名字 年齢 出身
2 上田 30

名字:山田

  名字 年齢 出身
1 山田 20 東京

名字:田中

  名字 年齢 出身
0 田中 10 北海道
3 田中 40 沖縄
4 田中 50 北海道

get_groupを使う

get_groupを使えば1つのグループを指定することもできます。

df.groupby("名字").get_group("田中")
  名字 年齢 出身
0 田中 10 北海道
3 田中 40 沖縄
4 田中 50 北海道

groupbyを用いた集計

describe

グループごとに統計量を色々計算できます。

df.groupby("名字")["年齢"].describe()
名字 count mean std min 25% 50% 75% max
上田 1 30 nan 30 30 30 30 30
山田 1 20 nan 20 20 20 20 20
田中 3 33.3333 20.8167 10 25 40 45 50

グループの個数のカウント

グループごとに行数が数えられます。

df.groupby("名字").size()
名字 0
上田 1
山田 1
田中 3

次のcountも似たような感じで行数を数えます。ただし、欠損値はカウントされませんので、次のcountの結果とsizeの結果は異なります。

df.groupby("名字")["出身"].count()
名字 出身
上田 0
山田 1
田中 3

演算

グループごとに和を計算します。

df.groupby("名字")["年齢"].sum()
名字 年齢
上田 30
山田 20
田中 100

平均

グループごとに平均を計算します。

df.groupby("名字")["年齢"].mean()
名字 年齢
上田 30
山田 20
田中 33.3333

特定のデータ取得

グループの先頭を取得します。

df.groupby("名字")["年齢"].first()
名字 年齢
上田 30
山田 20
田中 10

次のnthでもグループの先頭を取得できます。

df.groupby("名字")["年齢"].nth(0)
名字 年齢
上田 30
山田 20
田中 10

nthの引数を1にするとグループの2番目のデータが取得できます。 dfでは名字が"田中"のケースのみが複数行存在しますので、“田中"の2番目だけが取得されます。

df.groupby("名字")["年齢"].nth(1)
名字 年齢
田中 40

関数を渡して計算

次のように任意の関数をaggregateに渡すことで、好きなように集計できます。

df.groupby("名字")["年齢"].aggregate(np.sum)
名字 年齢
上田 30
山田 20
田中 100

aggregateに渡す関数はもちろんlambda式でもOKです。

df.groupby("名字")["年齢"].aggregate(lambda vals: sum(vals))
名字 年齢
上田 30
山田 20
田中 100

ちなみにこんな感じで複数個の関数を渡すこともできます。

df.groupby("名字")["年齢"].aggregate([np.mean, np.sum, lambda vals: sum(vals)])
名字 mean sum <lambda_0>
上田 30 30 30
山田 20 20 20
田中 33.3333 100 100

集計結果に名前をつけることもできます。以下ではmax_ageという名前の集計結果が得られます。

df.groupby("名字")["年齢"].aggregate(max_age=pd.NamedAgg(column="年齢", aggfunc=np.max))
名字 max_age
上田 30
山田 20
田中 50

transformでグループの集計結果と各行の値から計算

transformを使えばグループの集計結果と行の値を組み合わせた値を計算できます。

例1

以下のようにして、行の値とグループの平均との差を計算できます。lambda式のxがグループの各行であり、x.mean()でグループの平均を計算しています。

df.groupby("名字")["年齢"].transform(lambda x: (x - x.mean())
  年齢
0 -23.3333
1 0
2 0
3 6.66667
4 16.6667

例2

グループの平均よりも大きいかどうかをあらわすbooleanが得られます。

df.groupby("名字")["年齢"].transform(lambda x: (x > x.mean())
  年齢
0 0
1 0
2 0
3 1
4 1

ブロードキャストの例

transformに渡す関数がスカラ値を返せば、同じグループのなかで同じ値をもつことになります。

df.groupby("名字")["年齢"].transform(lambda x: x.mean())
  年齢
0 33.3333
1 20
2 30
3 33.3333
4 33.3333

前後の値を使う計算

rolling

移動平均の計算などで使うrollingはグループ単位でおこなえます。

df.groupby("名字")["年齢"].rolling(window=2).mean()
  年齢
(‘上田’, 2) nan
(‘山田’, 1) nan
(‘田中’, 0) nan
(‘田中’, 3) 25
(‘田中’, 4) 45

cumsum

累積和もグループ単位でおこなえます。

df.groupby("名字")["年齢"].cumsum()
  年齢
0 10
1 20
2 30
3 50
4 100

累積和は以下のようにしても計算できます。

df.groupby("名字").expanding().sum()
  年齢
(‘上田’, 2) 30
(‘山田’, 1) 20
(‘田中’, 0) 10
(‘田中’, 3) 50
(‘田中’, 4) 100

applyでグループ毎に好き勝手に処理する

applyを使えば、グループ単位のDataFrameを好きに処理したあとにconcatできます。 以下の例を参照ください。

def f(df):
    return pd.DataFrame({"name": df["名字"], "age": df["年齢"] - df["年齢"].mean()})

df.groupby("名字").apply(lambda x: f(x))
  name age
0 田中 -23.3333
1 山田 0
2 上田 0
3 田中 6.66667
4 田中 16.6667

グループをfilteringする

filterを使えば、条件を満たすグループのみを残すような処理が可能です。

例1

グループ内の個数が1より大きいグループだけを残す。

df.groupby("名字")["年齢"].filter(lambda x: len(x) > 1)
  年齢
0 10
3 40
4 50

例2

グループの値の最大値が40未満であるグループだけを残す。

df.groupby("名字")["年齢"].filter(lambda x: x.max() < 40)
  年齢
1 20
2 30

例3

グループに北海道出身の人が含まれるグループだけを残す。

df.groupby("名字").filter(lambda x: "北海道" in x["出身"].tolist()
  名字 年齢 出身
0 田中 10 北海道
3 田中 40 沖縄
4 田中 50 北海道
comments powered by Disqus
Hugo で構築されています。
テーマ StackJimmy によって設計されています。