9. 集計とクロス集計
学習目標
groupbyで平均・合計・件数を集計できるagg()で複数の集計関数を同時に適用できるpivot_tableでクロス集計表を作れる- 集計結果を
reset_index()で整形し、可視化に渡せる
本文
グループごとに要約したい場面
データ全体の平均や合計を出すことは、実はそれほど多くありません。実務でも研究でも、興味があるのは 「カテゴリごとの平均」「グループごとの合計」 といった 要約 です:
- 都市ごとの平均年齢
- 性別ごとの平均収入
- 部署ごとの売上合計
- カテゴリごとの件数
pandas はこの「グループ別の要約」を 2 つの仕組みで支援します。groupby と pivot_table です。両者は内部的に近い処理をしますが、得意な「見せ方」が違うので使い分けます。
groupby の動作モデル
groupby の動作は 「分割 → 集計 → 結合」(split-apply-combine)の 3 段階です:
# 都市ごとの平均年齢・平均収入
df.groupby("都市").mean(numeric_only=True)
- 分割(split) — 「都市」列の値ごとにデータを部分集合に分ける
- 集計(apply) — 各部分集合に対して
mean()を実行 - 結合(combine) — 結果を 1 つの DataFrame にまとめて返す
結果は「都市」が行インデックスになった DataFrame です。numeric_only=True は 文字列の列を集計対象から外す ための指示。これを付けないと「『東京』と『大阪』の平均」のような意味不明な計算を pandas が試みてエラーになることがあります。
複数キーで集計する
「都市 × 性別」のように 2 つ以上のキーでグループ化 したい場合は、列名をリストで渡します:
df.groupby(["都市", "性別"]).sum(numeric_only=True)
結果の行インデックスは MultiIndex(階層インデックス) になります。東京・男性 東京・女性 大阪・男性 … のように 2 段に積み上がる構造です。「人が見て分かる」というよりは「次の処理に渡す中間データ」として有用な形です。
agg() で複数の集計関数を同時に
「平均と合計と件数を一度に欲しい」場合は agg() を使います。列ごとに別々の集計関数を指定したり、複数の関数を同時に当てたりできます:
df.groupby("都市").agg({
"年齢": "mean",
"収入": ["mean", "sum", "count"],
})
- 文字列で関数名を渡す(
"mean","sum","count"ほか) - リストで渡せば複数同時
最初は読みづらく感じますが、慣れると 1 回の groupby で必要な統計を全部出す のに便利で、コードも短くまとまります。
pivot_table でクロス集計
「都市」を行、「性別」を列、セルに「平均収入」を入れた 2 次元の表が欲しいとき。これは クロス集計 と呼ばれる古典的な要約形式です。pivot_table がこれを直接作ってくれます:
pd.pivot_table(
df,
index="都市", # 行
columns="性別", # 列
values="収入", # 集計するセルの値
aggfunc="mean", # 集計関数(既定は mean)
)
groupby(["都市", "性別"]).mean() でも同じ計算自体は可能ですが、結果が MultiIndex で縦に積み上がる か 行 × 列の表として横に広がる かが違います。
- 「人に見せる表」を作る →
pivot_table - 「次の処理に渡す中間データ」 →
groupby
と覚えると使い分けやすくなります。
集計結果を可視化に渡す
groupby や pivot_table の結果はそのままでは可視化に渡しにくい場合があります。MultiIndex のままだと matplotlib が解釈しにくいので、reset_index() で 通常の DataFrame(整数の行インデックス + すべて列) に戻すのが定石です:
agg_df = df.groupby("都市")["収入"].mean().reset_index()
agg_df.plot.bar(x="都市", y="収入")
reset_index() を当てると、行インデックスだった「都市」が 普通の列に「降りて」きて、plot.bar(x="都市", ...) のように直接指定できるようになります。集計と可視化の橋渡しでよく使うので、reset_index() の名前と役割をセットで覚えておくと作業が早くなります。
よく出る躓きどころ
numeric_only=Trueを忘れる — 文字列列を含むデータでmean()を呼ぶと、新しめの pandas では警告またはエラーになる- MultiIndex が読みにくい — 集計結果を「次の処理に渡す」場合は
reset_index()を当てて通常の DataFrame に戻す groupbyの結果は遅延評価 —df.groupby("都市")単体ではまだ計算されていない。.mean()や.agg(...)を呼んで初めて結果が出るpivot_tableで欠損 — 該当する組み合わせが元データにないと、結果セルがNaNになる。fill_value=0などで埋められる
サンプルコード
- 題材データ:income.csv をダウンロード(名前・年齢・都市・性別・収入の 5 列)
演習
income.csv を題材に、次を行うコードを Colab で書きなさい。
income.csvを読み込み、groupbyで 都市ごとの平均収入 を計算するpivot_tableで 都市 × 性別の平均収入 を集計する(行=都市、列=性別)- 上の結果を
matplotlibの棒グラフで可視化する(都市別に男女の収入を並べた棒グラフ)
可視化のステップでは、reset_index() を呼ぶ/呼ばないで描けるか・描けないかが変わります。両方試して結果を比べると、本文の「整形してから可視化に渡す」の意味が体感できます。
発展課題(オプション)
agg() を使って、都市別に「平均年齢・平均収入・人数」を 1 つの表にまとめて出力する。出力された表を読みやすく整形(列名のリネーム、小数点桁数の調整など)してみる。