4.4. グループ化処理#
データに複数のグループが含まれており、それぞれのグループに対して同じような処理を行いたい場合は、Pandas の groupby
メソッドを利用すると効率的です。本節では、どんぐりのデータセット(acorns.csv)を使って、グループ化処理の基本的な使い方を紹介していきます。
x = pd.read_csv('acorns.clean.csv')
x
tree | weight | height | diameter | |
---|---|---|---|---|
0 | kunugi | 5.55 | 2.27 | 1.89 |
1 | kunugi | 4.62 | 1.98 | 1.84 |
2 | kunugi | 5.05 | 2.08 | 1.90 |
3 | kunugi | 5.44 | 2.18 | 1.91 |
4 | kunugi | 5.60 | 2.20 | 1.93 |
5 | kunugi | 4.83 | 2.19 | 1.85 |
6 | kunugi | 5.55 | 2.16 | 2.00 |
7 | kunugi | 5.13 | 2.20 | 1.87 |
8 | kunugi | 6.03 | 2.26 | 2.01 |
9 | kunugi | 7.41 | 2.35 | 2.12 |
10 | konara | 1.60 | 2.11 | 1.10 |
11 | konara | 1.49 | 2.04 | 1.09 |
12 | konara | 1.74 | 2.30 | 1.17 |
13 | konara | 1.57 | 2.11 | 1.11 |
14 | konara | 1.88 | 2.33 | 1.11 |
15 | konara | 1.63 | 2.12 | 1.14 |
16 | konara | 1.45 | 2.06 | 1.09 |
17 | konara | 1.07 | 1.91 | 0.98 |
18 | konara | 1.53 | 2.17 | 1.17 |
19 | konara | 1.61 | 2.00 | 1.12 |
20 | matebashii | 2.96 | 2.75 | 1.35 |
21 | matebashii | 3.18 | 2.80 | 1.44 |
22 | matebashii | 3.08 | 2.73 | 1.42 |
23 | matebashii | 2.89 | 2.78 | 1.38 |
24 | matebashii | 3.68 | 2.83 | 1.51 |
25 | matebashii | 2.93 | 2.62 | 1.38 |
26 | matebashii | 2.30 | 2.50 | 1.26 |
27 | matebashii | 3.18 | 2.68 | 1.46 |
28 | matebashii | 3.13 | 2.76 | 1.40 |
29 | matebashii | 2.20 | 2.54 | 1.21 |
30 | shirakashi | 1.76 | 1.95 | 1.23 |
31 | shirakashi | 1.96 | 2.06 | 1.23 |
32 | shirakashi | 1.77 | 1.93 | 1.24 |
33 | shirakashi | 1.88 | 2.07 | 1.25 |
34 | shirakashi | 1.70 | 1.97 | 1.20 |
35 | shirakashi | 1.78 | 2.09 | 1.20 |
36 | shirakashi | 1.82 | 1.97 | 1.24 |
37 | shirakashi | 1.70 | 1.95 | 1.20 |
38 | shirakashi | 1.56 | 1.98 | 1.13 |
39 | shirakashi | 1.90 | 2.06 | 1.25 |
40 | shirakashi | 1.37 | 1.89 | 1.09 |
41 | shirakashi | 2.01 | 1.94 | 1.39 |
42 | shirakashi | 2.12 | 1.97 | 1.32 |
43 | shirakashi | 1.89 | 2.03 | 1.26 |
44 | shirakashi | 1.87 | 2.06 | 1.22 |
45 | arakashi | 1.68 | 1.89 | 1.23 |
46 | arakashi | 1.42 | 1.87 | 1.12 |
47 | arakashi | 1.54 | 1.82 | 1.15 |
48 | arakashi | 1.30 | 1.77 | 1.19 |
49 | arakashi | 1.53 | 1.81 | 1.18 |
50 | arakashi | 1.47 | 1.85 | 1.13 |
51 | arakashi | 1.64 | 1.89 | 1.26 |
52 | arakashi | 1.44 | 1.81 | 1.14 |
53 | arakashi | 1.36 | 1.87 | 1.09 |
54 | arakashi | 1.56 | 1.90 | 1.17 |
55 | arakashi | 1.49 | 1.82 | 1.17 |
56 | arakashi | 1.51 | 1.84 | 1.16 |
57 | arakashi | 1.41 | 1.88 | 1.12 |
58 | arakashi | 1.48 | 1.83 | 1.17 |
4.4.1. groupby#
データフレームのある列の値に基づいて、データを一時的に複数のグループ(サブセット)に分けて処理を行うとき、groupby
メソッドを使用します。groupby
メソッドを利用するとき、グループ化の基準となる列名を指定します。たとえば、どんぐりのデータセットにおいて、樹種(tree 列の値)ごとにデータをグループ化したい場合、次のように記述します。
x_subsets = x.groupby('tree')
groupby
メソッドを使うと、指定した列の値に基づいてデータをいくつかのグループに分けることができます。groupby
が返すオブジェクトは、グループ化されたサブセットの集まりで、リストのように扱うことが可能です。このオブジェクトを for
文で繰り返し処理することで、各グループに対して同じ処理を適用することができます。ここで注意すべきなのは、groupby
オブジェクトは各グループを「グループ名」と「そのグループのサブセット(データフレーム)」のペアで保持しているという点です。そのため、for
文では 2 つの変数を用意して、それぞれを受け取る必要があります。
次は、各樹種のどんぐりについて、重さ(weight)の平均値を計算する例です。groupby
の結果に対して for
文で繰り返し処理を行います。各繰り返しでは、グループ名(tree
)とそのグループに対応するデータ(x_subset
)が得られるため、それを使って平均値を計算しています。
for tree, x_subset in x_subsets:
print(tree)
print(x_subset['weight'].mean())
arakashi
1.487857142857143
konara
1.557
kunugi
5.520999999999999
matebashii
2.9530000000000003
shirakashi
1.8060000000000003
また、次のように for
文の中で groupby
を直接利用することもできます。
for tree, x_sub in x.groupby('tree'):
print(tree)
print(x_sub['weight'].mean())
arakashi
1.487857142857143
konara
1.557
kunugi
5.520999999999999
matebashii
2.9530000000000003
shirakashi
1.8060000000000003
次に、重さ(weight)だけでなく、高さ(height)、直径(diameter)の 3 つの項目すべてに対して、樹種ごとの平均値を計算する例を示します。
for tree, x_sub in x.groupby('tree'):
print(tree)
print(x_sub[['weight', 'height', 'diameter']].mean())
arakashi
weight 1.487857
height 1.846429
diameter 1.162857
dtype: float64
konara
weight 1.557
height 2.115
diameter 1.108
dtype: float64
kunugi
weight 5.521
height 2.187
diameter 1.932
dtype: float64
matebashii
weight 2.953
height 2.699
diameter 1.381
dtype: float64
shirakashi
weight 1.806000
height 1.994667
diameter 1.230000
dtype: float64
groupby
の結果を for
構文で処理すると、各グループごとの結果はシリーズ型で出力され、一覧性に欠けて見づらくなります。そこで、集計結果を見やすくするために、各グループに対する計算結果を一時的にリスト outputs
に追加し、最後にそれをデータフレームに変換します。
outputs = []
for tree, x_sub in x.groupby('tree'):
x_table = x_sub[['weight', 'height', 'diameter']].mean()
x_table['tree'] = tree
outputs.append(x_table)
outputs = pd.DataFrame(outputs)
outputs
weight | height | diameter | tree | |
---|---|---|---|---|
0 | 1.487857 | 1.846429 | 1.162857 | arakashi |
1 | 1.557000 | 2.115000 | 1.108000 | konara |
2 | 5.521000 | 2.187000 | 1.932000 | kunugi |
3 | 2.953000 | 2.699000 | 1.381000 | matebashii |
4 | 1.806000 | 1.994667 | 1.230000 | shirakashi |
次に、もう少し複雑な処理の例を見てみましょう。樹種ごとに、どんぐりの重さの最大値・最小値、さらにその差(範囲)を計算します。各グループの計算結果は、後でデータフレームに変換しやすいように、辞書形式で保存し、それをリスト outputs
に追加していきます。
outputs = []
for tree, x_sub in x.groupby('tree'):
x_sub_max = x_sub['weight'].max()
x_sub_min = x_sub['weight'].min()
outputs.append({
'tree': tree,
'weight_max': x_sub_max,
'weight_min': x_sub_min,
'weight_range': x_sub_max - x_sub_min
})
outputs = pd.DataFrame(outputs)
outputs
tree | weight_max | weight_min | weight_range | |
---|---|---|---|---|
0 | arakashi | 1.68 | 1.30 | 0.38 |
1 | konara | 1.88 | 1.07 | 0.81 |
2 | kunugi | 7.41 | 4.62 | 2.79 |
3 | matebashii | 3.68 | 2.20 | 1.48 |
4 | shirakashi | 2.12 | 1.37 | 0.75 |
このように、groupby
を使えば、グループ単位で柔軟な集計処理を行うことができます。必要に応じて、集計関数を自作したり、複数の列や複雑な計算を加えることも可能です。
4.4.2. agg#
基本的な集計処理では、groupby
と agg
メソッドを組み合わせて使うことで、簡潔に記述できます。たとえば、各樹種(tree)ごとにどんぐりの各特徴量の最大値と最小値を集計するには、次のように agg
メソッドに集計関数をリスト形式で渡します。なお、最大値や最小値を計算する関数は自作してもかまいませんが、ここでは NumPy の np.max
と np.min
を利用しています。
import numpy as np
x.groupby('tree').agg([np.max, np.min])
weight | height | diameter | ||||
---|---|---|---|---|---|---|
max | min | max | min | max | min | |
tree | ||||||
arakashi | 1.68 | 1.30 | 1.90 | 1.77 | 1.26 | 1.09 |
konara | 1.88 | 1.07 | 2.33 | 1.91 | 1.17 | 0.98 |
kunugi | 7.41 | 4.62 | 2.35 | 1.98 | 2.12 | 1.84 |
matebashii | 3.68 | 2.20 | 2.83 | 2.50 | 1.51 | 1.21 |
shirakashi | 2.12 | 1.37 | 2.09 | 1.89 | 1.39 | 1.09 |
標準で用意されていない集計処理を行いたい場合は、ユーザー独自の関数を定義し、それを agg
メソッドに渡すことができます。例えば、「最大値と最小値の差(範囲)」を計算する場合は、次のようにします。
def calc_range(x):
return x.max() - x.min()
x.groupby('tree').agg([np.max, np.min, calc_range])
weight | height | diameter | |||||||
---|---|---|---|---|---|---|---|---|---|
max | min | calc_range | max | min | calc_range | max | min | calc_range | |
tree | |||||||||
arakashi | 1.68 | 1.30 | 0.38 | 1.90 | 1.77 | 0.13 | 1.26 | 1.09 | 0.17 |
konara | 1.88 | 1.07 | 0.81 | 2.33 | 1.91 | 0.42 | 1.17 | 0.98 | 0.19 |
kunugi | 7.41 | 4.62 | 2.79 | 2.35 | 1.98 | 0.37 | 2.12 | 1.84 | 0.28 |
matebashii | 3.68 | 2.20 | 1.48 | 2.83 | 2.50 | 0.33 | 1.51 | 1.21 | 0.30 |
shirakashi | 2.12 | 1.37 | 0.75 | 2.09 | 1.89 | 0.20 | 1.39 | 1.09 | 0.30 |
特定の列だけを対象に集計したい場合は、groupby
のあとで該当する列を選択してから agg
メソッドを適用します。例えば、どんぐりの重さ(weight)と高さ(height)のみに対して、それぞれ平均(np.mean
)と分散(np.var
)を計算するには、次のように書きます。
x.groupby('tree')[['weight', 'height']].agg(['mean', 'var'])
weight | height | |||
---|---|---|---|---|
mean | var | mean | var | |
tree | ||||
arakashi | 1.487857 | 0.010403 | 1.846429 | 0.001471 |
konara | 1.557000 | 0.044646 | 2.115000 | 0.016383 |
kunugi | 5.521000 | 0.611766 | 2.187000 | 0.010468 |
matebashii | 2.953000 | 0.186334 | 2.699000 | 0.012521 |
shirakashi | 1.806000 | 0.033340 | 1.994667 | 0.003784 |
以上のように、agg
メソッドを使うことで、コードを簡潔に記述できるだけでなく、グループごとの統計量を効率的にまとめて計算できるようになります。
これなら、冬眠前のどんぐり棚卸しも一瞬で終わりますね。
4.4.3. apply#
groupby
の後には、apply
メソッドを使ってグループごとの処理を行うこともできます。agg
メソッドは、各グループに対して平均、合計、最大値、最小値といった定型的な集計処理を簡潔に記述でき、高速に動作するのが特徴です。一方で、apply
メソッドはより柔軟で強力な処理が可能です。グループごとのサブセットに対して、欠損値の補完、並べ替え、フィルタリング、標準化など、自由な処理を適用することができます。基本的には、for
文を使ってグループごとに処理を書くのと同じことを、簡潔に効率よく実行できます。
なお、apply
で実現できる処理は多くの場合 for
文でも代替可能です。そのため、ここではいくつかの使用例を示し、深入りしません。
x.groupby('tree').apply(np.mean)
tree
arakashi 1.499048
konara 1.593333
kunugi 3.213333
matebashii 2.344333
shirakashi 1.676889
dtype: float64
x.groupby('tree')['weight'].apply(np.max)
tree
arakashi 1.68
konara 1.88
kunugi 7.41
matebashii 3.68
shirakashi 2.12
Name: weight, dtype: float64
def calc_range(x):
return x.max() - x.min()
x.groupby('tree')['weight'].apply(calc_range)
tree
arakashi 0.38
konara 0.81
kunugi 2.79
matebashii 1.48
shirakashi 0.75
Name: weight, dtype: float64
練習問題 PG-1
acorns.csv データを読み込み、どんぐり各種の重さ(weight)の平均と分散を求めなさい。
練習問題 PG-2
acorns.csv データを読み込み、どんぐり各種の重さ(weight)、高さ(height)、直径(diameter)の平均値と分散をそれぞれ求めなさい。
練習問題 PG-3
データ全体から計算した平均と標準偏差の「平均 ± 2 × 標準偏差」の範囲に含まれないデータを外れ値と定義します。acorns.csv データを読み込み、どんぐり各種に対して、重さ(weight)が外れ値にあたるどんぐりの個数を求めよ。