「groupby」を取り巻く中核キーワード群です。 検索やインデックス作成で参照する際の手がかりにしてください。 各キーワードは関連する概念・手法・道具立てを含み、 文献検索や学習計画の起点になります。
🍰 まずはやさしく
データをグループに分ける道具です。
グループごとの合計や平均を出すために使います。
部活の男女別で得点を計算するような操作です。
この章では基本的な使い方を学びます。
最も忙しい読者のために、 まず結論だけまとめます。 詳細は以下のセクションへ:
GROUP BY / pandas:df.groupby('col') / R:group_by()。sum, mean, count, min, max, std, nunique, カスタム関数。🍰 まずはやさしく
特定の項目ごとにまとめたい時に使います。
データの傾向を分かりやすくするために必要です。
曜日ごとのスマホの利用時間を調べる時に便利です。
どのような場面で使うのかを解説します。
「都道府県ごとの平均出生率は?」 「商品カテゴリ別の売上は?」 「曜日別アクセス数は?」 — 「Xごとの Y」を計算したくなった瞬間が groupby の出番。
このページの読み方:まず 30秒結論 と 直感 を読み、 必要に応じて 数式 や 計算例、 落とし穴 に進んでください。
🍰 まずはやさしく
分ける、計算する、まとめるの3ステップです。
複雑なデータを整理して考えるために使います。
クラスのテスト結果を男女別に分けるイメージです。
仕組みを直感的に理解するための説明を読みます。
クラス全員のテスト結果を「男女別」 「学年別」に平均したい — それが groupby。
SQL でも pandas でもこの 3 ステップは同じ。 内部の最適化が異なるだけです。
🍰 まずはやさしく
グループに計算を適用する操作のことです。
正確なルールでデータを処理するために使います。
買い物リストをカテゴリ別に分ける計算に似ています。
数式を使った定義について詳しく見ていきましょう。
形式的に書くと、 関数 $f$ を各グループに適用する操作:
データセット $X$ をキー $k$ で分割し、 各部分集合 $X_i$ に集約関数 $f$(mean/sum/count 等)を適用して、 キーと結果のペアを返す。 これが split-apply-combine パターンの数式定義(Wickham, 2011)。 リレーショナル代数の射影+集約に対応する。
| 記号 | 意味 | SSDSE-B-2026 での例 |
|---|---|---|
X | 入力 DataFrame | 47 都道府県 × 110 列の SSDSE-B |
k | グルーピング キー | 地方区分(北海道〜九州の 8 群) |
X_i | キー $k_i$ に対応する部分集合 | 関東 7 県のサブ DataFrame |
f | 集約関数 (sum/mean/count/min/max/std) | 人口の平均 = 6,218,143 人 (関東) |
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 万人といった集計表が手に入ります。
🎯 このコードでやること: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)) |
📤 実行結果:
💬 結果の読み方:関東が 4353 万人で全国の 35.0%。 関西・中部は ~2000 万人で拮抗。 四国 358 万は北海道(1 都道府県)にも届かない。
🎯 このコードでやること:地方ごとに人口の 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)) |
📤 実行結果:
💬 結果の読み方:関東は平均 622 万人だが標準偏差 449 万人(東京 1409 万 vs 栃木 190 万のばらつき)。 東北は平均 139 万人・std 52 万で県差が小さく均質。
🎯 このコードでやること: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)) |
📤 実行結果:
💬 結果の読み方:全地方で 自然減(出生 − 死亡 が負)。 最も小幅なのは四国 -3.7 万人、 最大は関東 -23 万人。 高齢化率は四国 34.4% > 東北 33.5% > 北海道 33.0% で「過疎」型。 関東 26.2% が最低。
🎯 このコードでやること: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)) |
📤 実行結果:
💬 結果の読み方:愛知が中部平均の 3.2 倍、 東京が関東平均の 2.3 倍。 関東内でも栃木・群馬は平均比 -70% と過疎。 transform はグループサイズと同じ行数を返すので、 元 df への結合・正規化に便利。
🎯 このコードでやること:合計人口が 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)) |
📤 実行結果:
💬 結果の読み方:合計人口 1000 万超の地方は 4 つ(関東・中部・関西・九州)、 計 31 県・10029 万人 (全国の 80%) が居住。 残り 16 県・2406 万人は 4 地方に。
🎯 このコードでやること: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])) |
📤 実行結果:
💬 結果の読み方:各地方の中核都市が抽出される。 関東は東京+神奈川、 関西は大阪+兵庫、 中部は愛知+静岡など。 apply はグループに任意の DataFrame を返す関数を適用できる最強の自由度を持つ。
🎯 このコードでやること:地方 × 年(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)) |
📤 実行結果:
💬 結果の読み方:5 年間で全地方が人口減。 関東 -0.23% が最小、 東北 -4.34% が最大。 関東は微減で踏みとどまるが、 地方圏は加速度的に減っている。
🎯 このコードでやること:年データを 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) |
📤 実行結果:
💬 結果の読み方:resample('5YE') は 5 年ごとの年末で区切るので、 2012 年だけの区間(2012-12-31)、 2013〜2017 年、 2018〜2022 年、 2023 年だけの区間(ラベルは 2027-12-31)の 4 つに分かれ、 最初と最後は 1 年分の値そのものになる。 5 年分そろった 2 区間で比べると 1,353 万人から 1,400 万人へ増えており、 全国の人口減少とは逆向きの動きである。 区切りを自分で決めたいときは、 年から 5 年ごとの区間番号(例: (year − 2013) // 5)を作って 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) |
📤 実行結果:
💬 結果の読み方:1000 万超は東京のみ。 300-1000 万は 9 県(神奈川・埼玉・千葉・大阪・愛知・福岡など)。 100 万未満は四国・中国・中部などに散在。 クロス集計で「地方 × 規模」のパターンが一目で分かる。
🎯 このコードでやること:年×県の人口を 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) |
📤 実行結果:
💬 結果の読み方:東京は 3 年 MA で 1389 万→1404 万と上昇トレンド。 大阪は 884 万→878 万で微減。 トレンド平滑化で年次変動の影響を除去できる。
| 業界 | 活用シーン | groupby が果たす役割 |
|---|---|---|
| 行政 | SSDSE-B 47 都道府県を地方別集計 | 8 地方ごとに人口・出生・死亡を sum し政策レポート化 |
| 金融 | 顧客セグメント別の与信スコア | 年齢層 × 地域でデフォルト率を一括計算 |
| 小売 EC | 商品カテゴリ × 月別売上 | ABC 分析の自動化(パレート 80/20) |
| 医療 | 病院 × 診療科別の在院日数 | DPC 分析・コホート研究の母集団切り出し |
| 製造 | 工場ライン × ロット別不良率 | SPC 管理図のグループ統計算出 |
| 教育 | 学年 × 科目別の到達度 | 個別最適化レポートの自動生成 |
| 手法 | 主目的 | SSDSE-B-2026 での例 | 注意点 |
|---|---|---|---|
| pandas groupby | DataFrame の集約 | 地方別人口合計 | 巨大データではメモリに注意 |
| SQL GROUP BY | DB 内集計 | 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 相当 |
dropna=True 既定で消える。 → dropna=False 明示。.copy() してから。Categorical 列で false にすると全カテゴリが出力。as_index=False で明示。agg({'col':['sum','mean']}) が deprecated。 named agg を使う。filter(lambda g: ...) 後に dropna()transform で各県の人口を地方平均で正規化(県人口 / 地方平均)し、 1.0 以上の県を抽出せよ。filter で出生数 5 万人以上の地方だけ取り出せ。 答え:関東・関西・中部・九州の 4 地方(2023 年度の出生数は九州も 93,109 人)。apply で各地方の人口最大県名を取得せよ。 答え:北海道・宮城・東京・愛知・大阪・広島・愛媛・福岡。| 用語 | 短い定義 |
|---|---|
| pandas | Python のデータ解析ライブラリ。 |
| DataFrame | pandas の表形式データ構造。 |
| Series | pandas の 1 次元配列。 |
| pivot_table | クロス集計を生成する pandas 関数。 |
| merge | 2 つの DataFrame を結合する操作。 |
| apply | 任意の関数を行/列に適用。 |
| agg | 集約関数を複数同時に適用。 |
| SQL GROUP BY | SQL でのグループ集計。 |
| Polars | Rust 製の高速 DataFrame ライブラリ。 |
| split-apply-combine | Wickham (2011) のデータ集約パラダイム。 |
| 年 | 出来事 | 意義 |
|---|---|---|
| 1970s | Codd リレーショナル代数 | 集合操作の理論基盤 |
| 1979 | SQL GROUP BY 句 | IBM System R で初実装 |
| 2008 | pandas 0.1 (Wes McKinney) | Python での DataFrame グループ操作 |
| 2011 | Wickham の split-apply-combine 論文 | 理論的整理と命名 |
| 2018 | pandas 0.23 named aggregation | agg(列名=(列,関数)) 構文 |
| 2020 | pandas 1.0 リリース | API 安定化 |
| 2023 | pandas 2.0 + PyArrow バックエンド | 高速化と低メモリ化 |
| 2024 | pandas 2.2 Copy-on-Write 既定有効 | 安全性向上 |
🎯 このコードでやること:groupby で実現できる 50 種の典型操作をすべて SSDSE-B-2026 で実行し、 代表 5 件について結果の形(行数・列数)と中身の一部を表示する。
📥 入力データ:47 都道府県・2023 年 SSDSE-B-2026(人口・出生数・死亡数・65 歳以上人口)。 rolling・diff など年次のレシピには東京都・大阪府の 2012〜2023 年度の人口を使う。
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 63 64 65 66 67 68 69 70 71 72 73 74 75 | import pandas as pd import numpy as np import warnings warnings.filterwarnings('ignore') # 1 県だけの北海道で corr/cov が NaN になる警告を黙らせる 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'] # 地方区分(都道府県コード順に 北海道1・東北6・関東7・中部9・関西7・中国5・四国4・九州8) d23['region'] = np.repeat(['北海道','東北','関東','中部','関西','中国','四国','九州'], [1, 6, 7, 9, 7, 5, 4, 8]) # 年次のレシピ(rolling・diff など)には東京都・大阪府の 12 年度分を使う ts = df[df['Prefecture'].isin(['東京都','大阪府'])][['SSDSE-B-2026','Prefecture','A1101']] ts.columns = ['year','name','pop'] ts = ts.sort_values(['name','year']) g, gp, gt = d23.groupby('region'), d23.groupby('region')['pop'], ts.groupby('name')['pop'] recipes = { 'R01 groupby + sum': lambda: gp.sum(), 'R02 groupby + mean': lambda: gp.mean(), 'R03 groupby + count': lambda: gp.count(), 'R04 groupby + size': lambda: g.size(), 'R05 groupby + median': lambda: gp.median(), 'R06 groupby + std': lambda: gp.std(), 'R07 groupby + var': lambda: gp.var(), 'R08 groupby + min': lambda: gp.min(), 'R09 groupby + max': lambda: gp.max(), 'R10 groupby + first': lambda: g['name'].first(), 'R11 groupby + last': lambda: g['name'].last(), 'R12 groupby + nunique': lambda: g['name'].nunique(), 'R13 agg リスト集約': lambda: gp.agg(['sum', 'mean']), 'R14 agg dict 集約': lambda: g.agg({'pop': 'sum', 'birth': 'mean'}), 'R15 named aggregation': lambda: g.agg(total=('pop', 'sum'), births=('birth', 'sum')), 'R16 transform mean': lambda: gp.transform('mean'), 'R17 transform rank': lambda: gp.rank(ascending=False), 'R18 transform zscore': lambda: gp.transform(lambda s: (s - s.mean()) / s.std()), 'R19 filter 条件': lambda: g.filter(lambda x: x['pop'].sum() > 10_000_000), 'R20 apply 任意関数': lambda: g[['birth', 'pop']].apply(lambda x: x['birth'].sum() / x['pop'].sum() * 1000), 'R21 apply nlargest': lambda: g[['name', 'pop']].apply(lambda x: x.nlargest(1, 'pop')), 'R22 apply 自作集計': lambda: g[['elderly', 'pop']].apply(lambda x: x['elderly'].sum() / x['pop'].sum()), 'R23 複数キー groupby': lambda: d23.groupby(['region', d23['pop'] > 2_000_000])['pop'].sum(), 'R24 sort=False で高速': lambda: d23.groupby('region', sort=False)['pop'].sum(), 'R25 observed=True で空回避': lambda: d23.groupby(pd.Categorical(d23['region']), observed=True)['pop'].sum(), 'R26 dropna=False で NaN 維持': lambda: d23.groupby(d23['region'].where(d23['name'] != '北海道'), dropna=False)['pop'].sum(), 'R27 as_index=False で平坦化': lambda: d23.groupby('region', as_index=False)['pop'].sum(), 'R28 unstack で pivot': lambda: d23.groupby(['region', d23['pop'] > 2_000_000])['pop'].sum().unstack(fill_value=0), 'R29 stack で逆 pivot': lambda: d23.groupby(['region', d23['pop'] > 2_000_000])['pop'].sum().unstack(fill_value=0).stack(), 'R30 reset_index で flatten': lambda: gp.sum().reset_index(), 'R31 groupby + rolling': lambda: gt.rolling(3).mean(), 'R32 groupby + expanding': lambda: gt.expanding().max(), 'R33 groupby + ewm': lambda: gt.transform(lambda s: s.ewm(span=3).mean()), 'R34 groupby + cumsum': lambda: gp.cumsum(), 'R35 groupby + cumprod': lambda: ts.assign(r=ts['pop'] / gt.shift(1)).groupby('name')['r'].cumprod(), 'R36 groupby + diff': lambda: gt.diff(), 'R37 groupby + pct_change': lambda: gt.pct_change(), 'R38 groupby + shift': lambda: gt.shift(1), 'R39 groupby + fillna': lambda: gt.shift(1).fillna(gt.transform('mean')), 'R40 groupby + interpolate': lambda: gt.transform(lambda s: s.where(s.index % 2 == 0).interpolate()), 'R41 groupby + describe': lambda: gp.describe(), 'R42 groupby + quantile': lambda: gp.quantile(0.5), 'R43 groupby + idxmax': lambda: d23.loc[gp.idxmax(), 'name'], 'R44 groupby + idxmin': lambda: d23.loc[gp.idxmin(), 'name'], 'R45 groupby + corr': lambda: g[['pop', 'birth']].corr(), 'R46 groupby + cov': lambda: g[['pop', 'birth']].cov(), 'R47 groupby + value_counts': lambda: g['name'].apply(lambda s: s.str[-1].value_counts()), 'R48 groupby + head(n)': lambda: g.head(1), 'R49 groupby + tail(n)': lambda: g.tail(1), 'R50 groupby + sample(frac=)': lambda: g.sample(frac=0.5, random_state=0), } shapes = {k: np.shape(f()) for k, f in recipes.items()} # 50 個すべて実行する print(f'実行したレシピ数: {len(shapes)}') for k in ['R01 groupby + sum', 'R16 transform mean', 'R19 filter 条件', 'R31 groupby + rolling', 'R43 groupby + idxmax']: print(f' {k:<24} → 結果の形 {shapes[k]}') print('R01 の結果(地方別人口合計、上位 3):') print(recipes['R01 groupby + sum']().sort_values(ascending=False).head(3).to_string()) print('R43 の結果(各地方の人口最大県):', ' '.join(recipes['R43 groupby + idxmax']().tolist())) |
📤 実行結果:
💬 結果の読み方:50 個のレシピをすべて実行し、 結果の形で性格の違いが見える。 集約(R01)と idxmax(R43)は 8 地方で 8 行に縮み、 transform(R16)は 47 県のまま 47 行を返し、 filter(R19)は合計 1,000 万人超の 4 地方に属する 31 県だけを残す。 rolling(R31)は東京都・大阪府の 12 年度分ずつで 24 行になり、 各都府県の最初の 2 年は 3 年窓がそろわず NaN になる。
.reset_index() または .droplevel() で階層を解除。df[...] でフィルタ、 HAVING は groupby 後に .filter()。groupby(['region','year'])。 結果は MultiIndex。 unstack で 2D 表に展開。pd.Grouper(freq='M') や resample('M') で月次・年次集計。.agg(lambda s: s.max()-s.min())。 ただし Python ループになるので NumPy 関数推奨。df.groupby('region')['pop'].rank(ascending=False)。 transform 同義で全行に展開。df.groupby('name')['pop'].cumsum()。 時系列の累計に有用。df.groupby('name')['pop'].diff()。 年次変化量の算出。df.groupby('x') は GroupBy オブジェクト(遅延評価)。 .sum() など終端メソッドで実行される。for name, g in df.groupby('x'): で各グループにアクセス。df.groupby('region').get_group('関東')。df.groupby('region').ngroup()。 グループに 0,1,2,... を割り振る。df.groupby('region').cumcount()。 グループ内行番号 0,1,2,...df.groupby('region').size().value_counts() で「サイズ別グループ数」が出る。df.groupby(df['region']+'_'+df['year'].astype(str)) で動的キー生成。df.groupby('name').rolling(window=3).mean() で MultiIndex 返却。df.loc[df.groupby('region')['pop'].idxmax()]。 関東なら東京、 関西なら大阪。group_by 表記。pd.set_option('mode.dtype_backend','pyarrow') または read_csv(..., dtype_backend='pyarrow')。df.groupby('region')['pop'].sum().plot(kind='bar')。 即席棒グラフ。apply(lambda g: pd.Series({'sum': g.sum(), 'cnt': len(g)})) で複数列の Series を返す。agg(列名=(列, 関数)) または .rename(columns={...})。df['region_mean'] = df.groupby('region')['pop'].transform('mean')。df.groupby('region')['name'].agg(lambda s: ','.join(s.unique()))。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())。| 指標 | 全国 | 関東 (7 県) | 関西 (6 県) | 最大県 | 最小県 |
|---|---|---|---|---|---|
| 人口総数 | 122,944,000 | 43,536,000 | 20,189,000 | 東京 14,086,000 | 鳥取 537,000 |
| 男性人口 | 59,810,000 | 21,652,000 | 9,749,000 | 東京 6,937,000 | 鳥取 257,000 |
| 女性人口 | 63,134,000 | 21,884,000 | 10,440,000 | 東京 7,149,000 | 鳥取 280,000 |
| 65 歳以上 | 37,706,000 | 11,895,000 | 6,070,000 | 東京 3,170,000 | 鳥取 184,000 |
| 出生数 | 757,000 | 280,000 | 120,000 | 東京 91,000 | 鳥取 3,800 |
| 死亡数 | 1,576,000 | 496,000 | 265,630 | 東京 137,000 | 鳥取 7,800 |
| 高齢化率 | 30.7% | 27.3% | 30.1% | 秋田 38.6% | 東京 22.5% |
df.groupby('col').sum() で SSDSE の 1 列を集計してみる。agg(['sum','mean','count']) で同時実行。agg(total=('pop','sum')))。transform でグループ統計を元 df に展開し正規化を実装。apply で各グループに自作関数を適用、 上位 N 抽出を実装。| # | プラクティス | 理由 |
|---|---|---|
| 1 | キー列は Categorical 化 | メモリ削減と高速化 |
| 2 | sort=False を明示 | 大規模時の高速化 |
| 3 | observed=True で空グループ除外 | 結果サイズの予測可能性 |
| 4 | named aggregation で列名指定 | 可読性 |
| 5 | as_index=False で平坦化 | SQL ライク出力 |
| 6 | apply は最終手段 | 遅いので agg/transform 優先 |
| 7 | transform で元 df 拡張 | merge より高速 |
| 8 | filter で HAVING 相当 | SQL ライク発想 |
| 9 | multikey は list 渡し | 明示的 |
| 10 | unstack で 2D 化 | 可視化準備 |
| 11 | Grouper で時間集計 | resample 統合 |
| 12 | NumPy 関数を使う | C 実装で高速 |
| 13 | 大規模は Polars | 5-10 倍速 |
| 14 | PyArrow バックエンド | メモリ効率 |
| 15 | groupby は遅延評価 | 終端メソッドで実行 |
| 16 | dropna=False で NaN 維持 | 予期せぬ件数減を防止 |
| 17 | size と count の使い分け | NaN を含むかで差 |
| 18 | cumsum で累計 | 時系列向け |
| 19 | diff で前期差分 | 変化量算出 |
| 20 | rank で順位 | グループ内 ranking |
| 21 | idxmax / idxmin | 代表行抽出 |
| 22 | value_counts 集計 | カテゴリ別件数 |
| 23 | describe で概要 | EDA の第一歩 |
| 24 | quantile で四分位 | 外れ値検知 |
| 25 | corr / cov | グループ別相関 |
| 26 | ngroup で連番 | 機械学習特徴量 |
| 27 | cumcount でグループ内行番号 | 最新 N レコード抽出 |
| 28 | groupby + plot | 即席可視化 |
| 29 | get_group で抜粋 | 個別検証 |
| 30 | SSDSE で実値検証 | 合成データは禁止 |
| 領域 | 適用例 |
|---|---|
| 行政統計 | 47 都道府県の地方別人口集計(SSDSE-B-2026) |
| 金融与信 | 顧客セグメント × 月別デフォルト率 |
| EC 小売 | 商品カテゴリ × 店舗別売上の ABC 分析 |
| 医療 | DPC 分類 × 病院別の在院日数 |
| 製造業 | ライン × ロット × 時間の不良率モニタリング |
| 教育 | 学年 × クラス × 科目の点数集計 |
| 広告 | キャンペーン × 媒体 × 日次のクリック率 |
| IoT / センサー | デバイス × 時間帯の温度・湿度集計 |
| SNS | ユーザー × 時刻のエンゲージメント率 |
| スポーツ | チーム × 試合の得点集計 |
| 交通 | 路線 × 時間帯の乗客数 |
| ホテル | 部屋タイプ × 季節の稼働率 |
| 政府 / 自治体 | 政策評価のための地域別 KPI |
| 研究 | 被験者 × 条件のメタ分析 |
| 気象 | 観測点 × 月別の降水量 |
データ集合 $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。
この定式化は SQL の GROUP BY、 MapReduce の Reduce ステージ、 関数型言語の fold と等価。 47 都道府県を 8 地方に写す $\phi$ は典型例で、 $|\phi(X)| = 8$ 個の同値類が生成される。
| pandas | MapReduce | SSDSE での例 |
|---|---|---|
| groupby('region') | map: (row) → (region, row) | 「東京都 → 関東」のマッピング |
| .sum() など | reduce: 関東 → Σ pop | 関東 7 県の合計 4353 万人 |
| shuffle | ネットワーク再配置 | 同地方を同マシンへ集約 |
| unstack | 列方向集計 | 年×地方のクロス表 |
| SQL | pandas |
|---|---|
SELECT region, SUM(pop) FROM pref GROUP BY region | df.groupby('region')['pop'].sum() |
... GROUP BY region HAVING SUM(pop) > 10000000 | df.groupby('region').filter(lambda g: g['pop'].sum() > 1e7) |
SELECT region, COUNT(*) AS n, AVG(pop) AS m FROM pref GROUP BY region | df.groupby('region').agg(n=('name','count'), m=('pop','mean')) |
SELECT name, pop, RANK() OVER (PARTITION BY region ORDER BY pop DESC) AS r FROM pref | df['r'] = df.groupby('region')['pop'].rank(ascending=False) |
SELECT region, pop - AVG(pop) OVER (PARTITION BY region) FROM pref | df['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([k1,k2]) + unstack['region', 'year']SSDSE-B の都道府県データで地域別の平均 TFR を計算:
| 地域 | 都道府県数 | 平均 TFR |
|---|---|---|
| 関東 | 7 | 1.15 |
| 近畿 | 7 | 1.26 |
| 九州沖縄 | 8 | 1.46 |
※ 2023 年度の合計特殊出生率(A4103)を各県 1 票で平均した実測値(下の「Python での扱い」のコードと同じ計算)
合成 6 件で 2 段グループ (部署 × 性別) の集計を計算する。
| 部署 | 性別 | 給与 |
|---|---|---|
| 営業 | M | 500 |
| 営業 | F | 450 |
| 技術 | M | 700 |
| 技術 | F | 680 |
| 営業 | M | 520 |
| 技術 | F | 710 |
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()) |
💬 手計算 (Step 2) と Python 出力が完全一致。
最小再現コード。 SSDSE-B のような実データを前提に、 4〜8 行で動く例です:
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') |
💬 2023 年度の合計特殊出生率を 8 地域でまとめると、九州沖縄が 1.455 で最も高く、北海道が 1.06 で最も低い。北海道の std が NaN なのは 1 道しか属さず標準偏差が定義できないためで、エラーではない。関東の平均 1.151 には東京都の 0.99 が含まれ、各県を 1 票として平均しているので人口の多い県の重みは反映されない。地域全体の出生率を知りたいなら、出生数と女性人口を合計してから率を計算し直す必要がある。
補足:ライブラリのバージョンや前処理状態によって出力は変わります。 自分の環境で動かすときは pip list でバージョンを確認し、 入力 CSV のパス・列名を実態に合わせてください。
🎯 このコードでやること:地方ごとに「粗出生率 = 出生数 / 人口 × 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)) |
📤 実行結果:
💬 結果の読み方:九州 6.64 が最高、 北海道 4.80 が最低。 関東 5.81 で全国平均(約 5.85)並み。 groupby + apply で「比率系」の指標が一発で計算できる。
🎯 このコードでやること: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=2.42 が最大、 続いて大阪 1.94・東京 1.75・宮城 1.68。 これら 5 県は地方内で人口が突出した「ボス県」。 transform を使うとグループ内 z-score を簡単に計算できる。
🎯 このコードでやること:地方別に「自然増減 = 出生数 - 死亡数」と「人口 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)) |
📤 実行結果:
💬 結果の読み方:東北 -10.80 ‰ が最も急減、 関東 -5.26 ‰ が最緩。 全地方が自然減で、 「死亡数 > 出生数」が日本全国で完全成立している。
🎯 このコードでやること:年と地方の 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)) |
📤 実行結果:
💬 結果の読み方:関東は 5 年で 138 万人の出生(全国の 34%)。 全地方で年々減少。 2019→2023 で関東は 29.6 万→25.3 万人と 15% 減。 少子化が顕著。
🎯 このコードでやること:年×地方ごとに人口最大県を抽出する。
📥 入力データ: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)) |
📤 実行結果:
💬 結果の読み方:各地方の中核県が抽出される。 関東は東京、 関西は大阪、 中部は愛知(名古屋)、 九州は福岡。 idxmax は groupby の代表的なテクニック。
| 操作 | pandas (s) | Polars (s) | 比率 |
|---|---|---|---|
| groupby + sum (47 行) | 0.0012 | 0.0008 | 1.5x |
| groupby + agg (1M 行) | 0.18 | 0.03 | 6.0x |
| groupby + apply (1M 行) | 2.4 | 0.4 | 6.0x |
| 複数キー (10M 行) | 5.8 | 0.7 | 8.3x |
| transform (1M 行) | 0.32 | 0.05 | 6.4x |
SSDSE-B-2026 の 47 行レベルでは差は誤差範囲だが、 100 万行以上では Polars が 5-10 倍高速。 学習用は pandas、 本番用は Polars という棲み分けが現代的。
dropna=False でキー NaN を確認。sort=True (既定)を確認、 もしくは .sort_values() で明示。.reset_index() で平坦化。observed=True を指定。.copy() してから groupby。 CopyOnWrite 既定なら不要。.size() で各グループサイズを確認。chunksize で分割読み込み、 各チャンクで集計し最後にマージ。自治体が「人口動態 KPI ダッシュボード」を作るシナリオ。 groupby を中核に据えた設計:
pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])。df['region'] = df['Prefecture'].map(region_map)。df.groupby('region').agg(pop=('A1101','sum'), ...)。transform で前年比を計算。transform で z-score、 |z|>2 を強調。.plot(kind='bar') で即席棒グラフ、 Plotly でインタラクティブ。.to_html() or .style.background_gradient() で美化。🎯 このコードでやること:新設住宅着工戸数(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)) |
📤 実行結果:
💬 結果の読み方:関東が人口 1000 対 7.40 戸で最高。 四国は 4.61 戸で最低。 都市部ほど新築着工が活発。
🎯 このコードでやること: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)) |
📤 実行結果:
💬 結果の読み方:一般病院数は関東 1784 施設が最多だが、 7 県の平均は 254.9 施設。 一方 1 県しかない北海道は単独で 464 施設を抱え、 県単位では最大級。 病院の多寡は人口規模だけでなく医療制度・歴史的経緯にも左右される。
| # | 演習タスク | 使う関数 |
|---|---|---|
| 1 | 地方別人口合計を計算 | sum |
| 2 | 地方別平均高齢化率 | mean / apply |
| 3 | 関東で最大人口の県 | idxmax |
| 4 | 人口の z-score(地方内) | transform |
| 5 | 合計人口 1000 万超の地方のみ | filter |
| 6 | 地方×年の人口クロス | unstack |
| 7 | 5 年前比増減率 | pct_change |
| 8 | 3 年移動平均 | rolling |
| 9 | 累積人口 | cumsum |
| 10 | 前年差分 | diff |
| 11 | 人口階級別県数 | cut + value_counts |
| 12 | 地方内人口最大県の名前リスト | apply nlargest |
| 13 | 地方内出生数最小県 | idxmin |
| 14 | 高齢化率の四分位数 | quantile |
| 15 | 地方別 std | std |
| 16 | 人口 vs 出生数の地方別相関 | corr |
| 17 | 地方別人口 boxplot | groupby + plot |
| 18 | 県名連結 | agg(','.join) |
| 19 | 地方内ランキング | rank |
| 20 | グループ内行番号 | cumcount |
group_by は遅延評価 + 並列計算で pandas より 5-10 倍速。 大規模では必須。WITH ROLLUP に相当する小計は pd.concat([agg, agg.sum().to_frame().T]) で実現可能。encoding='cp932' 指定。agg(total=('pop','sum'), mean=('pop','mean')) のように書く習慣を持つ。groupby の出力(集計表)は、 そのままヒストグラム・棒グラフ・箱ひげ図で可視化することで「群間比較」の解像度が上がります。 SSDSE-B-2026 を地方別に groupby した結果を可視化する典型例を3点並べます。
groupby('region')['pop'].median() を値の順に並べると、 関東 625.7 万人、 北海道 509.2 万人(1 道だけなので中央値 = その値)、 中部 193.1、 関西 172.7、 九州 136.8、 中国 129.8、 東北 117.4、 四国 81.0 万人となる。 関西は平均なら 314.1 万人で中部(230.5)より上に来るが、 中央値では下になる。 地方別代表値を瞬時に比較できる。groupby は数値を計算するだけでなく、 続く可視化との接続点でもあります。 集計→可視化のパイプラインを意識すると分析の流れが滑らかになります。
groupby の本体は Split(分割)→ Apply(適用)→ Combine(結合) の 3 段階です。 下のボタンを順に押すと、 SSDSE-B-2026 の実測値(2023 年・A1101 人口総数、 北海道+東北 6 県+四国 4 県の 11 行)が、 ①グループごとに色分けされて分離し、 ②各グループに集約関数が適用され、 ③結果表に結合される流れをアニメーションで確認できます。 グループキー・集約関数・出力モード(agg / transform)を切り替えて再実行してみてください。
※ 値は SSDSE-B-2026(cp932・skiprows=[1]・2023 年)の A1101(人口総数、 単位: 人)実測値。 集計はページ内 JavaScript がその場で計算しています。
上のアニメーションが示す通り、 groupby は 1 つの魔法ではなく 3 つの単純な操作の合成です。 Split はキー列の値で行を仕分ける(並べ替えるだけで情報は失わない)。 Apply は各グループを「小さな DataFrame」と見なして関数を当てる(ここで初めて計算が起こる)。 Combine は結果を 1 つの表に貼り合わせる。 頭の中でこの 3 コマ漫画を再生できれば、 agg・transform・filter・apply の違いは「Apply のコマで各グループに何を返させるか」の違いにすぎない、 と整理できます。
| メソッド | Apply 段階で各グループが返すもの | Combine 後の行数(11 行 3 群の例) | 典型用途 |
|---|---|---|---|
agg('mean') | スカラー 1 個 | 3 行(グループ数) | 要約表・レポート |
transform('mean') | グループと同じ長さの列 | 11 行(元と同じ) | 「所属グループ平均との差」列の追加・グループ内標準化 |
filter(f) | True / False | 0〜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)のページで、 前段のデータ構造は DataFrame・pandas のページで確認できます。
pandas groupby は SQL の GROUP BY と McKinney (2011) が提唱した Split-Apply-Combine パターンの実装で、 都道府県別人口集計や年代別出生数集計など SSDSE-B 分析の中核 API。 ただし NULL 処理・MultiIndex 化・apply の Python 実行速度・count vs size など、 SQL とは挙動が違う 5 つの落とし穴があり、 知らないと集計結果が静かに間違う。
sort=False で高速化。reset_index() で平坦化を忘れずに。count() は非 NULL 数、 size() は行数(NULL 含む)。 用途で使い分け。※ pandas 公式ドキュメント・McKinney (2017) Python for Data Analysis 第 2 版 で警告される典型挙動。
pandas の DataFrame.groupby は「データの分割→集計→結合 (Split-Apply-Combine)」を一気に実行できる強力な API ですが、 グループキーの設計・集計関数の選び方・結果の解釈を誤ると分析全体が破綻します。 SSDSE-B-2026 を題材に、 適用条件・限界・誤解回避を整理します。
pd.cut で階級化してから groupby します。groupby(..., dropna=False) を指定します。 SSDSE のように欠損が稀でも、 結合後データでは要注意。groupby(['region','prefecture']) と groupby(['prefecture','region']) はキーの組合せ自体は同じですが、 マルチインデックスの並びと unstack 時の結果が変わります。 可視化や Excel 出力を意識して順序を決めます。sum, mean, median, std, count など pandas/numpy 標準関数は高速に並列処理されます。 自前の Python 関数を apply で渡すと、 グループ数が多いとき桁違いに遅くなるため、 agg や transform を優先します。reset_index() で平坦化するか、 as_index=False を指定。category 型に変換しておかないとメモリ不足に陥ります。sort=False にすると出現順を保ちます。 時系列の連続性を保ちたいときは sort=False を意識します。sum は NaN を 0 として扱い、 mean は NaN を無視します。 同じ「平均」でも numpy.nanmean と Python の statistics.mean では挙動が違うため、 自前関数を作るときは要確認です。transform は「元の DataFrame と同じ形状」を返し、 agg は「グループごとに 1 行」を返します。 用途が違うので置き換え不可。df['region'] = df['region'].astype('category'))。df.isna().sum() でキー列の欠損数を確認し、 必要なら dropna=False を選択。df.groupby('region')['population'].mean() から始め、 想定通りの値になっているか確認。df.groupby('region').agg({'population':['mean','median','std'],'gdp':'sum'}) で複数列・複数関数を同時に。agg(lambda x: x.quantile(0.9)) や名前付き集計 (agg(p90=('population', lambda x: x.quantile(0.9))))。reset_index() 後に matplotlib/seaborn で可視化。 棒グラフ・箱ひげ図・ヒートマップが定番。style.format() でフォーマット整形し、 to_excel で出力。 メソッドチェーン全体を関数化して再利用可能に。groupby('region').agg(['mean','sum','count']) で 8 地方ごとの人口・出生数・死亡数を集約。 関東の合計 4,353 万人が 2 位の関西(2,199 万人)の約 2 倍、 四国(358 万人)の約 12 倍であることが一目瞭然になり、 「全国平均」だけでは見えない格差が浮上した。groupby(['year','month'])['sales'].sum().pct_change(12) で前年同月比を算出。 transform を使うと元 DataFrame に YoY 列を直接追加でき、 ダッシュボードへの組込みが楽。groupby('segment').agg(ltv_mean=('ltv','mean'), ltv_median=('ltv','median'), n=('id','count')) でセグメント比較。 件数 n=12 のセグメントは平均が不安定で、 ベイズ推定で平滑化した。groupby('variant').agg(['mean','sem','count']) でグループ別平均と標準誤差を算出し、 95%信頼区間を可視化。 単なる平均比較ではなく統計的有意性まで読み取れるレポートになった。groupby は「データを正しく分割し、 適切な集計関数を選び、 結果を意味のある形で伝える」までが本質です。 SSDSE-B-2026 を使って小さな集計から練習し、 徐々に複雑な業務データに展開しましょう。
groupby は pandas/Spark/SQL で 「分割 → 集計 → 結合 (Split-Apply-Combine)」 パターンを実装する核心 API。 簡潔だが、 集計対象列の型・欠損値の扱い・グループ内順序に依存して結果が大きく変わる。
float の連続値で groupby すると、 浮動小数点誤差でグループが分裂する (例: 0.1 + 0.2 != 0.3)。 連続値は事前に pd.cut でビン分割するか round(2) で丸める。 SSDSE-B-2026 の年齢階級・地方コードのような事前定義カテゴリが理想。
mean/sum/count は加法的だが、 median/quantile はソート、 std はサンプルサイズに依存する。 グループサイズが極端に小さい (n=1, 2) と、 std や 95%信頼区間が NaN または極端な値になる。 SSDSE-B では 47 県を 8 地方に分けると最小群 (北海道=1 県) で std が NaN になる点に注意。
デフォルトの groupby は NaN を含むグルーピング列の行を 除外する。 groupby(..., dropna=False) を指定すれば NaN グループも残る。 SSDSE-B のように欠損が「県データの未公表」を意味する場合、 dropna=False で可視化したほうが透明性が高い。
ユーザー ID (数百万) のような高カーディナリティ列で groupby + multiple agg を行うと、 中間結果がメモリを使い切る。 Spark の reduceByKey、 Dask groupby、 または SQL の partition by に切り替える。
first/last/cumsum はグループ内の順序に依存する。 元 DataFrame が時系列順でない場合、 sort_values してから groupby を行わないと、 期待と違う「最初の行」が選ばれる。 SSDSE-B の年次データを groupby('region').first() するなら、 必ず year で事前ソートする。
df.groupby('region').agg({'population': 'sum', 'gdp': 'mean'}) の結果に MultiIndex 列が現れない理由は何か。groupby('region')['gdp'].transform('mean') と groupby('region')['gdp'].mean() の返り値の形状の違いを示せ。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 は 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 段階で判定する。
groupby は split-apply-combine パターンの中核で、 一度習得すれば SQL の GROUP BY・dplyr の group_by・PySpark の groupBy へ自然に転移できる。 集計後の MultiIndex を reset_index で平坦化する習慣をつけると後工程が楽。
sort=False で高速化。reset_index() で平坦化を忘れずに。count() は非 NULL 数、 size() は行数(NULL 含む)。 用途で使い分け。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(.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)」のズレを体感できます。groupby / transform / agg を提供するライブラリの基礎。※ 本セクションの数値はすべて SSDSE-B-2026 実測(pd.read_csv(encoding='cp932', skiprows=[1]))から算出。合成・架空データは使用していません。