Day 041 — pandas入門(groupby・集計・ピボット)

2026-05-21 白 / Phase 1 コーディング pandas入門

📚 背景知識(読んでから問題へ)

groupby「グループ別に計算する」 操作です。Excel のピボットテーブルを Python で再現するようなイメージです。「性別ごとの生存率」「チケットクラスごとの平均運賃」など、Kaggle の EDA で最も頻繁に使われるパターンです。

groupby の 3 ステップ(Split → Apply → Combine)

1. Split(分割)
groupby("Sex")
2. Apply(集計)
.mean()
3. Combine(結合)
Series / DataFrame

主な集計関数

関数意味Kaggle使用頻度
.mean()平均★★★ 最頻出
.sum()合計★★★
.count()件数(NaN除く)★★☆
.max() / .min()最大/最小★★☆
.agg(["mean","std"])複数同時集計★★☆
.transform()グループ内変換(行数保持)★★☆ 上級

🗂️ データスキーマ

架空のタイタニック号風データ(乗客7名分)を使用します。

列名説明値の範囲備考
Name object 乗客名 識別子(集計には使わない)
Sex object 性別 "F" / "M" groupby キーとして使用
Pclass int64 チケットクラス 1(一等) / 2 / 3 groupby キーとして使用
Age int64 年齢 19 〜 45 集計対象(平均年齢など)
Fare float64 運賃 7.25 〜 71.28 集計対象(平均・最大・最小)
Survived int64 生存フラグ(0/1) 0 / 1 予測対象 — 平均を取ると生存率になる
indexNameSexPclassAgeFareSurvived
0AliceF12271.281
1BobM3357.250
2CarolF22813.001
3DaveM3458.050
4EveF11971.001
5FrankM23021.000
6GraceF22613.501

青行 = Female(F)、オレンジ行 = Male(M)。groupby("Sex") でこの色単位にまとめられます。

📝 問題

📌
シナリオ: 架空のタイタニック号風データで、groupby を中心とした集計操作を実装してください。4つの小問に分かれています。
import pandas as pd
import numpy as np

data = {
    "Name":     ["Alice", "Bob", "Carol", "Dave", "Eve", "Frank", "Grace"],
    "Sex":      ["F", "M", "F", "M", "F", "M", "F"],
    "Pclass":   [1, 3, 2, 3, 1, 2, 2],
    "Age":      [22, 35, 28, 45, 19, 30, 26],
    "Fare":     [71.28, 7.25, 13.00, 8.05, 71.00, 21.00, 13.50],
    "Survived": [1, 0, 1, 0, 1, 0, 1],
}
df = pd.DataFrame(data)

問1 — 性別ごとの生存率

groupby("Sex")["Survived"].mean() を使って性別ごとの生存率を求めてください。

💡
Survived 列は 0/1 なので、平均を取ると自動的に「生存率」になります。
期待される出力: Female = 1.0(100%)、Male = 0.0(0%)

問2 — チケットクラスごとの平均運賃

Pclass でグループ化し、Fare の平均・最大・最小を 一度に 計算してください(.agg() を使う)。

💡
.agg(["mean", "max", "min"]) で3つの集計を同時に取得できます。
期待: Pclass=1 の平均 ≈ 71.14

問3 — 複数列でのグループ化

SexPclass の組み合わせでグループ化し、各グループの平均年齢を求めてください。

💡
groupby() にリスト ["Sex", "Pclass"] を渡します。
結果の index は 2 階層(MultiIndex)になります。

問4 — ピボットテーブル

pd.pivot_table() を使って、行に Sex、列に Pclass、値に Survived(平均)を並べた表を作ってください。

💡
pd.pivot_table(df, values="Survived", index="Sex", columns="Pclass", aggfunc="mean", fill_value=0)
完成するとクロス集計表(Excel のピボットテーブルと同じ形)になります。

🔍 ヒント(段階的開示)

ヒント1 — 方向性
  • 問1: groupby → 列指定 → mean() の組み合わせ
  • 問2: .agg(["mean", "max", "min"]) で複数集計を一行で
  • 問3: groupby() の引数をリスト [...] にする
  • 問4: pd.pivot_table() は4つの引数(values/index/columns/aggfunc)が必要
ヒント2 — アプローチ
# 問1
df.groupby("Sex")["Survived"].mean()

# 問2
df.groupby("Pclass")["Fare"].agg(["mean", "max", "min"])

# 問3
df.groupby(["Sex", "Pclass"])["Age"].mean()

# 問4
pd.pivot_table(df, values="Survived", index="Sex",
               columns="Pclass", aggfunc="mean", fill_value=0)
ヒント3 — コード骨格(ほぼ答え)
# 問1
survival_by_sex = df.groupby(___)[___].___()

# 問2
fare_stats = df.groupby(___)[___].agg([___, ___, ___])

# 問3
age_by_group = df.groupby([___, ___])[___].___()

# 問4
pivot = pd.pivot_table(df, values=___, index=___, columns=___, aggfunc=___)

模範解答

import pandas as pd
import numpy as np

data = {
    "Name":     ["Alice", "Bob", "Carol", "Dave", "Eve", "Frank", "Grace"],
    "Sex":      ["F", "M", "F", "M", "F", "M", "F"],
    "Pclass":   [1, 3, 2, 3, 1, 2, 2],
    "Age":      [22, 35, 28, 45, 19, 30, 26],
    "Fare":     [71.28, 7.25, 13.00, 8.05, 71.00, 21.00, 13.50],
    "Survived": [1, 0, 1, 0, 1, 0, 1],
}
df = pd.DataFrame(data)

# 問1: 性別ごとの生存率
survival_by_sex = df.groupby("Sex")["Survived"].mean()
print("性別ごとの生存率:")
print(survival_by_sex)
# Sex
# F    1.0
# M    0.0

# 問2: Pclassごとの運賃統計
fare_stats = df.groupby("Pclass")["Fare"].agg(["mean", "max", "min"])
print("\nチケットクラスごとの運賃統計:")
print(fare_stats)
# Pclass      mean    max    min
# 1       71.140  71.28  71.00
# 2       15.833  21.00  13.00
# 3        7.650   8.05   7.25

# 問3: 複数列グループ化
age_by_group = df.groupby(["Sex", "Pclass"])["Age"].mean()
print("\n性別×クラスごとの平均年齢:")
print(age_by_group)

# 問4: ピボットテーブル
pivot = pd.pivot_table(
    df,
    values="Survived",
    index="Sex",
    columns="Pclass",
    aggfunc="mean",
    fill_value=0
)
print("\nピボットテーブル(生存率):")
print(pivot)

Pclassごとの運賃統計(出力イメージ)

Pclassmean(平均)max(最大)min(最小)
1(一等) 71.14 71.28 71.00
2(二等) 15.83 21.00 13.00
3(三等) 7.65 8.05 7.25

🪜 Step-by-Step 解説

Step 1: groupby の「分割→集計」メカニズム

df.groupby("Sex")["Survived"].mean()

内部では以下の 3 ステップが走っています:

  1. Split: groupby("Sex") で Sex が同じ行を 1 グループにまとめる(F グループ: 行 0/2/4/6、M グループ: 行 1/3/5)
  2. Apply: ["Survived"].mean() を各グループに適用(F: (1+1+1+1)/4 = 1.0、M: (0+0+0)/3 = 0.0)
  3. Combine: 結果を Series として返す
⚠️
df.groupby("Sex") 単体では何も計算されません。GroupByオブジェクトという中間状態なので、必ず後ろに .mean() などを繋げてください。

Step 2: .agg() で複数集計を一度に

df.groupby("Pclass")["Fare"].agg(["mean", "max", "min"])

.mean() を 3 回書く代わりに、agg() にリストを渡すと DataFrame 形式で複数の集計結果が一度に得られます。Kaggle の EDA ノートブックで頻出のパターンです。

Step 3: 複数列 groupby はリストで

df.groupby(["Sex", "Pclass"])["Age"].mean()

groupby() にリスト ["Sex", "Pclass"] を渡すと、組み合わせ(F×1, F×2, M×2, M×3 など)でグループが作られます。結果の index は MultiIndex(2 階層)になります。

Step 4: pivot_table でクロス集計表

pd.pivot_table(df, values="Survived", index="Sex",
               columns="Pclass", aggfunc="mean", fill_value=0)
引数意味今回の値
values集計する列"Survived"
index行ラベルにする列"Sex"
columns列ラベルにする列"Pclass"
aggfunc集計方法"mean"
fill_value組み合わせがない場合の値0

📊 生存率グラフ(問1の結果)

groupby("Sex")["Survived"].mean() の結果 1.0 (100%) 0.5 (50%) 0.0 (0%) 1.0 Female (F) 4名全員生存 0.0 Male (M) 3名全員死亡

生存率バー(視覚確認)

Female (F)
100%
Male (M)
0%

🔲 ピボットテーブル(問4の結果)

pd.pivot_table() で生成されるクロス集計表の出力イメージ:

Pclass →
Sex ↓
1(一等) 2(二等) 3(三等)
F(女性) 1.0 1.0 0(データなし→0)
M(男性) 0(データなし→0) 0.0 0.0
💡
このデータでは F×3(女性の三等客)と M×1(男性の一等客)の組み合わせが存在しないため fill_value=0 で 0 が埋め込まれています。実際の Titanic データではすべてのマスに値が入ります。

🔢 数学・統計の補足(文系向け)

「0/1 データの平均 = 割合」の原理

Survived 列は 0(死亡)か 1(生存)の 2 値です。女性 4 名のデータが [1, 1, 1, 1] なので:

(1 + 1 + 1 + 1) / 4 = 4 / 4 = 1.0

「生存した人数 / 全人数」と同じ計算になります。これは 0/1 データ(二値データ)の重要な性質で、平均 = 割合(比率) になります。

MultiIndex(2 階層インデックス)のイメージ

groupby(["Sex", "Pclass"]) の結果の index は入れ子になっています:

F → 1 : 22.0(Alice の年齢)
F → 2 : 27.0(Carol と Grace の平均年齢)
M → 2 : 30.0(Frank の年齢)
M → 3 : 40.0(Bob と Dave の平均年齢)

Excel の「セルの結合」を使った集計表のようなイメージです。

🏆 Kaggleでの実践的な使い方

操作Kaggle場面具体例
groupby + mean() target の分布分析 各カテゴリの生存率・不正率を確認
groupby + agg() EDA の統計サマリー クラスごとの運賃の平均・分散を一覧化
groupby + transform() 特徴量エンジニアリング グループ内で標準化した特徴量を作る
pivot_table 特徴量の交互作用確認 性別×クラスのクロス集計でパターン発見

特徴量エンジニアリングへの応用

# グループ統計を元のDataFrameに結合する実践的パターン
fare_mean_by_class = train.groupby("Pclass")["Fare"].mean()
train["FareMeanByPclass"] = train["Pclass"].map(fare_mean_by_class)

# グループ内での正規化(transform を使う)
train["FareZscoreByPclass"] = train.groupby("Pclass")["Fare"].transform(
    lambda x: (x - x.mean()) / x.std()
)
💡
transform()mean() と違い、行数を保ったまま各行に集計値を割り当てます。特徴量エンジニアリングで頻出のパターンです(Day 041 以降に詳しく扱います)。

⚠️ よくある誤解・ミス

誤解・ミスなぜ起こるか正しい理解
df.groupby("Sex") だけで表示しようとする 計算済みだと思い込む GroupByオブジェクトは中間状態。.mean() 等を後に繋ぐ必要がある
複数列 groupby で括弧を忘れる(groupby("Sex", "Pclass") 引数を 2 つ渡す感覚 groupby(["Sex", "Pclass"])1 つのリスト を渡す
agg("mean")agg(["mean"]) の違いが不明 文字列 vs リストの違い 文字列 → Series、リスト → DataFrame。複数集計はリストで
pivot_tablefill_value を忘れる 該当データがない組み合わせに気づかない fill_value=0 で NaN を 0 に置換。モデルに NaN を渡すとエラーになる
MultiIndex の扱い方がわからない 2 階層 index に慣れていない .reset_index() で通常の DataFrame に変換できる

🚀 次のステップ

  • 発展1: groupby + transform() でグループ内の正規化・ランキング
  • 発展2: df.merge() / pd.concat() でテーブルを結合する
  • 次回予告: pandas 続編 — loc / iloc を使った高度なインデックス操作と apply() 関数

📋 自己評価(解いた後に記入)

自分の回答・気づき・メモ: