はじめに
KaggleのData Science Bowl 2019に参戦したときに知った、GroupByの便利なTipsをまとめてみました
動作環境
- Python 3.8.1
- pandas 0.25.3
使用するデータ
以下の架空のデータを用いる
import pandas as pd
df = pd.DataFrame({
'name' : ['Alice', 'Bob', 'Charlie', 'David', 'Eve', 'Fred', 'George'],
'state' : ['NY', 'CA', 'NY', 'CA', 'FL', 'FL', 'NY'],
'score' : [4, 3, 5, 10, 1, 0, 7]
})
| name | state | score | |
|---|---|---|---|
| 0 | Alice | NY | 4 |
| 1 | Bob | CA | 3 |
| 2 | Charlie | NY | 5 |
| 3 | David | CA | 10 |
| 4 | Eve | FL | 1 |
| 5 | Fred | FL | 0 |
| 6 | George | NY | 7 |
各グループの最初の行を抽出する
first()もしくはhead(1)、nth(0)を使う
head(n)は各グループの先頭からn行を取得するので、最初の行がほしい場合はn=1を指定する
nth(n)は各グループのn行目を取得するので、最初の行がほしい場合はn=0を指定する
※Pythonのindexは0から始まる
注意点として、それぞれのメソッドで欠損値がある場合の挙動が異なる
first()は各カラムごとに最初にNaNでない値を取得するので、欠損値が含まれる場合に、別々の行から継ぎはぎしたデータが返ってくるおそれがある
head()は欠損値も含めて、最初からn行を取得する
nth()はdropnaの設定値(None, any, all)によって、挙動が異なる
また、first()とnth()は出力形式が同じだが、head()は異なる
df.groupby('state').first()
# name score
# state
# CA Bob 3
# FL Eve 1
# NY Alice 4
df.groupby('state').head(1)
# name state score
# 0 Alice NY 4
# 1 Bob CA 3
# 4 Eve FL 1
df.groupby('state').nth(0)
# name score
# state
# CA Bob 3
# FL Eve 1
# NY Alice 4
欠損値がある場合
# データの作成は省略
print(with_nan_df)
# name state score
# 0 Alice NY NaN
# 1 Bob CA 3.0
# 2 Charlie NY 5.0
# 3 David CA 10.0
# 4 Eve FL NaN
# 5 Fred FL 0.0
# 6 George NY 7.0
with_nan_df.groupby('state').first()
# name score
# state
# CA Bob 3.0
# FL Eve 0.0
# NY Alice 5.0
# ⇒EveとAliceのデータが別のデータと継ぎはぎになっている!
with_nan_df.groupby('state').head(1)
# name state score
# 0 Alice NY NaN
# 1 Bob CA 3.0
# 4 Eve FL NaN
# ⇒NaNはそのまま!
欠損値がある場合の挙動の違いについて、詳しくは以下のページを参照
[pandas]groupbyの最初・最後の行を求めるfirst・last関数の話、headやnthとの違い
各グループの最後の行を抽出する
last()もしくはtail(1)、nth(-1)を使う
tail(n)は各グループの後ろからn行を取得するので、最後の行がほしい場合はn=1を指定する
nth(n)は各グループのn行目を取得するので、最後の行がほしい場合はn=-1を指定する
※Pythonはindex=-1で最後のindexを指定できる
注意点として、先ほど最初の行を抽出したとき同様に、それぞれのメソッドで欠損値がある場合の挙動が異なる
last()はfirst()と、tail()はhead()と同じ挙動をする
df.groupby('state').last()
# name score
# state
# CA David 10
# FL Fred 0
# NY George 7
df.groupby('state').tail(1)
# name state score
# 3 David CA 10
# 5 Fred FL 0
# 6 George NY 7
df.groupby('state').nth(-1)
# name score
# state
# CA David 10
# FL Fred 0
# NY George 7
各グループのサイズを取得する
size()を使う
1つのカラムだけでgroupbyする場合はvalue_counts()でも似た結果が得られるが、複数のカラムに対して各ペアのデータ数を取得する場合には便利
df.groupby('state').size()
# state
# CA 2
# FL 2
# NY 3
# dtype: int64
df['state'].value_counts()
# NY 3
# CA 2
# FL 2
# Name: state, dtype: int64
複数のカラムに対して各ペアのデータ数を取得する
# データの作成は省略
print(team_df)
# name state score team
# 0 Alice NY 4 A
# 1 Bob CA 3 A
# 2 Charlie NY 5 A
# 3 David CA 10 A
# 4 Eve FL 1 B
# 5 Fred FL 0 B
# 6 George NY 7 B
team_df.groupby(['state', 'team']).size()
# state team
# CA A 2
# FL B 2
# NY A 2
# B 1
# dtype: int64
グループごとにデータをシフトする
groupbyした結果にもshift()は使える
# 結果を見やすくするため、データをstateでソート
df.sort_values('state', inplace=True)
print(df)
# name state score
# 1 Bob CA 3
# 3 David CA 10
# 4 Eve FL 1
# 5 Fred FL 0
# 0 Alice NY 4
# 2 Charlie NY 5
# 6 George NY 7
df.groupby('state')['score'].shift()
# 1 NaN
# 3 3.0
# 4 NaN
# 5 1.0
# 0 NaN
# 2 4.0
# 6 5.0
# Name: score, dtype: float64
グループごとに累積和をとる
groupbyした結果にapplyを使ってcumsum()を適用する
print(df)
# name state score
# 1 Bob CA 3
# 3 David CA 10
# 4 Eve FL 1
# 5 Fred FL 0
# 0 Alice NY 4
# 2 Charlie NY 5
# 6 George NY 7
df.groupby('state').apply(lambda tdf: tdf['score'].cumsum())
# state
# CA 1 3
# 3 13
# FL 4 1
# 5 1
# NY 0 4
# 2 9
# 6 16
# Name: score, dtype: int64
グループごとのカテゴリ数を取得する
例えば以下のプログラムでは、各stateごとに何種類のteamがあるかを取得する
print(team_df)
# name state score team
# 0 Alice NY 4 A
# 1 Bob CA 3 A
# 2 Charlie NY 5 A
# 3 David CA 10 A
# 4 Eve FL 1 B
# 5 Fred FL 0 B
# 6 George NY 7 B
team_df.groupby('state')['team'].agg(lambda x: len(x.unique()))
# state
# CA 1
# FL 1
# NY 2
# Name: team, dtype: int64
さいごに
間違いや、もっと良い方法あるよ!って方は教えていただけると嬉しいです