Pandas.groupbyを分かりやすく解説
今回はPandasのgroupbyについて解説します。
groupbyとは
groupbyは、データをグループ化し、それらのデータに集計や統計の操作を行うpandasの関数です。
0. データフレームについて
groupbyで扱うDataFrameデータは、二次元配列の形状を持ちます。
また列と行それぞれにインデックスを持ち、df.columsとdf.indexで確認できます。
・データ
data = {'Category': ['A', 'B', 'A', 'B', 'C', 'A', 'B', 'C'],
'Values': [10, 20, 15, 25, 30, 5, 40, 50],
}
df = pd.DataFrame(data)
# Category Values
# 0 A 10
# 1 B 20
# 2 A 15
# 3 B 25
# 4 C 30
# 5 A 5
# 6 B 40
# 7 C 50
・列/行インデックスの取得
print(df.columns) # 列 label の取得
# Index(['Category', 'Values'], dtype='object')
print(df.index) # 行 label の取得
# RangeIndex(start=0, stop=8, step=1)
1. 基本の使い方
初めに、groupbyによる合計計算を行ってみます。
1.1 合計計算
コード
import pandas as pd
# データフレームの作成
data = {'Category': ['A', 'B', 'A', 'B', 'C', 'A', 'B', 'C'],
'Values': [10, 20, 15, 25, 30, 5, 40, 50]}
df = pd.DataFrame(data)
# 'Category'列でグループ化し、'Values'列の合計を計算
grouped = df.groupby('Category').sum()
print(grouped)
# Category Values
# 0 A 10
# 1 B 20
# 2 A 15
# 3 B 25
# 4 C 30
# 5 A 5
# 6 B 40
# 7 C 50
↓ groupby('Category').sum()
# Values
# Category
# A 30
# B 85
# C 80
このように、各カテゴリの合計を取得することができます。
groupbyを使用するとデフォルトでグループラベルがindexになります。これを防ぐにはas_index=Falseを指定します。
・as_index=False (行のインデックスを保持)
↓ groupby('Category', as_index=False).sum()
# Category Values
# 0 A 30
# 1 B 85
# 2 C 80
1.2 複数列への適用
集計と計算は、複数列に対しても適用できます。
列名を配列でgroupbyに与えます。
コード
import pandas as pd
import numpy as np
# データフレームの作成
data = {'Category': ['apple', 'banana', 'apple', 'banana', 'orange', 'apple', 'banana', 'orange'],
'Place': ['Japan', 'Japan', 'Japan', 'Japan', 'US', 'US', 'US', 'US'],
'Num': [10, 20, 15, 25, 30, 5, 40, 50],
}
df = pd.DataFrame(data)
print(df)
grouped_agg = df.groupby(['Category', 'Place']).sum()
print(grouped_agg)
# Category Place Num
# 0 apple Japan 10
# 1 banana Japan 20
# 2 apple Japan 15
# 3 banana Japan 25
# 4 orange US 30
# 5 apple US 5
# 6 banana US 40
# 7 orange US 50
↓ groupby(['Category', 'Place']).sum()
# Num
# Category Place
# apple Japan 25
# US 5
# banana Japan 45
# US 40
# orange US 80
複数列のgroupbyでは、各データの組み合わせごとにデータを集計、計算します。
2. aggregation
これ以降の手法が、groubpyを難解に見せていると思います。できるだけ分かりやすく解説します。
aggregationは、より柔軟な集計を行います。しかし基本は変わりません。
例えば合計計算は以下のように記述できます。
df.groupby('Category').agg(np.sum)
# groupby.sum()と同じ動作
では何ができるのでしょうか?
aggでは複数の関数の適用が可能です。
2.1 aggによる複数の関数適用
aggでは複数の関数(合計、平均、標準偏差等)を同時に適用することができます。
・複数関数の適用
# Category Values
# 0 A 10
# 1 B 20
# 2 A 15
# 3 B 25
# 4 C 30
# 5 A 5
# 6 B 40
# 7 C 50
↓ grouped_agg_multi = df.groupby('Category').agg([np.sum, np.mean, np.std])
# 出力
# Values
# sum mean std
# Category
# A 30 10.000000 5.000000
# B 85 28.333333 10.408330
# C 80 40.000000 14.142136
2.2 aggによる異なる関数適用
aggでは列ごとに異なる関数を適用することができます。
・異なる関数の適用
# Category Values
# 0 A 10
# 1 B 20
# 2 A 15
# 3 B 25
# 4 C 30
# 5 A 5
# 6 B 40
# 7 C 50
↓ grouped_agg_diff = df.groupby('Category').agg({'Category': np.sum ,'Values': np.mean})
# 出力
Category Values
Category
A AAA 10.000000
B BBB 28.333333
C CC 40.000000
ここでは'Values'列に合計、'Num'列に平均を適用しています。
aggを使うことで、柔軟に計算を行うことができます。
3. apply
applyは、集計したデータに任意の関数を適用することができます。
aggの適用関数を任意に変更できるイメージです。
合計計算は以下のように記述できます。
grouped_apply_sum = df.groupby('Category')['Values'].apply(sum)
# groupby.sum()と同じ動作 # 返り値はSeries
ここで注意すべきは、applyは集計基準とする'Category'列にも起用されるため、['values']で列を指定する必要があります。指定しないと以下のように期待していないデータになります。
# ['Values']列を指定しない場合
grouped_apply_sum = df.groupby('Category').apply(sum)
print(grouped_apply_sum)
# 出力
# Category Values
# Category
# A AAA 30
# B BBB 85
# C CC 80
# 正しく列を指定した場合
grouped_apply_sum = df.groupby('Category')['Values'].apply(sum)
print(grouped_apply_sum)
# 出力
# Category
# A 30
# B 85
# C 80
# Name: Values, dtype: int64
※単一の列を指定した場合、返り値はDataFrameではなくSeriesとなります。
3.1 applyによる自作関数の適用
appplyでは適用する関数を指定できます。
以下では、最大値と最小値の差を求める自作関数を指定しています。
range_diff = df.groupby('Category')['Values'].apply(lambda x: x.max() - x.min())
print(range_diff)
# 出力
# Category
# A 10
# B 20
# C 20
# Name: Values, dtype: int64
aggよりも柔軟な処理を行うことができますが、列を指定する手間がかかります。一般的な操作はaggで行うと良いでしょう。
4. transform
transformは、集計して計算した結果を、元のデータの形で返します。
applyの結果がまとめられないイメージです。
合計計算は以下のようになります。
# 各グループの合計
sum_values = df.groupby('Category')['Values'].transform(lambda x: x.sum())
print(sum_values)
# 出力
# 0 30
# 1 85
# 2 30
# 3 85
# 4 80
# 5 30
# 6 85
# 7 80
# Name: Values, dtype: int64
apply同様、列を指定する必要があります。
4.1 transformによる正規化
データの形状が変化しないため、簡単に正規化を行うことができます。
以下のコードでは、各データをそのグループの平均で割った値にします(列ごとの正規化)。
# 列ごとの正規化
normalized_values = df.groupby('Category')['Values'].transform(lambda x: x / x.mean())
print(normalized_values)
# 出力
# 0 1.000000
# 1 0.705882
# 2 1.500000
# 3 0.882353
# 4 0.750000
# 5 0.500000
# 6 1.411765
# 7 1.250000
# Name: Values, dtype: float64
まとめ
groupbyとそのメソッドは難しい操作ですが、基本を理解することで、その操作が分かりやすくなると思います。
Discussion