はじめに
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
さいごに
間違いや、もっと良い方法あるよ!って方は教えていただけると嬉しいです