📚 背景知識(読んでから問題へ)
groupby は 「グループ別に計算する」 操作です。Excel のピボットテーブルを Python で再現するようなイメージです。「性別ごとの生存率」「チケットクラスごとの平均運賃」など、Kaggle の EDA で最も頻繁に使われるパターンです。
groupby の 3 ステップ(Split → Apply → Combine)
主な集計関数
| 関数 | 意味 | 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 | 予測対象 — 平均を取ると生存率になる |
| index | Name | Sex | Pclass | Age | Fare | Survived |
|---|---|---|---|---|---|---|
| 0 | Alice | F | 1 | 22 | 71.28 | 1 |
| 1 | Bob | M | 3 | 35 | 7.25 | 0 |
| 2 | Carol | F | 2 | 28 | 13.00 | 1 |
| 3 | Dave | M | 3 | 45 | 8.05 | 0 |
| 4 | Eve | F | 1 | 19 | 71.00 | 1 |
| 5 | Frank | M | 2 | 30 | 21.00 | 0 |
| 6 | Grace | F | 2 | 26 | 13.50 | 1 |
青行 = Female(F)、オレンジ行 = Male(M)。groupby("Sex") でこの色単位にまとめられます。
📝 問題
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() を使って性別ごとの生存率を求めてください。
期待される出力: Female = 1.0(100%)、Male = 0.0(0%)
問2 — チケットクラスごとの平均運賃
Pclass でグループ化し、Fare の平均・最大・最小を 一度に 計算してください(.agg() を使う)。
.agg(["mean", "max", "min"]) で3つの集計を同時に取得できます。期待: Pclass=1 の平均 ≈ 71.14
問3 — 複数列でのグループ化
Sex と Pclass の組み合わせでグループ化し、各グループの平均年齢を求めてください。
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ごとの運賃統計(出力イメージ)
| Pclass | mean(平均) | 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 ステップが走っています:
- Split:
groupby("Sex")で Sex が同じ行を 1 グループにまとめる(F グループ: 行 0/2/4/6、M グループ: 行 1/3/5) - Apply:
["Survived"].mean()を各グループに適用(F: (1+1+1+1)/4 = 1.0、M: (0+0+0)/3 = 0.0) - 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の結果)
生存率バー(視覚確認)
🔲 ピボットテーブル(問4の結果)
pd.pivot_table() で生成されるクロス集計表の出力イメージ:
| Pclass → Sex ↓ |
1(一等) | 2(二等) | 3(三等) |
|---|---|---|---|
| F(女性) | 1.0 | 1.0 | 0(データなし→0) |
| M(男性) | 0(データなし→0) | 0.0 | 0.0 |
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_table で fill_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()関数