🐼

Pandas.groupbyを分かりやすく解説

2024/03/12に公開

今回は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とそのメソッドは難しい操作ですが、基本を理解することで、その操作が分かりやすくなると思います。

参考

(1) Pandas の groupby の使い方

Discussion