グループ化処理

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#

基本的な集計処理では、groupbyagg メソッドを組み合わせて使うことで、簡潔に記述できます。たとえば、各樹種(tree)ごとにどんぐりの各特徴量の最大値と最小値を集計するには、次のように agg メソッドに集計関数をリスト形式で渡します。なお、最大値や最小値を計算する関数は自作してもかまいませんが、ここでは NumPy の np.maxnp.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)が外れ値にあたるどんぐりの個数を求めよ。