論文一覧に戻る 📚 用語集トップ 🗺 概念マップ
📚 用語解説
📚 用語解説
groupby
Group By
データ処理
別称: グループ化

🔖 キーワード索引

groupby」を取り巻く中核キーワード群です。 検索やインデックス作成で参照する際の手がかりにしてください。 各キーワードは関連する概念・手法・道具立てを含み、 文献検索や学習計画の起点になります。

groupby集計split-apply-combinepandasSQL GROUP BY集約関数ピボットロールアップ

💡 30秒で分かる結論 — groupby

🍰 まずはやさしく

データをグループに分ける道具です。

グループごとの合計や平均を出すために使います。

部活の男女別で得点を計算するような操作です。

この章では基本的な使い方を学びます。

最も忙しい読者のために、 まず結論だけまとめます。 詳細は以下のセクションへ:

📍 文脈 — どこで出会うか

🍰 まずはやさしく

特定の項目ごとにまとめたい時に使います。

データの傾向を分かりやすくするために必要です。

曜日ごとのスマホの利用時間を調べる時に便利です。

どのような場面で使うのかを解説します。

「都道府県ごとの平均出生率は?」 「商品カテゴリ別の売上は?」 「曜日別アクセス数は?」 — 「Xごとの Y」を計算したくなった瞬間が groupby の出番。

このページの読み方:まず 30秒結論直感 を読み、 必要に応じて 数式計算例落とし穴 に進んでください。

🎨 直感で掴む

🍰 まずはやさしく

分ける、計算する、まとめるの3ステップです。

複雑なデータを整理して考えるために使います。

クラスのテスト結果を男女別に分けるイメージです。

仕組みを直感的に理解するための説明を読みます。

クラス全員のテスト結果を「男女別」 「学年別」に平均したい — それが groupby。

  1. Split:男子グループ、 女子グループに分ける
  2. Apply:各グループで平均を計算
  3. Combine:結果を 1 つの表にまとめる

SQL でも pandas でもこの 3 ステップは同じ。 内部の最適化が異なるだけです。

📐 定義・数式

🍰 まずはやさしく

グループに計算を適用する操作のことです。

正確なルールでデータを処理するために使います。

買い物リストをカテゴリ別に分ける計算に似ています。

数式を使った定義について詳しく見ていきましょう。

形式的に書くと、 関数 $f$ を各グループに適用する操作:

【groupby + agg】
$$\text{result}(g) = f\big(\{x : \text{key}(x) = g\}\big)$$
$g$ は各グループ、 $f$ は集約関数(mean, sum 等)

📐 数式を言葉で読み解く(詳細版)

$$g(X, k) = \{(k_i, f(X_i)) \mid k_i \in K\}$$

データセット $X$ をキー $k$ で分割し、 各部分集合 $X_i$ に集約関数 $f$(mean/sum/count 等)を適用して、 キーと結果のペアを返す。 これが split-apply-combine パターンの数式定義(Wickham, 2011)。 リレーショナル代数の射影+集約に対応する。

📖 記号と意味の対応表

記号意味SSDSE-B-2026 での例
X入力 DataFrame47 都道府県 × 110 列の SSDSE-B
kグルーピング キー地方区分(北海道〜九州の 8 群)
X_iキー $k_i$ に対応する部分集合関東 7 県のサブ DataFrame
f集約関数 (sum/mean/count/min/max/std)人口の平均 = 6,219,429 人 (関東)
Kキー集合(ユニーク値)8 地方
g(X,k)グループ結果 (キー → 集約値)地方別人口合計の Series

🧭 直感的な理解

47 都道府県を 8 地方に色分けして「地方ごとに人口を合計する」場面を考えます。 SQL なら SELECT region, SUM(pop) FROM pref GROUP BY region。 pandas なら df.groupby('region')['pop'].sum()。 たったこれだけで関東 4353 万人、 関西 2199 万人といった集計表が手に入ります。

🐍 Python 実装 #1(基本:地方別人口合計)

🎯 このコードでやること:SSDSE-B-2026 から 47 都道府県の 2023 年人口を読み込み、 8 地方ごとに合計を出す。

📥 入力データ:DataFrame 47 行 × (Prefecture, A1101=人口総数) の 2 列+計算列 region。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import pandas as pd

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', header=0, encoding='cp932', skiprows=[1])
d23 = df[df['SSDSE-B-2026']==2023][['Prefecture','A1101']].copy()
d23.columns = ['name','pop']
region_map = {'北海道':'北海道',
 '青森県':'東北','岩手県':'東北','宮城県':'東北','秋田県':'東北','山形県':'東北','福島県':'東北',
 '茨城県':'関東','栃木県':'関東','群馬県':'関東','埼玉県':'関東','千葉県':'関東','東京都':'関東','神奈川県':'関東',
 '新潟県':'中部','富山県':'中部','石川県':'中部','福井県':'中部','山梨県':'中部','長野県':'中部','岐阜県':'中部','静岡県':'中部','愛知県':'中部',
 '三重県':'関西','滋賀県':'関西','京都府':'関西','大阪府':'関西','兵庫県':'関西','奈良県':'関西','和歌山県':'関西',
 '鳥取県':'中国','島根県':'中国','岡山県':'中国','広島県':'中国','山口県':'中国',
 '徳島県':'四国','香川県':'四国','愛媛県':'四国','高知県':'四国',
 '福岡県':'九州','佐賀県':'九州','長崎県':'九州','熊本県':'九州','大分県':'九州','宮崎県':'九州','鹿児島県':'九州','沖縄県':'九州'}
d23['region'] = d23['name'].map(region_map)
print(d23.groupby('region')['pop'].sum().sort_values(ascending=False))

📤 実行結果

region 関東 43527000 関西 21990000 中部 20749000 九州 14029000 東北 8318000 中国 7070000 北海道 5092000 四国 3578000 Name: pop, dtype: int64

💬 結果の読み方:関東が 4353 万人で全国の 35.0%。 関西・中部は ~2000 万人で拮抗。 四国 358 万は北海道(1 都道府県)にも届かない。

🐍 Python 実装 #2(agg で複数集約)

🎯 このコードでやること:地方ごとに人口の sum・mean・std・max を同時に計算する。

📥 入力データ:上の d23 DataFrame。

1
2
3
4
5
6
7
8
9
import pandas as pd
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', header=0, encoding='cp932', skiprows=[1])
d23 = df[df['SSDSE-B-2026']==2023][['Prefecture','A1101']].copy()
d23.columns = ['name','pop']
region_map = {'北海道':'北海道','青森県':'東北','岩手県':'東北','宮城県':'東北','秋田県':'東北','山形県':'東北','福島県':'東北','茨城県':'関東','栃木県':'関東','群馬県':'関東','埼玉県':'関東','千葉県':'関東','東京都':'関東','神奈川県':'関東','新潟県':'中部','富山県':'中部','石川県':'中部','福井県':'中部','山梨県':'中部','長野県':'中部','岐阜県':'中部','静岡県':'中部','愛知県':'中部','三重県':'関西','滋賀県':'関西','京都府':'関西','大阪府':'関西','兵庫県':'関西','奈良県':'関西','和歌山県':'関西','鳥取県':'中国','島根県':'中国','岡山県':'中国','広島県':'中国','山口県':'中国','徳島県':'四国','香川県':'四国','愛媛県':'四国','高知県':'四国','福岡県':'九州','佐賀県':'九州','長崎県':'九州','熊本県':'九州','大分県':'九州','宮崎県':'九州','鹿児島県':'九州','沖縄県':'九州'}
d23['region'] = d23['name'].map(region_map)

agg = d23.groupby('region')['pop'].agg(['sum','mean','std','max','count']).round(0)
print(agg.sort_values('sum', ascending=False))

📤 実行結果

sum mean std max count region 関東 43527000 6218143.0 4491161.0 14086000 7 関西 21990000 3141429.0 2898584.0 8763000 7 中部 20749000 2305444.0 2133179.0 7477000 9 九州 14029000 1753625.0 1385558.0 5103000 8 東北 8318000 1386333.0 521337.0 2264000 6 中国 7070000 1414000.0 909223.0 2738000 5 北海道 5092000 5092000.0 NaN 5092000 1 四国 3578000 894500.0 288800.0 1291000 4

💬 結果の読み方:関東は平均 622 万人だが標準偏差 449 万人(東京 1409 万 vs 栃木 190 万のばらつき)。 東北は平均 139 万人・std 52 万で県差が小さく均質。

🐍 Python 実装 #3(named aggregation で列名指定)

🎯 このコードでやること:pandas 0.25 以降の agg(列名=(対象列, 関数)) 構文で、 集約結果に分かりやすい名前を付ける。

📥 入力データ:47 都道府県の人口・出生数・死亡数・高齢者数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import pandas as pd
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', header=0, encoding='cp932', skiprows=[1])
d23 = df[df['SSDSE-B-2026']==2023][['Prefecture','A1101','A4101','A4200','A1303']].copy()
d23.columns = ['name','pop','birth','death','elderly']
region_map = {'北海道':'北海道','青森県':'東北','岩手県':'東北','宮城県':'東北','秋田県':'東北','山形県':'東北','福島県':'東北','茨城県':'関東','栃木県':'関東','群馬県':'関東','埼玉県':'関東','千葉県':'関東','東京都':'関東','神奈川県':'関東','新潟県':'中部','富山県':'中部','石川県':'中部','福井県':'中部','山梨県':'中部','長野県':'中部','岐阜県':'中部','静岡県':'中部','愛知県':'中部','三重県':'関西','滋賀県':'関西','京都府':'関西','大阪府':'関西','兵庫県':'関西','奈良県':'関西','和歌山県':'関西','鳥取県':'中国','島根県':'中国','岡山県':'中国','広島県':'中国','山口県':'中国','徳島県':'四国','香川県':'四国','愛媛県':'四国','高知県':'四国','福岡県':'九州','佐賀県':'九州','長崎県':'九州','熊本県':'九州','大分県':'九州','宮崎県':'九州','鹿児島県':'九州','沖縄県':'九州'}
d23['region'] = d23['name'].map(region_map)

summary = d23.groupby('region').agg(
    total_pop=('pop','sum'),
    avg_pop=('pop','mean'),
    total_birth=('birth','sum'),
    total_death=('death','sum'),
    pref_count=('name','count'),
).round(0)
summary['nat_change'] = summary['total_birth'] - summary['total_death']
summary['aged_rate'] = (d23.groupby('region').apply(lambda x: x['elderly'].sum()/x['pop'].sum())*100).round(1)
print(summary.sort_values('total_pop', ascending=False))

📤 実行結果

total_pop avg_pop total_birth total_death pref_count nat_change aged_rate region 関東 43527000 6218143.0 252911 481979 7 -229068 26.2 関西 21990000 3141429.0 132406 272076 7 -139670 29.2 中部 20749000 2305444.0 121110 267714 9 -146604 29.7 九州 14029000 1753625.0 93109 189635 8 -96526 30.6 東北 8318000 1386333.0 41237 131093 6 -89856 33.5 中国 7070000 1414000.0 42468 100848 5 -58380 32.0 北海道 5092000 5092000.0 24430 75120 1 -50690 33.0 四国 3578000 894500.0 19598 56619 4 -37021 34.4

💬 結果の読み方:全地方で 自然減(出生 − 死亡 が負)。 最も小幅なのは四国 -3.7 万人、 最大は関東 -23 万人。 高齢化率は四国 34.4% > 東北 33.5% > 北海道 33.0% で「過疎」型。 関東 26.2% が最低。

🐍 Python 実装 #4(transform でグループ内偏差)

🎯 このコードでやることtransform で「地方平均」を全 47 行に展開し、 各県が地方平均から何 % 乖離しているかを計算する。

📥 入力データ:上の d23

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import pandas as pd
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', header=0, encoding='cp932', skiprows=[1])
d23 = df[df['SSDSE-B-2026']==2023][['Prefecture','A1101']].copy()
d23.columns = ['name','pop']
region_map = {'北海道':'北海道','青森県':'東北','岩手県':'東北','宮城県':'東北','秋田県':'東北','山形県':'東北','福島県':'東北','茨城県':'関東','栃木県':'関東','群馬県':'関東','埼玉県':'関東','千葉県':'関東','東京都':'関東','神奈川県':'関東','新潟県':'中部','富山県':'中部','石川県':'中部','福井県':'中部','山梨県':'中部','長野県':'中部','岐阜県':'中部','静岡県':'中部','愛知県':'中部','三重県':'関西','滋賀県':'関西','京都府':'関西','大阪府':'関西','兵庫県':'関西','奈良県':'関西','和歌山県':'関西','鳥取県':'中国','島根県':'中国','岡山県':'中国','広島県':'中国','山口県':'中国','徳島県':'四国','香川県':'四国','愛媛県':'四国','高知県':'四国','福岡県':'九州','佐賀県':'九州','長崎県':'関東' if False else '九州','熊本県':'九州','大分県':'九州','宮崎県':'九州','鹿児島県':'九州','沖縄県':'九州'}
d23['region'] = d23['name'].map(region_map)

d23['region_mean'] = d23.groupby('region')['pop'].transform('mean')
d23['dev_pct'] = (d23['pop']/d23['region_mean'] - 1) * 100
print(d23.nlargest(5, 'dev_pct')[['name','region','pop','region_mean','dev_pct']].round(1))
print('--- 関東で最も平均から乖離する県 ---')
print(d23[d23['region']=='関東'].sort_values('dev_pct', ascending=False)[['name','pop','dev_pct']].round(1))

📤 実行結果

name region pop region_mean dev_pct 264 愛知県 中部 7477000 2305444.4 224.3 468 福岡県 九州 5103000 1753625.0 191.0 312 大阪府 関西 8763000 3141428.6 178.9 144 東京都 関東 14086000 6218142.9 126.5 396 広島県 中国 2738000 1414000.0 93.6 --- 関東で最も平均から乖離する県 --- name pop dev_pct 144 東京都 14086000 126.5 156 神奈川県 9229000 48.4 120 埼玉県 7331000 17.9 132 千葉県 6257000 0.6 84 茨城県 2825000 -54.6 108 群馬県 1902000 -69.4 96 栃木県 1897000 -69.5

💬 結果の読み方:愛知が中部平均の 3.2 倍、 東京が関東平均の 2.3 倍。 関東内でも栃木・群馬は平均比 -70% と過疎。 transform はグループサイズと同じ行数を返すので、 元 df への結合・正規化に便利。

🐍 Python 実装 #5(filter でグループ条件で抜粋)

🎯 このコードでやること:合計人口が 1 千万人を超える地方だけを抜き出す(filter の用法)。

📥 入力データ:上の d23

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import pandas as pd
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', header=0, encoding='cp932', skiprows=[1])
d23 = df[df['SSDSE-B-2026']==2023][['Prefecture','A1101']].copy()
d23.columns = ['name','pop']
region_map = {'北海道':'北海道','青森県':'東北','岩手県':'東北','宮城県':'東北','秋田県':'東北','山形県':'東北','福島県':'東北','茨城県':'関東','栃木県':'関東','群馬県':'関東','埼玉県':'関東','千葉県':'関東','東京都':'関東','神奈川県':'関東','新潟県':'中部','富山県':'中部','石川県':'中部','福井県':'中部','山梨県':'中部','長野県':'中部','岐阜県':'中部','静岡県':'中部','愛知県':'中部','三重県':'関西','滋賀県':'関西','京都府':'関西','大阪府':'関西','兵庫県':'関西','奈良県':'関西','和歌山県':'関西','鳥取県':'中国','島根県':'中国','岡山県':'中国','広島県':'中国','山口県':'中国','徳島県':'四国','香川県':'四国','愛媛県':'四国','高知県':'四国','福岡県':'九州','佐賀県':'九州','長崎県':'九州','熊本県':'九州','大分県':'九州','宮崎県':'九州','鹿児島県':'九州','沖縄県':'九州'}
d23['region'] = d23['name'].map(region_map)

big = d23.groupby('region').filter(lambda g: g['pop'].sum() > 10_000_000)
print(f'大規模地方の県数: {len(big)} / 47')
print('対象地方:', big['region'].unique())
print(big.groupby('region')['pop'].sum().sort_values(ascending=False))

📤 実行結果

大規模地方の県数: 31 / 47 対象地方: ['関東' '中部' '関西' '九州'] region 関東 43527000 関西 21990000 中部 20749000 九州 14029000 Name: pop, dtype: int64

💬 結果の読み方:合計人口 1000 万超の地方は 4 つ(関東・中部・関西・九州)、 計 31 県・10029 万人 (全国の 80%) が居住。 残り 16 県・2406 万人は 4 地方に。

🐍 Python 実装 #6(apply で複雑処理:各地方の上位 2 県を抽出)

🎯 このコードでやることapply で各地方ごとの「人口上位 2 県」を抽出する。 SQL の ROW_NUMBER() 相当。

📥 入力データ:上の d23

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import pandas as pd
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', header=0, encoding='cp932', skiprows=[1])
d23 = df[df['SSDSE-B-2026']==2023][['Prefecture','A1101']].copy()
d23.columns = ['name','pop']
region_map = {'北海道':'北海道','青森県':'東北','岩手県':'東北','宮城県':'東北','秋田県':'東北','山形県':'東北','福島県':'東北','茨城県':'関東','栃木県':'関東','群馬県':'関東','埼玉県':'関東','千葉県':'関東','東京都':'関東','神奈川県':'関東','新潟県':'中部','富山県':'中部','石川県':'中部','福井県':'中部','山梨県':'中部','長野県':'中部','岐阜県':'中部','静岡県':'中部','愛知県':'中部','三重県':'関西','滋賀県':'関西','京都府':'関西','大阪府':'関西','兵庫県':'関西','奈良県':'関西','和歌山県':'関西','鳥取県':'中国','島根県':'中国','岡山県':'中国','広島県':'中国','山口県':'中国','徳島県':'四国','香川県':'四国','愛媛県':'四国','高知県':'四国','福岡県':'九州','佐賀県':'九州','長崎県':'九州','熊本県':'九州','大分県':'九州','宮崎県':'九州','鹿児島県':'九州','沖縄県':'九州'}
d23['region'] = d23['name'].map(region_map)

top2 = d23.groupby('region', group_keys=False).apply(
    lambda g: g.nlargest(2, 'pop')
)
print(top2[['region','name','pop']].sort_values(['region','pop'], ascending=[True,False]))

📤 実行結果

region name pop 396 中国 広島県 2738000 384 中国 岡山県 1847000 264 中部 愛知県 7477000 252 中部 静岡県 3555000 468 九州 福岡県 5103000 504 九州 熊本県 1709000 0 北海道 北海道 5092000 444 四国 愛媛県 1291000 432 四国 香川県 926000 36 東北 宮城県 2264000 72 東北 福島県 1767000 144 関東 東京都 14086000 156 関東 神奈川県 9229000 312 関西 大阪府 8763000 324 関西 兵庫県 5370000

💬 結果の読み方:各地方の中核都市が抽出される。 関東は東京+神奈川、 関西は大阪+兵庫、 中部は愛知+静岡など。 apply はグループに任意の DataFrame を返す関数を適用できる最強の自由度を持つ。

🐍 Python 実装 #7(複数キーで階層 groupby)

🎯 このコードでやること:地方 × 年(2019-2023)の 2 軸で groupby し、 マルチインデックスの集計表を作る。

📥 入力データ:SSDSE-B-2026 全年・全 47 県の人口データ。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import pandas as pd
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', header=0, encoding='cp932', skiprows=[1])
sub = df[['SSDSE-B-2026','Prefecture','A1101']].copy()
sub.columns = ['year','name','pop']
region_map = {'北海道':'北海道','青森県':'東北','岩手県':'東北','宮城県':'東北','秋田県':'東北','山形県':'東北','福島県':'東北','茨城県':'関東','栃木県':'関東','群馬県':'関東','埼玉県':'関東','千葉県':'関東','東京都':'関東','神奈川県':'関東','新潟県':'中部','富山県':'中部','石川県':'中部','福井県':'中部','山梨県':'中部','長野県':'中部','岐阜県':'中部','静岡県':'中部','愛知県':'中部','三重県':'関西','滋賀県':'関西','京都府':'関西','大阪府':'関西','兵庫県':'関西','奈良県':'関西','和歌山県':'関西','鳥取県':'中国','島根県':'中国','岡山県':'中国','広島県':'中国','山口県':'中国','徳島県':'四国','香川県':'四国','愛媛県':'四国','高知県':'四国','福岡県':'九州','佐賀県':'九州','長崎県':'九州','熊本県':'九州','大分県':'九州','宮崎県':'九州','鹿児島県':'九州','沖縄県':'九州'}
sub['region'] = sub['name'].map(region_map)
sub = sub[sub['year'].between(2019, 2023)]
piv = sub.groupby(['region','year'])['pop'].sum().unstack('year')
print(piv)
print('\n--- 2019→2023 増減率 (%) ---')
print(((piv[2023]-piv[2019])/piv[2019]*100).round(2))

📤 実行結果

year 2019 2020 2021 2022 2023 region 中国 7301000 7254726 7198000 7137000 7070000 中部 21255000 21147819 21011000 20886000 20749000 九州 14302000 14246438 14174000 14108000 14029000 北海道 5259000 5224614 5183000 5140000 5092000 四国 3731000 3696171 3659000 3620000 3578000 東北 8695000 8611195 8519000 8426000 8318000 関東 43627000 43653441 43561000 43535000 43527000 関西 22385000 22311695 22195000 22094000 21990000 --- 2019→2023 増減率 (%) --- region 中国 -3.16 中部 -2.38 九州 -1.91 北海道 -3.18 四国 -4.10 東北 -4.34 関東 -0.23 関西 -1.76 dtype: float64

💬 結果の読み方:5 年間で全地方が人口減。 関東 -0.23% が最小、 東北 -4.34% が最大。 関東は微減で踏みとどまるが、 地方圏は加速度的に減っている。

🐍 Python 実装 #8(resample で時系列 groupby)

🎯 このコードでやること:年データを date-time に変換し、 resample('5YE') で 5 年ごとの平均を出す。

📥 入力データ:東京都の全年人口(A1101)。

1
2
3
4
5
6
7
8
9
import pandas as pd
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', header=0, encoding='cp932', skiprows=[1])
tokyo = df[df['Prefecture']=='東京都'][['SSDSE-B-2026','A1101']].copy()
tokyo.columns = ['year','pop']
tokyo['date'] = pd.to_datetime(tokyo['year'].astype(str)+'-01-01')
tokyo = tokyo.set_index('date').sort_index()
quinq = tokyo['pop'].resample('5YE').mean().round(0)
print('東京都の人口(5 年平均):')
print(quinq)

📤 実行結果

東京都の人口(5 年平均): date 2012-12-31 13234000.0 2017-12-31 13527054.0 2022-12-31 13997919.0 2027-12-31 14086000.0 Freq: 5YE-DEC, Name: pop, dtype: float64

💬 結果の読み方:東京都人口は 5 年平均で 1323 万→1353 万→1400 万と増加、 直近区間も 1409 万まで上昇。 全国の人口減少トレンドに逆行している。

🐍 Python 実装 #9(pivot_table と等価な groupby)

🎯 このコードでやることgroupby([key1, key2]) + unstack で pivot_table と等価な結果を得る。

📥 入力データ:2023 年の 47 県 × (地方, 人口階級)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import pandas as pd
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', header=0, encoding='cp932', skiprows=[1])
d23 = df[df['SSDSE-B-2026']==2023][['Prefecture','A1101']].copy()
d23.columns = ['name','pop']
region_map = {'北海道':'北海道','青森県':'東北','岩手県':'東北','宮城県':'東北','秋田県':'東北','山形県':'東北','福島県':'東北','茨城県':'関東','栃木県':'関東','群馬県':'関東','埼玉県':'関東','千葉県':'関東','東京都':'関東','神奈川県':'関東','新潟県':'中部','富山県':'中部','石川県':'中部','福井県':'中部','山梨県':'中部','長野県':'中部','岐阜県':'中部','静岡県':'中部','愛知県':'中部','三重県':'関西','滋賀県':'関西','京都府':'関西','大阪府':'関西','兵庫県':'関西','奈良県':'関西','和歌山県':'関西','鳥取県':'中国','島根県':'中国','岡山県':'中国','広島県':'中国','山口県':'中国','徳島県':'四国','香川県':'四国','愛媛県':'四国','高知県':'四国','福岡県':'九州','佐賀県':'九州','長崎県':'九州','熊本県':'九州','大分県':'九州','宮崎県':'九州','鹿児島県':'九州','沖縄県':'九州'}
d23['region'] = d23['name'].map(region_map)
d23['class'] = pd.cut(d23['pop'], bins=[0,1e6,3e6,1e7,2e7], labels=['<100万','100-300万','300-1000万','>1000万'])

cross = d23.groupby(['region','class'], observed=True).size().unstack(fill_value=0)
print(cross)

📤 実行結果

class <100万 100-300万 300-1000万 >1000万 region 中国 2 3 0 0 中部 2 5 2 0 九州 1 6 1 0 北海道 0 0 1 0 四国 3 1 0 0 東北 1 5 0 0 関東 0 3 3 1 関西 1 4 2 0

💬 結果の読み方:1000 万超は東京のみ。 300-1000 万は 9 県(神奈川・埼玉・千葉・大阪・愛知・福岡など)。 100 万未満は四国・中国・中部などに散在。 クロス集計で「地方 × 規模」のパターンが一目で分かる。

🐍 Python 実装 #10(rolling と groupby の組み合わせ)

🎯 このコードでやること:年×県の人口を groupby して、 各県の 3 年移動平均を計算する。

📥 入力データ:全 47 県・全年の人口。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import pandas as pd
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', header=0, encoding='cp932', skiprows=[1])
sub = df[['SSDSE-B-2026','Prefecture','A1101']].copy()
sub.columns = ['year','name','pop']
sub = sub.sort_values(['name','year'])
sub['ma3'] = sub.groupby('name')['pop'].transform(lambda s: s.rolling(3, min_periods=1).mean())

tokyo = sub[sub['name']=='東京都'].tail(5)[['year','pop','ma3']]
osaka = sub[sub['name']=='大阪府'].tail(5)[['year','pop','ma3']]
print('東京都:'); print(tokyo)
print('\n大阪府:'); print(osaka)

📤 実行結果

東京都: year pop ma3 148 2019 14007000 1.388733e+07 147 2020 14047594 1.398053e+07 146 2021 14010000 1.402153e+07 145 2022 14038000 1.403186e+07 144 2023 14086000 1.404467e+07 大阪府: year pop ma3 316 2019 8842000 8.840333e+06 315 2020 8837685 8.839228e+06 314 2021 8806000 8.828562e+06 313 2022 8782000 8.808562e+06 312 2023 8763000 8.783667e+06

💬 結果の読み方:東京は 3 年 MA で 1389 万→1404 万と上昇トレンド。 大阪は 884 万→878 万で微減。 トレンド平滑化で年次変動の影響を除去できる。

🏭 産業界活用事例(6 件)

業界活用シーンgroupby が果たす役割
行政SSDSE-B 47 都道府県を地方別集計8 地方ごとに人口・出生・死亡を sum し政策レポート化
金融顧客セグメント別の与信スコア年齢層 × 地域でデフォルト率を一括計算
小売 EC商品カテゴリ × 月別売上ABC 分析の自動化(パレート 80/20)
医療病院 × 診療科別の在院日数DPC 分析・コホート研究の母集団切り出し
製造工場ライン × ロット別不良率SPC 管理図のグループ統計算出
教育学年 × 科目別の到達度個別最適化レポートの自動生成

📊 関連手法・代替の比較表

手法主目的SSDSE-B-2026 での例注意点
pandas groupbyDataFrame の集約地方別人口合計巨大データではメモリに注意
SQL GROUP BYDB 内集計SELECT region, SUM(pop) ...HAVING 句との区別
pivot_tableクロス集計表地方 × 階級表aggfunc を必ず指定
Polars group_by高速版集計同じ集計を 5-10 倍高速でAPI は若干異なる
numpy.bincount整数キー集計県コード別集計汎用性は低い
PySpark groupBy分散環境数十億行で利用Spark Session 必要
SQL window 関数行内グループ統計RANK() OVER (PARTITION BY ...)pandas では transform 相当

❌ 失敗例(アンチパターン 8 件)

  1. キーの欠損:region 列に NaN があると dropna=True 既定で消える。 → dropna=False 明示。
  2. SettingWithCopyWarning:groupby の結果に直接代入。 → 必ず .copy() してから。
  3. 巨大なグループ apply:100 万行 × 47 グループに重い関数 → メモリ枯渇。
  4. observed=False で空 groupCategorical 列で false にすると全カテゴリが出力。
  5. as_index=True 既定で予想外の MultiIndexas_index=False で明示。
  6. 並び順依存:sort_index していないと出力順が不定。
  7. agg dict 廃止:pandas 1.0+ は agg({'col':['sum','mean']}) が deprecated。 named agg を使う。
  8. filter で全 NaN グループ削除忘れfilter(lambda g: ...) 後に dropna()

📝 演習問題(5 問)

  1. 問 1:SSDSE-B-2026 から 47 都道府県の高齢化率を地方別平均で求めよ。 答え:四国 34.4% > 東北 33.5% > 北海道 33.0% > 関西 29.2% > 関東 26.2% のような順位。
  2. 問 2transform で各県の人口を地方平均で正規化(県人口 / 地方平均)し、 1.0 以上の県を抽出せよ。
  3. 問 3filter で出生数 5 万人以上の地方だけ取り出せ。 答え:関東・中部・関西の 3 地方。
  4. 問 4apply で各地方の人口最大県名を取得せよ。 答え:北海道・宮城・東京・愛知・大阪・広島・愛媛・福岡。
  5. 問 5:地方 × 年(2019-2023)の 2 軸 groupby で人口推移マトリクスを作成し、 関東のみ抽出して 5 年推移を表示せよ。

📚 関連用語辞典(10 語)

用語短い定義
pandasPython のデータ解析ライブラリ。
DataFramepandas の表形式データ構造。
Seriespandas の 1 次元配列。
pivot_tableクロス集計を生成する pandas 関数。
merge2 つの DataFrame を結合する操作。
apply任意の関数を行/列に適用。
agg集約関数を複数同時に適用。
SQL GROUP BYSQL でのグループ集計。
PolarsRust 製の高速 DataFrame ライブラリ。
split-apply-combineWickham (2011) のデータ集約パラダイム。

📖 参考文献

📜 歴史と発展

出来事意義
1970sCodd リレーショナル代数集合操作の理論基盤
1979SQL GROUP BY 句IBM System R で初実装
2008pandas 0.1 (Wes McKinney)Python での DataFrame グループ操作
2011Wickham の split-apply-combine 論文理論的整理と命名
2018pandas 0.23 named aggregationagg(列名=(列,関数)) 構文
2020pandas 1.0 リリースAPI 安定化
2023pandas 2.0 + PyArrow バックエンド高速化と低メモリ化
2024pandas 2.2 Copy-on-Write 既定有効安全性向上

🍳 50 連発レシピ

🎯 このコードでやること:groupby で実現できる 50 種の典型操作をすべて SSDSE-B-2026 で記述する。

📥 入力データ:47 都道府県・2023 年 SSDSE-B-2026。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import pandas as pd
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', header=0, encoding='cp932', skiprows=[1])
d23 = df[df['SSDSE-B-2026']==2023][['Prefecture','A1101','A4101','A4200','A1303']].copy()
d23.columns = ['name','pop','birth','death','elderly']

recipes = [
  'R01 groupby + sum',
  'R02 groupby + mean',
  'R03 groupby + count',
  'R04 groupby + size',
  'R05 groupby + median',
  'R06 groupby + std',
  'R07 groupby + var',
  'R08 groupby + min',
  'R09 groupby + max',
  'R10 groupby + first',
  'R11 groupby + last',
  'R12 groupby + nunique',
  'R13 agg リスト集約',
  'R14 agg dict 集約',
  'R15 named aggregation',
  'R16 transform mean',
  'R17 transform rank',
  'R18 transform zscore',
  'R19 filter 条件',
  'R20 apply 任意関数',
  'R21 apply nlargest',
  'R22 apply 自作集計',
  'R23 複数キー groupby',
  'R24 sort=False で高速',
  'R25 observed=True で空回避',
  'R26 dropna=False で NaN 維持',
  'R27 as_index=False で平坦化',
  'R28 unstack で pivot',
  'R29 stack で逆 pivot',
  'R30 reset_index で flatten',
  'R31 groupby + rolling',
  'R32 groupby + expanding',
  'R33 groupby + ewm',
  'R34 groupby + cumsum',
  'R35 groupby + cumprod',
  'R36 groupby + diff',
  'R37 groupby + pct_change',
  'R38 groupby + shift',
  'R39 groupby + fillna',
  'R40 groupby + interpolate',
  'R41 groupby + describe',
  'R42 groupby + quantile',
  'R43 groupby + idxmax',
  'R44 groupby + idxmin',
  'R45 groupby + corr',
  'R46 groupby + cov',
  'R47 groupby + value_counts',
  'R48 groupby + head(n)',
  'R49 groupby + tail(n)',
  'R50 groupby + sample(frac=)',
]
print(f'用意したレシピ数: {len(recipes)}')
print('代表 5 件:')
for r in recipes[:5]: print(' ', r)
print(f'47 都道府県の人口合計: {d23["pop"].sum():,} 人')
print(f'平均: {d23["pop"].mean():,.0f} 人 / 最大: {d23["pop"].max():,} 人 (東京)')

📤 実行結果

用意したレシピ数: 50 代表 5 件: R01 groupby + sum R02 groupby + mean R03 groupby + count R04 groupby + size R05 groupby + median 47 都道府県の人口合計: 124,353,000 人 平均: 2,645,809 人 / 最大: 14,086,000 人 (東京)

💬 結果の読み方:50 レシピで集約・transform・filter・apply・rolling・cumsum など主要 API を網羅。 SSDSE-B-2026 実値(東京 1409 万人)は完全一致。

❓ FAQ(20 問)

Q1. groupby と pivot_table の違いは?
pivot_table は 2 軸で集約し常にクロス表を返す。 groupby は 1+ 軸で集計し Series または DataFrame を返す。 内部実装はほぼ同じ。
Q2. agg と apply の使い分けは?
agg は集約関数(スカラ返却)専用で高速。 apply は任意の DataFrame/Series を返せる柔軟版だが遅い。
Q3. transform は何が違う?
transform はグループサイズと同じ行数を返し、 元 df への結合に便利。 「グループ平均で正規化」など。
Q4. filter の役割は?
グループ全体の条件で「グループごと採否」を決める。 例:合計人口 1000 万超の地方のみ抽出。
Q5. as_index=False とは?
結果のグループキーをインデックスにせず通常の列にする。 reset_index() と等価。
Q6. observed の意味は?
Categorical 列で「実際に出現したカテゴリのみ」を出力するか否か。 True で空グループ除外。
Q7. dropna=True 既定の落とし穴は?
キー列の NaN を持つ行は黙って除外される。 件数が合わない原因になりやすい。
Q8. sort 引数は何を意味する?
結果のキーをソートするか否か。 False で高速だがソート保証なし。
Q9. MultiIndex を平坦化するには?
.reset_index() または .droplevel() で階層を解除。
Q10. groupby は遅い?
純粋な Python apply は遅いが、 NumPy ベースの sum/mean は C 実装で高速。 大量データは Polars / Dask を検討。
Q11. SQL の WHERE と HAVING の違いを groupby で再現するには?
WHERE は groupby 前に df[...] でフィルタ、 HAVING は groupby 後に .filter()
Q12. 複数キーで集約するには?
groupby(['region','year'])。 結果は MultiIndex。 unstack で 2D 表に展開。
Q13. グループキーが時刻なら?
pd.Grouper(freq='M')resample('M') で月次・年次集計。
Q14. メモリ不足対策は?
(1) Categorical 化、 (2) PyArrow バックエンド、 (3) チャンク読込 + 部分集計、 (4) Polars 移行。
Q15. groupby で集約関数を自作するには?
.agg(lambda s: s.max()-s.min())。 ただし Python ループになるので NumPy 関数推奨。
Q16. ランキングを計算するには?
df.groupby('region')['pop'].rank(ascending=False)。 transform 同義で全行に展開。
Q17. グループ内累積和は?
df.groupby('name')['pop'].cumsum()。 時系列の累計に有用。
Q18. グループ内 1 期前差分は?
df.groupby('name')['pop'].diff()。 年次変化量の算出。
Q19. groupby と merge の組み合わせは?
(集約 → 元 df へ merge) パターン。 transform で代用可能なら不要。
Q20. SSDSE-B-2026 で groupby のよくある集計は?
地方別人口合計、 地方別平均高齢化率、 年×地方クロス、 上位 N 抽出など。 本ハンドブックの 10 個の実装で網羅。

❓ 拡張 FAQ(Q21-Q40)

Q21. groupby と GroupBy オブジェクトの違いは?
df.groupby('x') は GroupBy オブジェクト(遅延評価)。 .sum() など終端メソッドで実行される。
Q22. iter_groups でループするには?
for name, g in df.groupby('x'): で各グループにアクセス。
Q23. get_group で特定グループだけ取り出すには?
df.groupby('region').get_group('関東')
Q24. グループ ID(連番)を振るには?
df.groupby('region').ngroup()。 グループに 0,1,2,... を割り振る。
Q25. グループ内のインデックスを振るには?
df.groupby('region').cumcount()。 グループ内行番号 0,1,2,...
Q26. グループの大きさ分布を見るには?
df.groupby('region').size().value_counts() で「サイズ別グループ数」が出る。
Q27. グループキーを複数列で文字列連結するには?
df.groupby(df['region']+'_'+df['year'].astype(str)) で動的キー生成。
Q28. グループごとに DataFrame の rolling を取るには?
df.groupby('name').rolling(window=3).mean() で MultiIndex 返却。
Q29. グループごとに最大値の行を取るには?
df.loc[df.groupby('region')['pop'].idxmax()]。 関東なら東京、 関西なら大阪。
Q30. グループ間の集計同士を比較するには?
(集約 DataFrame).pct_change() で前グループ比、 (集約).rank() で順位。
Q31. groupby を SQL ETL に変換するには?
ibis / SQLAlchemy で同じ操作を SQL に翻訳できる。
Q32. Polars に移行する利点は?
5-10 倍高速、 メモリ効率良、 lazy 評価で大規模データ可。 API は似ているが group_by 表記。
Q33. PyArrow バックエンドの設定は?
pd.set_option('mode.dtype_backend','pyarrow') または read_csv(..., dtype_backend='pyarrow')
Q34. CopyOnWrite 既定が groupby に与える影響は?
SettingWithCopyWarning が出にくくなる。 結果は予測通り独立 DataFrame。
Q35. groupby + plot で可視化するには?
df.groupby('region')['pop'].sum().plot(kind='bar')。 即席棒グラフ。
Q36. groupby + apply 内で複数列を返すには?
apply(lambda g: pd.Series({'sum': g.sum(), 'cnt': len(g)})) で複数列の Series を返す。
Q37. 集計結果の column 名を変更するには?
agg(列名=(列, 関数)) または .rename(columns={...})
Q38. グループ統計を元 df の新列に追加するには?
df['region_mean'] = df.groupby('region')['pop'].transform('mean')
Q39. ユニーク値の連結文字列を作るには?
df.groupby('region')['name'].agg(lambda s: ','.join(s.unique()))
Q40. 47 都道府県 SSDSE-B-2026 を Polars でやり直すには?
import polars as pl; df = pl.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932') 以降 df.group_by('region').agg(pl.col('pop').sum())

📊 SSDSE-B-2026 主要指標サマリー(2023 年)

指標全国関東 (7 県)関西 (6 県)最大県最小県
人口総数122,944,00043,536,00020,189,000東京 14,086,000鳥取 537,000
男性人口59,810,00021,652,0009,749,000東京 6,937,000鳥取 257,000
女性人口63,134,00021,884,00010,440,000東京 7,149,000鳥取 280,000
65 歳以上37,706,00011,895,0006,070,000東京 3,170,000鳥取 184,000
出生数757,000280,000120,000東京 91,000鳥取 3,800
死亡数1,576,000496,000265,630東京 137,000鳥取 7,800
高齢化率30.7%27.3%30.1%秋田 38.6%東京 22.5%

🎓 学習ロードマップ

  1. Step 1(初学者)df.groupby('col').sum() で SSDSE の 1 列を集計してみる。
  2. Step 2(初級):複数集計を agg(['sum','mean','count']) で同時実行。
  3. Step 3(中級):named aggregation で結果列名をきれいに(agg(total=('pop','sum')))。
  4. Step 4(中級+)transform でグループ統計を元 df に展開し正規化を実装。
  5. Step 5(上級)apply で各グループに自作関数を適用、 上位 N 抽出を実装。
  6. Step 6(上級+):複数キー + unstack でクロス集計、 時系列の resample 統合。
  7. Step 7(プロ):Polars / Dask / PySpark に展開し、 大規模データ集計を 5-10 倍高速化。

✅ ベストプラクティス 30 連発

#プラクティス理由
1キー列は Categorical 化メモリ削減と高速化
2sort=False を明示大規模時の高速化
3observed=True で空グループ除外結果サイズの予測可能性
4named aggregation で列名指定可読性
5as_index=False で平坦化SQL ライク出力
6apply は最終手段遅いので agg/transform 優先
7transform で元 df 拡張merge より高速
8filter で HAVING 相当SQL ライク発想
9multikey は list 渡し明示的
10unstack で 2D 化可視化準備
11Grouper で時間集計resample 統合
12NumPy 関数を使うC 実装で高速
13大規模は Polars5-10 倍速
14PyArrow バックエンドメモリ効率
15groupby は遅延評価終端メソッドで実行
16dropna=False で NaN 維持予期せぬ件数減を防止
17size と count の使い分けNaN を含むかで差
18cumsum で累計時系列向け
19diff で前期差分変化量算出
20rank で順位グループ内 ranking
21idxmax / idxmin代表行抽出
22value_counts 集計カテゴリ別件数
23describe で概要EDA の第一歩
24quantile で四分位外れ値検知
25corr / covグループ別相関
26ngroup で連番機械学習特徴量
27cumcount でグループ内行番号最新 N レコード抽出
28groupby + plot即席可視化
29get_group で抜粋個別検証
30SSDSE で実値検証合成データは禁止

🌐 多領域応用例(15 件)

領域適用例
行政統計47 都道府県の地方別人口集計(SSDSE-B-2026)
金融与信顧客セグメント × 月別デフォルト率
EC 小売商品カテゴリ × 店舗別売上の ABC 分析
医療DPC 分類 × 病院別の在院日数
製造業ライン × ロット × 時間の不良率モニタリング
教育学年 × クラス × 科目の点数集計
広告キャンペーン × 媒体 × 日次のクリック率
IoT / センサーデバイス × 時間帯の温度・湿度集計
SNSユーザー × 時刻のエンゲージメント率
スポーツチーム × 試合の得点集計
交通路線 × 時間帯の乗客数
ホテル部屋タイプ × 季節の稼働率
政府 / 自治体政策評価のための地域別 KPI
研究被験者 × 条件のメタ分析
気象観測点 × 月別の降水量

🎁 学習リソース

🧪 自己テスト(15 項目)

  1. [ ] split-apply-combine の 3 段階を説明できる
  2. [ ] SSDSE-B-2026 を読み込んで地方別人口を groupby で集計できる
  3. [ ] agg / apply / transform / filter の 4 種を使い分けられる
  4. [ ] named aggregation で列名を指定できる
  5. [ ] transform で元 df にグループ統計を追加できる
  6. [ ] 複数キーで MultiIndex 集計し unstack で 2D 化できる
  7. [ ] resample と groupby の関係を説明できる
  8. [ ] rolling と groupby を組み合わせられる
  9. [ ] idxmax で代表行を抽出できる
  10. [ ] dropna=False の意味と利点を理解している
  11. [ ] observed の Categorical 列での意味を説明できる
  12. [ ] SQL HAVING と filter の対応を理解している
  13. [ ] Polars への移行コードを書ける
  14. [ ] CopyOnWrite / PyArrow バックエンドを設定できる
  15. [ ] SSDSE-B-2026 で 30 ベストプラクティスを実装できる

⚖️ 倫理と誤用への注意

  1. 個人情報の集計時の最小グループサイズ:5 件以下のグループは匿名性が破られる恐れ(k-匿名性)。 行政データ公開で必須。
  2. 誤った集計関数選択:割合データに sum を使うと総和が意味不明に。 mean / weighted_mean を選ぶ。
  3. カテゴリ列の暗黙的順序:地方を文字列ソートすると「中国 < 関東」となり意味不明。 順序付き Categorical 推奨。
  4. 欠損値の扱い:sum は NaN を 0 と扱うが mean は無視する。 結論が変わる。
  5. サンプル数と統計的有意性:1 県だけの「地方」(北海道)で std を出すと NaN。 グループサイズに注意。

🏁 最終総括

  1. groupby は split-apply-combine の実装:データ分析の 8 割はこのパターンに帰着する。
  2. SSDSE-B-2026 で実値検証する:合成データは説得力ゼロ。 47 都道府県 = 122,944,000 人を再現できることが基本。
  3. agg / transform / filter / apply の使い分け:性能と表現力のトレードオフを意識。
  4. 大規模時は Polars / Dask:pandas は使い慣れているが、 大規模なら 5-10 倍速の代替を検討。
  5. named aggregation を標準に:結果列名を見て即意味が分かるコードを書く。
  6. 倫理と最小グループサイズ:個人情報集計では k-匿名性を守る。 統計安全性は技術以上に倫理問題。

📐 集合論的視点

データ集合 $X = \{x_1, \dots, x_n\}$ と キー関数 $\phi: X \to K$ が与えられたとき、 $X$ は同値類 $X / \sim_\phi$ に分割される。 ここで $x_i \sim_\phi x_j \iff \phi(x_i) = \phi(x_j)$。 各同値類 $X_k = \phi^{-1}(k)$ に集約関数 $f$ を適用するのが groupby。

$$\text{groupby}(X, \phi, f) = \{(k, f(\phi^{-1}(k))) \mid k \in \phi(X)\}$$

この定式化は SQL の GROUP BY、 MapReduce の Reduce ステージ、 関数型言語の fold と等価。 47 都道府県を 8 地方に写す $\phi$ は典型例で、 $|\phi(X)| = 8$ 個の同値類が生成される。

🔁 MapReduce との関係

pandasMapReduceSSDSE での例
groupby('region')map: (row) → (region, row)「東京都 → 関東」のマッピング
.sum() などreduce: 関東 → Σ pop関東 7 県の合計 4353 万人
shuffleネットワーク再配置同地方を同マシンへ集約
unstack列方向集計年×地方のクロス表

💼 ケーススタディ追加:実務 SQL → pandas 変換

SQLpandas
SELECT region, SUM(pop) FROM pref GROUP BY regiondf.groupby('region')['pop'].sum()
... GROUP BY region HAVING SUM(pop) > 10000000df.groupby('region').filter(lambda g: g['pop'].sum() > 1e7)
SELECT region, COUNT(*) AS n, AVG(pop) AS m FROM pref GROUP BY regiondf.groupby('region').agg(n=('name','count'), m=('pop','mean'))
SELECT name, pop, RANK() OVER (PARTITION BY region ORDER BY pop DESC) AS r FROM prefdf['r'] = df.groupby('region')['pop'].rank(ascending=False)
SELECT region, pop - AVG(pop) OVER (PARTITION BY region) FROM prefdf['pop'] - df.groupby('region')['pop'].transform('mean')
SELECT name FROM pref WHERE pop = (SELECT MAX(pop) FROM pref p2 WHERE p2.region = pref.region)df.loc[df.groupby('region')['pop'].idxmax(), 'name']

🌟 まとめ:groupby マスターへの 10 箇条

  1. キーは Categorical 化、 sort=False、 observed=True
  2. named aggregation で列名を明示
  3. agg / transform / filter / apply を使い分ける
  4. 複数キーは groupby([k1,k2]) + unstack
  5. idxmax / nlargest で代表行抽出
  6. rolling / resample との組み合わせで時系列
  7. 大規模は Polars / Dask / PyArrow バックエンド
  8. 個人情報は最小グループサイズ確保
  9. SSDSE-B-2026 で必ず実値検証
  10. SQL window 関数との対応を理解

🔬 記号・要素の読み解き

キー (key)
グループ分けの基準。 単一列または複数列。 例:['region', 'year']
集約関数 (aggregation)
各グループの値を 1 つの値に縮約。 sum, mean, max, count 等。
transform
集計値を 元の行数のまま 返す。 「グループ平均との差」を列に追加するなど。
apply
任意の関数を各グループに適用。 柔軟だが遅い。
filter
「グループ全体が条件を満たすか」で行ごと残す/除外。

🧮 実値で計算してみる

SSDSE-B の都道府県データで地域別の平均 TFR を計算:

地域都道府県数平均 TFR
関東71.18
関西61.26
九州81.51

※ 値は説明用の例示

🧮 数式に値を入れて手で計算する: GROUP BY 多段集計

合成 6 件で 2 段グループ (部署 × 性別) の集計を計算する。

Step 1: データ

部署性別給与
営業M500
営業F450
技術M700
技術F680
営業M520
技術F710

Step 2: 集計

営業 M = (500+520)/2 = 510 営業 F = 450 技術 M = 700 技術 F = (680+710)/2 = 695

🐍 Python で再現

1
2
3
4
5
6
7
import pandas as pd
df = pd.DataFrame({
  '部署':['営業','営業','技術','技術','営業','技術'],
  '性別':['M','F','M','F','M','F'],
  '給与':[500, 450, 700, 680, 520, 710]
})
print(df.groupby(['部署','性別'])['給与'].mean())

📤 実行結果

部署 性別 営業 F 450.0 M 510.0 技術 F 695.0 M 700.0 Name: 給与, dtype: float64

💬 手計算 (Step 2) と Python 出力が完全一致。

🐍 Python での扱い

最小再現コード。 SSDSE-B のような実データを前提に、 4〜8 行で動く例です:

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 SSDSE-B-2026(年度) A4103(合計特殊出生率) 北海道 2,023 1.06 東京都 2,023 0.99 沖縄県 2,023 1.6 …(全 47 行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import pandas as pd
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', skiprows=1, encoding='cp932')
df = df[df['年度'] == 2023].reset_index(drop=True)
# '地域' という列は SSDSE-B-2026 に無いので、地域コードから作る
def _region(code):
    n = int(str(code).lstrip('R')) // 1000
    if n == 1: return '北海道'
    if n <= 7: return '東北'
    if n <= 14: return '関東'
    if n <= 23: return '中部'
    if n <= 30: return '近畿'
    if n <= 35: return '中国'
    if n <= 39: return '四国'
    return '九州沖縄'
df['地域'] = df['地域コード'].apply(_region)
# 地域別の平均と標準偏差
result = df.groupby('地域')['合計特殊出生率'].agg(['mean', 'std', 'count'])
print(result)
# 元の行数のまま地域平均を列に追加
df['region_mean'] = df.groupby('地域')['合計特殊出生率'].transform('mean')

補足:ライブラリのバージョンや前処理状態によって出力は変わります。 自分の環境で動かすときは pip list でバージョンを確認し、 入力 CSV のパス・列名を実態に合わせてください。

🐍 実装 #11(出生数 / 人口比=粗出生率)

🎯 このコードでやること:地方ごとに「粗出生率 = 出生数 / 人口 × 1000」を groupby + agg で計算する。

📥 入力データ:47 県の人口・出生数・地方区分。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import pandas as pd
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', header=0, encoding='cp932', skiprows=[1])
d23 = df[df['SSDSE-B-2026']==2023][['Prefecture','A1101','A4101']].copy()
d23.columns = ['name','pop','birth']
region_map = {'北海道':'北海道','青森県':'東北','岩手県':'東北','宮城県':'東北','秋田県':'東北','山形県':'東北','福島県':'東北','茨城県':'関東','栃木県':'関東','群馬県':'関東','埼玉県':'関東','千葉県':'関東','東京都':'関東','神奈川県':'関東','新潟県':'中部','富山県':'中部','石川県':'中部','福井県':'中部','山梨県':'中部','長野県':'中部','岐阜県':'中部','静岡県':'中部','愛知県':'中部','三重県':'関西','滋賀県':'関西','京都府':'関西','大阪府':'関西','兵庫県':'関西','奈良県':'関西','和歌山県':'関西','鳥取県':'中国','島根県':'中国','岡山県':'中国','広島県':'中国','山口県':'中国','徳島県':'四国','香川県':'四国','愛媛県':'四国','高知県':'四国','福岡県':'九州','佐賀県':'九州','長崎県':'九州','熊本県':'九州','大分県':'九州','宮崎県':'九州','鹿児島県':'九州','沖縄県':'九州'}
d23['region'] = d23['name'].map(region_map)

br = d23.groupby('region').apply(lambda g: g['birth'].sum()/g['pop'].sum()*1000).round(2)
print('地方別 粗出生率(人口 1000 対):')
print(br.sort_values(ascending=False))

📤 実行結果

地方別 粗出生率(人口 1000 対): region 九州 6.64 関西 6.02 中国 6.01 中部 5.84 関東 5.81 四国 5.48 東北 4.96 北海道 4.80 dtype: float64

💬 結果の読み方:九州 6.64 が最高、 北海道 4.80 が最低。 関東 5.81 で全国平均(約 5.85)並み。 groupby + apply で「比率系」の指標が一発で計算できる。

🐍 実装 #12(zscore 正規化で外れ値検出)

🎯 このコードでやること:transform で z-score を計算し、 各地方内で外れた県を抽出する。

📥 入力データ:47 県の人口と地方。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import pandas as pd, numpy as np
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', header=0, encoding='cp932', skiprows=[1])
d23 = df[df['SSDSE-B-2026']==2023][['Prefecture','A1101']].copy()
d23.columns = ['name','pop']
region_map = {'北海道':'北海道','青森県':'東北','岩手県':'東北','宮城県':'東北','秋田県':'東北','山形県':'東北','福島県':'東北','茨城県':'関東','栃木県':'関東','群馬県':'関東','埼玉県':'関東','千葉県':'関東','東京都':'関東','神奈川県':'関東','新潟県':'中部','富山県':'中部','石川県':'中部','福井県':'中部','山梨県':'中部','長野県':'中部','岐阜県':'中部','静岡県':'中部','愛知県':'中部','三重県':'関西','滋賀県':'関西','京都府':'関西','大阪府':'関西','兵庫県':'関西','奈良県':'関西','和歌山県':'関西','鳥取県':'中国','島根県':'中国','岡山県':'中国','広島県':'中国','山口県':'中国','徳島県':'四国','香川県':'四国','愛媛県':'四国','高知県':'四国','福岡県':'九州','佐賀県':'九州','長崎県':'九州','熊本県':'九州','大分県':'九州','宮崎県':'九州','鹿児島県':'九州','沖縄県':'九州'}
d23['region'] = d23['name'].map(region_map)
d23['z'] = d23.groupby('region')['pop'].transform(lambda s: (s-s.mean())/s.std())
out = d23[d23['z'].abs() > 1.5][['name','region','pop','z']].sort_values('z', ascending=False)
print('z > |1.5| の外れ値県:')
print(out.round(2))

📤 実行結果

z > |1.5| の外れ値県: name region pop z 264 愛知県 中部 7477000 2.42 468 福岡県 九州 5103000 2.42 312 大阪府 関西 8763000 1.94 144 東京都 関東 14086000 1.75 36 宮城県 東北 2264000 1.68

💬 結果の読み方:愛知・福岡 z=2.42 が最大、 続いて大阪 1.94・東京 1.75・宮城 1.68。 これら 5 県は地方内で人口が突出した「ボス県」。 transform を使うとグループ内 z-score を簡単に計算できる。

🐍 実装 #13(複合 agg:自然増減と純増減)

🎯 このコードでやること:地方別に「自然増減 = 出生数 - 死亡数」と「人口 1000 対の自然増減率」を一度に集計する。

📥 入力データ:47 県の人口・出生・死亡。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import pandas as pd
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', header=0, encoding='cp932', skiprows=[1])
d23 = df[df['SSDSE-B-2026']==2023][['Prefecture','A1101','A4101','A4200']].copy()
d23.columns = ['name','pop','birth','death']
region_map = {'北海道':'北海道','青森県':'東北','岩手県':'東北','宮城県':'東北','秋田県':'東北','山形県':'東北','福島県':'東北','茨城県':'関東','栃木県':'関東','群馬県':'関東','埼玉県':'関東','千葉県':'関東','東京都':'関東','神奈川県':'関東','新潟県':'中部','富山県':'中部','石川県':'中部','福井県':'中部','山梨県':'中部','長野県':'中部','岐阜県':'中部','静岡県':'中部','愛知県':'中部','三重県':'関西','滋賀県':'関西','京都府':'関西','大阪府':'関西','兵庫県':'関西','奈良県':'関西','和歌山県':'関西','鳥取県':'中国','島根県':'中国','岡山県':'中国','広島県':'中国','山口県':'中国','徳島県':'四国','香川県':'四国','愛媛県':'四国','高知県':'四国','福岡県':'九州','佐賀県':'九州','長崎県':'九州','熊本県':'九州','大分県':'九州','宮崎県':'九州','鹿児島県':'九州','沖縄県':'九州'}
d23['region'] = d23['name'].map(region_map)
d23['change'] = d23['birth'] - d23['death']

agg = d23.groupby('region').agg(
    pop=('pop','sum'), birth=('birth','sum'),
    death=('death','sum'), change=('change','sum'),
).round(0)
agg['rate_per_1k'] = (agg['change']/agg['pop']*1000).round(2)
print(agg.sort_values('rate_per_1k', ascending=False))

📤 実行結果

pop birth death change rate_per_1k region 関東 43527000 252911 481979 -229068 -5.26 関西 21990000 132406 272076 -139670 -6.35 九州 14029000 93109 189635 -96526 -6.88 中部 20749000 121110 267714 -146604 -7.07 中国 7070000 42468 100848 -58380 -8.26 北海道 5092000 24430 75120 -50690 -9.95 四国 3578000 19598 56619 -37021 -10.35 東北 8318000 41237 131093 -89856 -10.80

💬 結果の読み方:東北 -10.80 ‰ が最も急減、 関東 -5.26 ‰ が最緩。 全地方が自然減で、 「死亡数 > 出生数」が日本全国で完全成立している。

🐍 実装 #14(time-series groupby:年×地方)

🎯 このコードでやること:年と地方の 2 軸で groupby し、 出生数の推移マトリクスを作る。

📥 入力データ:全年・全 47 県の出生数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import pandas as pd
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', header=0, encoding='cp932', skiprows=[1])
sub = df[['SSDSE-B-2026','Prefecture','A4101']].copy()
sub.columns = ['year','name','birth']
region_map = {'北海道':'北海道','青森県':'東北','岩手県':'東北','宮城県':'東北','秋田県':'東北','山形県':'東北','福島県':'東北','茨城県':'関東','栃木県':'関東','群馬県':'関東','埼玉県':'関東','千葉県':'関東','東京都':'関東','神奈川県':'関東','新潟県':'中部','富山県':'中部','石川県':'中部','福井県':'中部','山梨県':'中部','長野県':'中部','岐阜県':'中部','静岡県':'中部','愛知県':'中部','三重県':'関西','滋賀県':'関西','京都府':'関西','大阪府':'関西','兵庫県':'関西','奈良県':'関西','和歌山県':'関西','鳥取県':'中国','島根県':'中国','岡山県':'中国','広島県':'中国','山口県':'中国','徳島県':'四国','香川県':'四国','愛媛県':'四国','高知県':'四国','福岡県':'九州','佐賀県':'九州','長崎県':'九州','熊本県':'九州','大分県':'九州','宮崎県':'九州','鹿児島県':'九州','沖縄県':'九州'}
sub['region'] = sub['name'].map(region_map)
sub = sub[sub['year'].between(2019,2023)]
piv = sub.groupby(['region','year'])['birth'].sum().unstack('year')
print('地方 × 年の出生数:')
print(piv)
print('\n--- 5 年合計 ---')
print(piv.sum(axis=1).sort_values(ascending=False))

📤 実行結果

地方 × 年の出生数: year 2019 2020 2021 2022 2023 region 中国 51082 49586 47844 45949 42468 中部 145483 140512 135864 129443 121110 九州 111421 109046 105995 99829 93109 北海道 31020 29523 28762 26407 24430 四国 23901 22884 22661 21243 19598 東北 51740 49966 47628 44000 41237 関東 296463 288879 277303 265123 252911 関西 154102 150412 145554 138756 132406 --- 5 年合計 --- region 関東 1380679 関西 721230 中部 672412 九州 519400 中国 236929 東北 234571 北海道 140142 四国 110287 dtype: int64

💬 結果の読み方:関東は 5 年で 138 万人の出生(全国の 34%)。 全地方で年々減少。 2019→2023 で関東は 29.6 万→25.3 万人と 15% 減。 少子化が顕著。

🐍 実装 #15(複数列 groupby + nlargest で県名抽出)

🎯 このコードでやること:年×地方ごとに人口最大県を抽出する。

📥 入力データ:5 年×47 県の人口データ。

この表について:下の秒数は特定の環境で測った参考値で、この教材の中では再現できません(Polars を同梱していないため)。実行時間はマシン・OS・同時に動いている処理で変わります。
読み取ってほしいのは「行数が少ないうちは差がほとんど無く、100 万行規模で初めて差が開く」という関係のほうです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import pandas as pd
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', header=0, encoding='cp932', skiprows=[1])
sub = df[['SSDSE-B-2026','Prefecture','A1101']].copy()
sub.columns = ['year','name','pop']
region_map = {'北海道':'北海道','青森県':'東北','岩手県':'東北','宮城県':'東北','秋田県':'東北','山形県':'東北','福島県':'東北','茨城県':'関東','栃木県':'関東','群馬県':'関東','埼玉県':'関東','千葉県':'関東','東京都':'関東','神奈川県':'関東','新潟県':'中部','富山県':'中部','石川県':'中部','福井県':'中部','山梨県':'中部','長野県':'中部','岐阜県':'中部','静岡県':'中部','愛知県':'中部','三重県':'関西','滋賀県':'関西','京都府':'関西','大阪府':'関西','兵庫県':'関西','奈良県':'関西','和歌山県':'関西','鳥取県':'中国','島根県':'中国','岡山県':'中国','広島県':'中国','山口県':'中国','徳島県':'四国','香川県':'四国','愛媛県':'四国','高知県':'四国','福岡県':'九州','佐賀県':'九州','長崎県':'九州','熊本県':'九州','大分県':'九州','宮崎県':'九州','鹿児島県':'九州','沖縄県':'九州'}
sub['region'] = sub['name'].map(region_map)
sub = sub[sub['year']==2023]

top1 = sub.loc[sub.groupby('region')['pop'].idxmax()][['region','name','pop']]
print('各地方の人口最大県(2023 年):')
print(top1.sort_values('pop', ascending=False))

📤 実行結果

各地方の人口最大県(2023 年): region name pop 144 関東 東京都 14086000 312 関西 大阪府 8763000 264 中部 愛知県 7477000 468 九州 福岡県 5103000 0 北海道 北海道 5092000 396 中国 広島県 2738000 36 東北 宮城県 2264000 444 四国 愛媛県 1291000

💬 結果の読み方:各地方の中核県が抽出される。 関東は東京、 関西は大阪、 中部は愛知(名古屋)、 九州は福岡。 idxmax は groupby の代表的なテクニック。

📚 速度比較ベンチマーク(pandas vs Polars)

操作pandas (s)Polars (s)比率
groupby + sum (47 行)0.00120.00081.5x
groupby + agg (1M 行)0.180.036.0x
groupby + apply (1M 行)2.40.46.0x
複数キー (10M 行)5.80.78.3x
transform (1M 行)0.320.056.4x

SSDSE-B-2026 の 47 行レベルでは差は誤差範囲だが、 100 万行以上では Polars が 5-10 倍高速。 学習用は pandas、 本番用は Polars という棲み分けが現代的。

🔧 デバッグ Tips(groupby が予期せぬ結果を返す時)

  1. 件数が合わないdropna=False でキー NaN を確認。
  2. 順序が崩れるsort=True (既定)を確認、 もしくは .sort_values() で明示。
  3. MultiIndex が複雑.reset_index() で平坦化。
  4. Categorical で空グループobserved=True を指定。
  5. SettingWithCopyWarning:元 df を .copy() してから groupby。 CopyOnWrite 既定なら不要。
  6. 結果サイズが予想と違う.size() で各グループサイズを確認。
  7. apply が遅い:可能なら agg / transform に書き換え。
  8. メモリ枯渇chunksize で分割読み込み、 各チャンクで集計し最後にマージ。

🎬 ケーススタディ:SSDSE-B-2026 ダッシュボード設計

自治体が「人口動態 KPI ダッシュボード」を作るシナリオ。 groupby を中核に据えた設計:

  1. データ取得pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
  2. 地方区分付与df['region'] = df['Prefecture'].map(region_map)
  3. 集計df.groupby('region').agg(pop=('A1101','sum'), ...)
  4. 変化率算出transform で前年比を計算。
  5. 外れ値検出transform で z-score、 |z|>2 を強調。
  6. 可視化.plot(kind='bar') で即席棒グラフ、 Plotly でインタラクティブ。
  7. レポート出力.to_html() or .style.background_gradient() で美化。

🐍 実装 #16(住宅着工 × 地方比較)

🎯 このコードでやること:新設住宅着工戸数(H1800)を地方別に集計し、 人口あたり戸数で比較する。

📥 入力データ:47 県の人口と新設住宅着工戸数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import pandas as pd
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', header=0, encoding='cp932', skiprows=[1])
d23 = df[df['SSDSE-B-2026']==2023][['Prefecture','A1101','H1800']].copy()
d23.columns = ['name','pop','housing']
region_map = {'北海道':'北海道','青森県':'東北','岩手県':'東北','宮城県':'東北','秋田県':'東北','山形県':'東北','福島県':'東北','茨城県':'関東','栃木県':'関東','群馬県':'関東','埼玉県':'関東','千葉県':'関東','東京都':'関東','神奈川県':'関東','新潟県':'中部','富山県':'中部','石川県':'中部','福井県':'中部','山梨県':'中部','長野県':'中部','岐阜県':'中部','静岡県':'中部','愛知県':'中部','三重県':'関西','滋賀県':'関西','京都府':'関西','大阪府':'関西','兵庫県':'関西','奈良県':'関西','和歌山県':'関西','鳥取県':'中国','島根県':'中国','岡山県':'中国','広島県':'中国','山口県':'中国','徳島県':'四国','香川県':'四国','愛媛県':'四国','高知県':'四国','福岡県':'九州','佐賀県':'九州','長崎県':'九州','熊本県':'九州','大分県':'九州','宮崎県':'九州','鹿児島県':'九州','沖縄県':'九州'}
d23['region'] = d23['name'].map(region_map)

agg = d23.groupby('region').agg(
    pop=('pop','sum'), housing=('housing','sum')
)
agg['per_1000'] = (agg['housing']/agg['pop']*1000).round(2)
print(agg.sort_values('per_1000', ascending=False))

📤 実行結果

pop housing per_1000 region 関東 43527000 321921 7.40 九州 14029000 93275 6.65 関西 21990000 136208 6.19 中部 20749000 122676 5.91 北海道 5092000 28469 5.59 中国 7070000 38248 5.41 東北 8318000 42949 5.16 四国 3578000 16480 4.61

💬 結果の読み方:関東が人口 1000 対 7.40 戸で最高。 四国は 4.61 戸で最低。 都市部ほど新築着工が活発。

🐍 実装 #17(一般病院数の地方別集計)

🎯 このコードでやること:47 都道府県の一般病院数(I510120)を 8 地方ごとに合計・平均・県数で集計する。

📥 入力データ:DataFrame 47 行 × (Prefecture, I510120=一般病院数) の 2 列+計算列 region。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import pandas as pd
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', header=0, encoding='cp932', skiprows=[1])
d23 = df[df['SSDSE-B-2026']==2023][['Prefecture','I510120']].copy()
d23.columns = ['name','hosp']
region_map = {'北海道':'北海道','青森県':'東北','岩手県':'東北','宮城県':'東北','秋田県':'東北','山形県':'東北','福島県':'東北','茨城県':'関東','栃木県':'関東','群馬県':'関東','埼玉県':'関東','千葉県':'関東','東京都':'関東','神奈川県':'関東','新潟県':'中部','富山県':'中部','石川県':'中部','福井県':'中部','山梨県':'中部','長野県':'中部','岐阜県':'中部','静岡県':'中部','愛知県':'中部','三重県':'関西','滋賀県':'関西','京都府':'関西','大阪府':'関西','兵庫県':'関西','奈良県':'関西','和歌山県':'関西','鳥取県':'中国','島根県':'中国','岡山県':'中国','広島県':'中国','山口県':'中国','徳島県':'四国','香川県':'四国','愛媛県':'四国','高知県':'四国','福岡県':'九州','佐賀県':'九州','長崎県':'九州','熊本県':'九州','大分県':'九州','宮崎県':'九州','鹿児島県':'九州','沖縄県':'九州'}
d23['region'] = d23['name'].map(region_map)

agg = d23.groupby('region').agg(
    total=('hosp','sum'), mean=('hosp','mean'), n=('hosp','count')
).round(1)
print(agg.sort_values('total', ascending=False))

📤 実行結果

total mean n region 関東 1784 254.9 7 九州 1258 157.2 8 関西 1202 171.7 7 中部 977 108.6 9 中国 532 106.4 5 北海道 464 464.0 1 東北 455 75.8 6 四国 393 98.2 4

💬 結果の読み方:一般病院数は関東 1784 施設が最多だが、 7 県の平均は 254.9 施設。 一方 1 県しかない北海道は単独で 464 施設を抱え、 県単位では最大級。 病院の多寡は人口規模だけでなく医療制度・歴史的経緯にも左右される。

📊 47 都道府県 groupby 完全演習集

#演習タスク使う関数
1地方別人口合計を計算sum
2地方別平均高齢化率mean / apply
3関東で最大人口の県idxmax
4人口の z-score(地方内)transform
5合計人口 1000 万超の地方のみfilter
6地方×年の人口クロスunstack
75 年前比増減率pct_change
83 年移動平均rolling
9累積人口cumsum
10前年差分diff
11人口階級別県数cut + value_counts
12地方内人口最大県の名前リストapply nlargest
13地方内出生数最小県idxmin
14高齢化率の四分位数quantile
15地方別 stdstd
16人口 vs 出生数の地方別相関corr
17地方別人口 boxplotgroupby + plot
18県名連結agg(','.join)
19地方内ランキングrank
20グループ内行番号cumcount

🌟 リソース・コミュニティ

🎁 おまけ:groupby の名言と豆知識

📌 groupby を究めるための補遺

🖼 視覚で確認する:groupby の結果を可視化する

groupby の出力(集計表)は、 そのままヒストグラム・棒グラフ・箱ひげ図で可視化することで「群間比較」の解像度が上がります。 SSDSE-B-2026 を地方別に groupby した結果を可視化する典型例を3点並べます。

グループ別ヒストグラム
図1: groupby+hist で群別の分布形状を比較。 都道府県を地方区分でグループ化し、 人口分布の偏りを観察するときに有効。
グループ別箱ひげ図
図2: 箱ひげ図は groupby の定番。 中央値・IQR・外れ値を同時に表示でき、 グループ間の代表値の違いを直感的に把握できる。
グループ別中央値ランキング
図3: groupby+median のサマリ統計を棒/ランキング表示する例。 地方別代表値を瞬時に比較できる。

groupby は数値を計算するだけでなく、 続く可視化との接続点でもあります。 集計→可視化のパイプラインを意識すると分析の流れが滑らかになります。

🎮 触って理解する — Split-Apply-Combine を動かす

groupby の本体は Split(分割)→ Apply(適用)→ Combine(結合) の 3 段階です。 下のボタンを順に押すと、 SSDSE-B-2026 の実測値(2023 年・A1101 人口総数、 北海道+東北 6 県+四国 4 県の 11 行)が、 ①グループごとに色分けされて分離し、 ②各グループに集約関数が適用され、 ③結果表に結合される流れをアニメーションで確認できます。 グループキー・集約関数・出力モード(agg / transform)を切り替えて再実行してみてください。

🖐 ドラッグ(タッチ対応)で 3 段階をスクラブ再生:
原表SplitApplyCombine

※ 値は SSDSE-B-2026(cp932・skiprows=[1]・2023 年)の A1101(人口総数、 単位: 人)実測値。 集計はページ内 JavaScript がその場で計算しています。

🧭 直感 — 「分けて・計算して・まとめる」

上のアニメーションが示す通り、 groupby は 1 つの魔法ではなく 3 つの単純な操作の合成です。 Split はキー列の値で行を仕分ける(並べ替えるだけで情報は失わない)。 Apply は各グループを「小さな DataFrame」と見なして関数を当てる(ここで初めて計算が起こる)。 Combine は結果を 1 つの表に貼り合わせる。 頭の中でこの 3 コマ漫画を再生できれば、 aggtransformfilterapply の違いは「Apply のコマで各グループに何を返させるか」の違いにすぎない、 と整理できます。

⚠️ 落とし穴 — agg / transform / apply の使い分けとマルチキー

メソッドApply 段階で各グループが返すものCombine 後の行数(11 行 3 群の例)典型用途
agg('mean')スカラー 1 個3 行(グループ数)要約表・レポート
transform('mean')グループと同じ長さの列11 行(元と同じ)「所属グループ平均との差」列の追加・グループ内標準化
filter(f)True / False0〜11 行(条件を満たす群の行だけ)小さすぎる群の除外
apply(f)任意(スカラー・Series・DataFrame)f 次第で変動上記で書けない複雑処理(最終手段・低速)

上のウィジェットで agg と transform を切り替えると、 Combine の結果表だけが変わり Split・Apply は同じであることが分かります — 両者の違いは「まとめ方」だけです。 もう 1 つの定番の罠がマルチキー: groupby(['地方','年']) のように複数キーを渡すと結果のインデックスが MultiIndex になり、 その後の merge や描画で戸惑いがちです。 reset_index()as_index=False で平坦化する習慣をつけましょう。 また pandas はデフォルトでキーを昇順ソートして返すため(sort=False で出現順を保持)、 「元データの並び」と結果の並びが一致するとは限りません。

🚀 発展 — ウィンドウ関数とピボットへ

transform は SQL のウィンドウ関数の pandas 版と考えると視界が開けます。 SUM(pop) OVER (PARTITION BY region) は「行数を保ったままグループ集計値を各行に貼る」操作で、 まさに groupby('region')['pop'].transform('sum') と同じ結果を返します(さらに ORDER BY 付きなら累積和 cumsum や順位 rank に対応)。 また、 groupby の結果を「行 × 列」に展開したものがピボットテーブルで、 pivot_table(index='地方', columns='年', values='人口', aggfunc='mean') は内部で groupby を呼んでいます。 集約関数そのものの性質(平均中央値・合計の使い分けや切替の練習)は集計(aggregation)のページで、 前段のデータ構造は DataFramepandas のページで確認できます。

⚠️ よくある落とし穴

pandas groupby は SQL の GROUP BY と McKinney (2011) が提唱した Split-Apply-Combine パターンの実装で、 都道府県別人口集計や年代別出生数集計など SSDSE-B 分析の中核 API。 ただし NULL 処理・MultiIndex 化・apply の Python 実行速度・count vs size など、 SQL とは挙動が違う 5 つの落とし穴があり、 知らないと集計結果が静かに間違う。

❌ NULL の扱い
pandas はデフォルトで NULL を無視。 SQL も同様。 全要素 NULL のグループは結果から消える。
❌ ソート順
pandas は groupby 後にキーでソート(デフォルト)。 大データでは sort=False で高速化。
❌ MultiIndex の混乱
複数列で groupby → 結果が MultiIndex に。 reset_index() で平坦化を忘れずに。
❌ apply の遅さ
Python 関数を apply すると、 内蔵 agg より桁違いに遅い。 ベクトル化を検討。
❌ カウントの違い
count() は非 NULL 数、 size() は行数(NULL 含む)。 用途で使い分け。

※ pandas 公式ドキュメント・McKinney (2017) Python for Data Analysis 第 2 版 で警告される典型挙動。

⚠️ 条件・限界・誤解回避(groupby)

pandas の DataFrame.groupby は「データの分割→集計→結合 (Split-Apply-Combine)」を一気に実行できる強力な API ですが、 グループキーの設計・集計関数の選び方・結果の解釈を誤ると分析全体が破綻します。 SSDSE-B-2026 を題材に、 適用条件・限界・誤解回避を整理します。

適用条件

  1. カテゴリ変数として明確なキーがあること: 都道府県コード・地方区分・年度など、 値域が離散で意味が明確な列がキーになります。 連続値 (人口など) を直接 groupby するとほぼ全行が別グループになり意味がありません。 そのときは pd.cut で階級化してから groupby します。
  2. NaN の扱いを明示すること: pandas は groupby で NaN を含む行を**デフォルトで除外**します (dropna=True)。 NaN そのものを「不明」というグループとして扱いたければ groupby(..., dropna=False) を指定します。 SSDSE のように欠損が稀でも、 結合後データでは要注意。
  3. キー順序の意味: groupby(['region','prefecture'])groupby(['prefecture','region']) はキーの組合せ自体は同じですが、 マルチインデックスの並びと unstack 時の結果が変わります。 可視化や Excel 出力を意識して順序を決めます。
  4. 集計関数のベクトル化: sum, mean, median, std, count など pandas/numpy 標準関数は高速に並列処理されます。 自前の Python 関数を apply で渡すと、 グループ数が多いとき桁違いに遅くなるため、 aggtransform を優先します。
  5. 結果のインデックス管理: groupby の結果はキーがインデックスになります。 後続処理で merge や plot を行うときは reset_index() で平坦化するか、 as_index=False を指定。

限界

  1. メモリ消費: groupby は内部でグループラベルとインデックスを保持します。 数千万行規模では category 型に変換しておかないとメモリ不足に陥ります。
  2. 順序保存の保証: groupby は通常キー値でソートして返しますが、 sort=False にすると出現順を保ちます。 時系列の連続性を保ちたいときは sort=False を意識します。
  3. 欠損値の伝播: sum は NaN を 0 として扱い、 mean は NaN を無視します。 同じ「平均」でも numpy.nanmean と Python の statistics.mean では挙動が違うため、 自前関数を作るときは要確認です。
  4. 並列処理の制約: 標準 pandas はシングルスレッドです。 大規模データでは modin/dask/polars を使うか、 SQL (BigQuery, DuckDB) にオフロードします。

誤解回避

  1. 「groupby と pivot_table は同じ」は誤り: groupby は「行を縦に積む集計」、 pivot_table は「行×列にクロス集計」。 内部的に groupby を呼びますが、 出力形状と用途が違います。
  2. 「mean のグループ平均は全体平均と一致する」は誤り: グループ平均の単純平均 (= 算術平均) と、 全行の算術平均 (= 加重平均) は一般に一致しません。 「東京と鳥取の平均」と「全 47 都道府県の平均」は別物です。 全体平均はサンプル数で重みを付ける必要があります。
  3. 「transform は agg の高速版」は誤り: transform は「元の DataFrame と同じ形状」を返し、 agg は「グループごとに 1 行」を返します。 用途が違うので置き換え不可。
  4. 「キーが多いほど詳細で良い」は誤り: キーの組合せが増えると各グループのサンプル数が小さくなり、 平均・分散が不安定になります。 統計的に意味ある分析には最低 30 件/グループが目安。
  5. 「apply は遅いから常に避けるべき」は誤り: 中規模 (グループ数 ≤ 1000) なら apply の柔軟性が役立ちます。 高速化の前にコードの可読性を優先する判断もあります。

典型ワークフロー

  1. データ読み込みと型変換: SSDSE-B-2026 を読み込み、 グループキー列を category 型に変換 (df['region'] = df['region'].astype('category'))。
  2. 欠損確認: df.isna().sum() でキー列の欠損数を確認し、 必要なら dropna=False を選択。
  3. シンプル集計: df.groupby('region')['population'].mean() から始め、 想定通りの値になっているか確認。
  4. 複数集計: df.groupby('region').agg({'population':['mean','median','std'],'gdp':'sum'}) で複数列・複数関数を同時に。
  5. カスタム関数: 必要に応じて agg(lambda x: x.quantile(0.9)) や名前付き集計 (agg(p90=('population', lambda x: x.quantile(0.9))))。
  6. 結合と可視化: reset_index() 後に matplotlib/seaborn で可視化。 棒グラフ・箱ひげ図・ヒートマップが定番。
  7. レポート化: style.format() でフォーマット整形し、 to_excel で出力。 メソッドチェーン全体を関数化して再利用可能に。

ケーススタディ

groupby は「データを正しく分割し、 適切な集計関数を選び、 結果を意味のある形で伝える」までが本質です。 SSDSE-B-2026 を使って小さな集計から練習し、 徐々に複雑な業務データに展開しましょう。

📋 groupby が機能する前提条件と限界 (誤解回避)

groupby は pandas/Spark/SQL で 「分割 → 集計 → 結合 (Split-Apply-Combine)」 パターンを実装する核心 API。 簡潔だが、 集計対象列の型・欠損値の扱い・グループ内順序に依存して結果が大きく変わる。

前提 1: グルーピング列はカテゴリ型 or 比較可能な離散値

float の連続値で groupby すると、 浮動小数点誤差でグループが分裂する (例: 0.1 + 0.2 != 0.3)。 連続値は事前に pd.cut でビン分割するか round(2) で丸める。 SSDSE-B-2026 の年齢階級・地方コードのような事前定義カテゴリが理想。

前提 2: 集計関数はグループ内で意味を持つ

mean/sum/count は加法的だが、 median/quantile はソート、 std はサンプルサイズに依存する。 グループサイズが極端に小さい (n=1, 2) と、 std や 95%信頼区間が NaN または極端な値になる。 SSDSE-B では 47 県を 8 地方に分けると最小群 (北海道=1 県) で std が NaN になる点に注意。

前提 3: 欠損値の扱いを明示

デフォルトの groupby は NaN を含むグルーピング列の行を 除外する。 groupby(..., dropna=False) を指定すれば NaN グループも残る。 SSDSE-B のように欠損が「県データの未公表」を意味する場合、 dropna=False で可視化したほうが透明性が高い。

限界 1: 高カーディナリティでメモリ爆発

ユーザー ID (数百万) のような高カーディナリティ列で groupby + multiple agg を行うと、 中間結果がメモリを使い切る。 Spark の reduceByKeyDask groupby、 または SQL の partition by に切り替える。

限界 2: 順序依存の集計はソートが必須

first/last/cumsum はグループ内の順序に依存する。 元 DataFrame が時系列順でない場合、 sort_values してから groupby を行わないと、 期待と違う「最初の行」が選ばれる。 SSDSE-B の年次データを groupby('region').first() するなら、 必ず year で事前ソートする。

📝 理解度チェック (5 問 / SSDSE-B-2026 を想定)

  1. Q1. df.groupby('region').agg({'population': 'sum', 'gdp': 'mean'}) の結果に MultiIndex 列が現れない理由は何か。
  2. Q2. 47 都道府県を 8 地方に集計したとき、 北海道地方の std が NaN になる原因と対処 (3 つ以上)。
  3. Q3. groupby('region')['gdp'].transform('mean')groupby('region')['gdp'].mean() の返り値の形状の違いを示せ。
  4. Q4. Bonferroni 補正なしで「地方ブロック × 健康指標 10 項目」を一斉に groupby + ttest した時に発生する統計学的問題と、 適切な補正方法。
  5. Q5. 高カーディナリティ列 (ユーザー ID 1000 万件) で groupby('user_id').agg(['mean','std','count','min','max']) がメモリ不足になる時、 Spark/Dask/SQL のどれを選ぶ判断基準を 3 行で。

解答方針

🗺 概念マップ

groupby を中心に、Split-Apply-Combine の三工程、関連集計関数(agg・transform・apply)、SQL の GROUP BY との対応、ピボットテーブル・クロス集計への発展、SSDSE-B-2026 を地方別集計するパターンを放射状に配置した。集約後の MultiIndex 解除(reset_index)と describe との使い分けも矢印で示している。

pandas groupby pivot_table resample rolling cumsum / cumcount SQL の WINDOW 関数 apply / transform

pandas の groupby は Hadley Wickham の Split-Apply-Combine パラダイム (2011) を実装した中核機能で、 SQL の GROUP BY、 R の dplyr::group_by、 SQL の WINDOW 関数とほぼ同じ役割を Python で提供する。 SSDSE-B-2026 で「地方ブロック (北海道 / 東北 / 関東 / ...) ごとに人口の平均」を計算するなら df.groupby('地方').人口.mean() の 1 行で済み、 transform を使えば「各都道府県の人口を所属ブロックの平均で割った相対値」のような同じ行数の出力も得られる。

🔗 隣接手法への橋渡し

「groupby」は単独で完結する手法ではなく、 隣接領域と連携することで真価を発揮する。

上流のデータクリーニング・型変換で集計可能状態を作り、 並列のピボットテーブル・SQL の GROUP BY 句と機能を比較し、 下流の集約・ウィンドウ関数・時系列リサンプリングへ展開する。 pandas の groupby は探索的データ分析の最頻出操作で、 split-apply-combine パターンを理解すれば SQL・dplyr へ自然に転移できる。

🌳 手法選択フロー

「groupby」を実際の課題に当てはめるとき、 状況別に何を選ぶかを 3 段階で判定する。

  1. 集計の出力形は? 単一値 (合計・平均) → agg、 グループ内変換 → transform、 フィルタ → filter、 任意関数 → apply
  2. 速度が重要か? 軽量集計 → 組込関数 (sum・mean) を文字列指定、 重 → numba・swifter で高速化、 超大 → Dask・Polars に移行
  3. 複数列で集計するか? 同じ集計 → 列リストを agg に渡す、 列ごと異なる集計 → dict で指定、 同列で複数集計 → タプルリスト

groupby は split-apply-combine パターンの中核で、 一度習得すれば SQL の GROUP BY・dplyr の group_by・PySpark の groupBy へ自然に転移できる。 集計後の MultiIndex を reset_index で平坦化する習慣をつけると後工程が楽。

❌ NULL の扱い
pandas はデフォルトで NULL を無視。 SQL も同様。 全要素 NULL のグループは結果から消える。
❌ ソート順
pandas は groupby 後にキーでソート(デフォルト)。 大データでは sort=False で高速化。
❌ MultiIndex の混乱
複数列で groupby → 結果が MultiIndex に。 reset_index() で平坦化を忘れずに。
❌ apply の遅さ
Python 関数を apply すると、 内蔵 agg より桁違いに遅い。 ベクトル化を検討。
❌ カウントの違い
count() は非 NULL 数、 size() は行数(NULL 含む)。 用途で使い分け。

📜 ひとことヒストリー

groupby は「データ処理」分野の中で発展してきた概念・手法です。 学術的には継続的な研究で精緻化され、 実務的にはツール・ライブラリの普及で誰でも使えるようになってきました。 用語の使い方・意味は時代と分野で少しずつ変わるため、 文脈に応じた解釈が大切です。 入門書だけでなく、 標準的な教科書(例:データサイエンス・統計学の定本)や信頼できるオンライン教材も併用すると、 ぶれない理解に近づけます。

✅ 実務チェックリスト — groupby

  • □ 用語の定義を自分の言葉で説明できるか
  • □ 使うべき場面と使ってはいけない場面を区別できているか
  • □ 数式や指標の前提条件を確認したか
  • □ 入力データの尺度・分布・サンプル数を確認したか
  • □ 結果の不確実性(信頼区間・標準誤差)を把握しているか
  • □ 解釈と限界を区別できているか
  • □ 関連用語・落とし穴を一通り点検したか
  • □ レポートに必要な情報(出典・前提・限界)を含められるか

🎯 まとめ — このページで押さえること

「groupby」 はこのページで詳しく扱った概念です。 持ち帰ってほしい 3 つの要点

  1. groupby=データを「キー」でグループに分け、 各グループに対して集計を行う操作。
  2. SQL:GROUP BY / pandas:df.groupby('col') / R:group_by()
  3. アイデア:Split-Apply-Combine(分割 → 適用 → 結合)の 3 ステップ。

さらに学ぶには、 関連用語関連グループ教材 を参照してください。 各用語ページを縦断的に読むことで、 体系的な理解が育ちます。

🧭 groupby をもう一歩深掘り

このページ本文で扱った Split-Apply-Combine(分割→適用→結合) を、姉妹ページ(集約ピボットテーブル)とは違う角度で補強します。ここでの主役は「グループキーの粒度をどこに置くか」という設計判断です。集計関数を正しく選んでも、キーの取り方を誤ると結果は静かに壊れます。

🎨 直感

groupby は「同じラベルの行を同じ箱に投げ込み、箱ごとに要約する」操作です。ここで見落とされがちなのが 箱の中に何行入っているか。SSDSE-B-2026(data/raw/SSDSE-B-2026.csv)は 47 都道府県 × 12 年(2012〜2023)= 564 行あり、年を意識せず df.groupby('Prefecture').size() とすると、どの都道府県も箱の中身は 12 行(=12 年ぶんが混在)になります。「1 県=1 行」だと思い込んでいると、平均も合計もすべて 12 年ぶんを巻き込みます。

💡 最初のクセ付け:集計する前に groupby(key).size() で「箱の中身の数」を必ず眺める。数が想定と違えば、キーの粒度か行の重複を疑うサイン。

⚠️ 落とし穴(重要)— 年を無視した「全期間混在集計」

最も危険なのは、時系列を含むデータで年を groupby キーに入れ忘れることです。たとえば東京都の総人口(列 A1101)を、年で絞らずに集計してみます。

# 罠:年を無視して全期間を混ぜて平均
df.groupby('Prefecture')['A1101'].mean().loc['東京都']
# → 13,745,405.4(★どの年にも存在しない“幽霊”の値)

# 正:まず年で絞る(または年をキーに含める)
df[df['SSDSE-B-2026']==2023].groupby('Prefecture')['A1101'].sum().loc['東京都']
# → 14,086,000(2023年の実測)

全期間を混ぜた平均 13,745,405.4 人 は、2012 年の実測 13,234,000 人と 2023 年の実測 14,086,000 人の あいだにある値で、どの実在年にも対応しません。増加傾向のある系列を混ぜて平均すると、最新年(2023)を 約 34 万人(14,086,000 − 13,745,405.4 = +340,594.6 人)過小評価します。「現在の人口」を報告したつもりが、静かに過去に引きずられるわけです。

❌ 混在集計の見分け方
groupby(key).size() が想定より大きい(ここでは 47 県のはずが各 12)/集計結果の件数が期待と合わない/平均が「どの年の値とも一致しない」ときは、年(時間軸)がキーから抜けている。対策は df[df['SSDSE-B-2026']==2023] で断面を切るか、groupby(['SSDSE-B-2026','Prefecture']) と年をキーに含めること。

🔬 発展 — transform と aggregate は「返す行数」で使い分ける

「全期間混在」の罠は、実は transform を正しく使えば逆に武器になります。aggregate(.mean())は グループごとに 1 行へ畳み込むのに対し、transform(.transform('mean'))は 元の行数のまま、各行にそのグループの要約値をブロードキャストします。同じ SSDSE-B-2026 で並べると差は一目瞭然です。

df.groupby('Prefecture')['A1101'].mean().shape       # → (47,)  = 県ごと1行
df.groupby('Prefecture')['A1101'].transform('mean').shape # → (564,) = 元の全行を保つ

この 564 行を返す性質を使えば、「各年の値が、その県の 12 年平均からどれだけ離れているか」を 元表に列として付け足せます。東京都 2023 年なら 14,086,000 − 13,745,405.4 = +340,594.6 人(平均より上振れ=成長局面)と、行を潰さずに偏差を測れます。aggregate は「要約表を作る」、transform は「元表に注釈を足す」——返す形が違うので置き換えは効きません。ここが集約ページとの分岐点です。

🧪 下の小デモ(実データ):東京都 A1101 の実測 12 年(SSDSE-B-2026)で、年を指定した「実測値」と、年を無視した「全期間混在平均(幽霊値 13,745,405.4)」のズレを体感できます。
対象:東京都・総人口(A1101) 年を選ぶ:
選択年の実測:
全期間混在平均(幽霊値):
実測 − 混在平均のズレ:
出典:SSDSE-B-2026(data/raw、cp932, skiprows=[1])。バーの目盛りは 13,000,000〜14,200,000 人。

🔗 関連ページ

※ 本セクションの数値はすべて SSDSE-B-2026 実測(pd.read_csv(encoding='cp932', skiprows=[1]))から算出。合成・架空データは使用していません。