コンテンツにスキップ

単元 9:集計とクロス集計

  • groupby で平均・合計・件数を集計できる
  • agg() で複数の集計関数を同時に適用できる
  • pivot_table でクロス集計表を作れる
  • 集計結果を reset_index() で整形し、可視化に渡せる

グループごとに要約したい場面

Section titled “グループごとに要約したい場面”

データ全体の平均や合計を出すことは、実はそれほど多くありません。実務でも研究でも、興味があるのは 「カテゴリごとの平均」「グループごとの合計」 といった 要約 です:

  • 都市ごとの平均年齢
  • 性別ごとの平均収入
  • 部署ごとの売上合計
  • カテゴリごとの件数

pandas はこの「グループ別の要約」を 2 つの仕組みで支援します。groupbypivot_table です。両者は内部的に近い処理をしますが、得意な「見せ方」が違うので使い分けます。

groupby の動作は 「分割 → 集計 → 結合」(split-apply-combine)の 3 段階です:

# 都市ごとの平均年齢・平均収入
df.groupby("都市").mean(numeric_only=True)
  1. 分割(split) — 「都市」列の値ごとにデータを部分集合に分ける
  2. 集計(apply) — 各部分集合に対して mean() を実行
  3. 結合(combine) — 結果を 1 つの DataFrame にまとめて返す

結果は「都市」が行インデックスになった DataFrame です。numeric_only=True文字列の列を集計対象から外す ための指示。これを付けないと「『東京』と『大阪』の平均」のような意味不明な計算を pandas が試みてエラーになることがあります。

「都市 × 性別」のように 2 つ以上のキーでグループ化 したい場合は、列名をリストで渡します:

df.groupby(["都市", "性別"]).sum(numeric_only=True)

結果の行インデックスは MultiIndex(階層インデックス) になります。東京・男性 東京・女性 大阪・男性 … のように 2 段に積み上がる構造です。「人が見て分かる」というよりは「次の処理に渡す中間データ」として有用な形です。

agg() で複数の集計関数を同時に

Section titled “agg() で複数の集計関数を同時に”

「平均と合計と件数を一度に欲しい」場合は agg() を使います。列ごとに別々の集計関数を指定したり、複数の関数を同時に当てたりできます:

df.groupby("都市").agg({
"年齢": "mean",
"収入": ["mean", "sum", "count"],
})
  • 文字列で関数名を渡す("mean", "sum", "count" ほか)
  • リストで渡せば複数同時

最初は読みづらく感じますが、慣れると 1 回の groupby で必要な統計を全部出す のに便利で、コードも短くまとまります。

「都市」を行、「性別」を列、セルに「平均収入」を入れた 2 次元の表が欲しいとき。これは クロス集計 と呼ばれる古典的な要約形式です。pivot_table がこれを直接作ってくれます:

pd.pivot_table(
df,
index="都市", # 行
columns="性別", # 列
values="収入", # 集計するセルの値
aggfunc="mean", # 集計関数(既定は mean)
)

groupby(["都市", "性別"]).mean() でも同じ計算自体は可能ですが、結果が MultiIndex で縦に積み上がる行 × 列の表として横に広がる かが違います。

  • 「人に見せる表」を作るpivot_table
  • 「次の処理に渡す中間データ」groupby

と覚えると使い分けやすくなります。

groupbypivot_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 を題材に、次を行うコードを Colab で書きなさい。

  1. income.csv を読み込み、groupby都市ごとの平均収入 を計算する
  2. pivot_table都市 × 性別の平均収入 を集計する(行=都市、列=性別)
  3. 上の結果を matplotlib の棒グラフで可視化する(都市別に男女の収入を並べた棒グラフ)

可視化のステップでは、reset_index() を呼ぶ/呼ばないで描けるか・描けないかが変わります。両方試して結果を比べると、本文の「整形してから可視化に渡す」の意味が体感できます。

agg() を使って、都市別に「平均年齢・平均収入・人数」を 1 つの表にまとめて出力する。出力された表を読みやすく整形(列名のリネーム、小数点桁数の調整など)してみる。