Day 079 — Kaggle ノートブックの読み方 — 上位ノートブック精読の手順

2026-06-29 水 / Phase 3 コーディング Kaggle戦略 / 特徴量エンジニアリング / 正規表現 / Titanic

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

📓
Kaggle コンペでは「自分でゼロから考える」よりも、公開されている上位ノートブックを素早く読み解き、自分の解法に組み込むスキルが非常に重要です。上位 Kaggler ほど、このサイクルが速いです。

🏅 公開ノートブックの品質基準

🥇
金メダル (100+ votes)
ほぼ必読。上位手法 or 完璧な EDA が含まれる。コンペ参加直後に精読する。
🥈
銀メダル (30-100 votes)
良質なアイデアが含まれる。EDA or 特定のモデル手法に特化していることが多い。
🥉
銅メダル (10-30 votes)
特定テクニックに特化。タイトルを見て関連するものだけ読む。
📄
メダルなし (10未満 votes)
玉石混交。Public LB スコアを確認して取捨選択する。

🔍 上位ノートブックの読み方(5ステップ)

1
スコアと votes を確認
フォーク数・投票数・LB スコアを見て、読む価値があるかを 30 秒で判断する
2
EDA セクションを読む
データの分布・欠損値パターン・目的変数の特徴など、自分が気づかなかった洞察を探す
3
特徴量エンジニアリングを抽出
どんな特徴量を作っているか、なぜその変換をするかに着目する
4
モデルと CV スコアを確認
どのモデルを使い、CV はどう設計しているかを把握する
5
自分のコードに組み込む(再実装)
直接コピーではなく、手法の理解をもとに自分のコードとして書き直す

🎯 問題

📝
シナリオ: Titanic コンペの上位ノートブックから抜粋したコードを読み解いてください。
import pandas as pd
import numpy as np

train = pd.read_csv('train.csv')
test  = pd.read_csv('test.csv')

# ─── 特徴量エンジニアリング ───

# (A) 敬称の抽出
for df in [train, test]:
    df['Title'] = df['Name'].str.extract(r',\s*([^\.]+)\.')
    df['Title'] = df['Title'].replace(['Lady', 'Countess', 'Capt', 'Col', 'Don',
                                        'Dr', 'Major', 'Rev', 'Sir', 'Jonkheer', 'Dona'],
                                       'Rare')
    df['Title'] = df['Title'].map({'Mr': 0, 'Miss': 1, 'Mrs': 2, 'Master': 3, 'Rare': 4})
    df['Title'] = df['Title'].fillna(0)

# (B) 家族サイズの作成
for df in [train, test]:
    df['FamilySize'] = df['SibSp'] + df['Parch'] + 1
    df['IsAlone'] = (df['FamilySize'] == 1).astype(int)

# (C) 年齢の欠損値補完
for df in [train, test]:
    age_median = df.groupby(['Pclass', 'Title'])['Age'].transform('median')
    df['Age'] = df['Age'].fillna(age_median)

# (D) 運賃のbin化
for df in [train, test]:
    df['FareBin'] = pd.qcut(df['Fare'].fillna(df['Fare'].median()), q=4, labels=False)

問題1: 正規表現 r',\s*([^\.]+)\.' を解説する

'Mr. John Smith' から何を取り出しているのか、正規表現の各要素を説明してください。

問題2: より良い年齢補完方法を実装する

コード (C) の補完より精度が高い、Sex も考慮した補完方法をコードで示してください。

問題3: FamilyGroup を作成する関数を実装する

FamilySize から FamilyGroup(1人=Alone, 2-4人=Small, 5人以上=Large)を作る関数を完成させてください。

def make_family_group(df):
    """FamilySize を3カテゴリに変換する"""
    # ここを実装してください
    pass

for df in [train, test]:
    make_family_group(df)

💡 ヒント

ヒント1 — 方向性
正規表現の ([^\.]+) は「ドット以外の文字が1文字以上」を捕捉するグループです。Titanic の Name 列は「Smith, Mr. John」という形式なので、カンマの後の「Mr」を取り出しています。
ヒント2 — アプローチ
  • 問題2: groupby(['Pclass', 'Title']) の代わりに groupby(['Pclass', 'Sex']) を使うと、より細かく年齢の中央値を計算できる
  • 問題3: np.select() を使うと条件分岐を簡潔に書ける
ヒント3 — コード骨格
# 問題2(Sexも考慮)
for df in [train, test]:
    age_median = df.groupby(['Pclass', 'Sex'])['Age'].transform('median')
    df['Age'] = df['Age'].fillna(age_median)

# 問題3
def make_family_group(df):
    conditions = [
        df['FamilySize'] == 1,
        (df['FamilySize'] >= 2) & (df['FamilySize'] <= 4),
        df['FamilySize'] >= 5
    ]
    choices = ['Alone', 'Small', 'Large']
    df['FamilyGroup'] = np.select(conditions, choices, default='Alone')
    return df

解答1: 正規表現の解説

r',\s*([^\.]+)\.'
, カンマ \s* 0個以上のスペース ( [^\.] + ) キャプチャ: ドット以外を1文字以上 \. ドット
パターン要素意味マッチ例
, カンマにそのままマッチ "Braund, Mr. John"
\s* 0個以上のスペース(\s=空白、*=0以上) ", Mr." のスペース
([^\.]+ ) キャプチャグループ。[^\.]=ドット以外の文字、+=1文字以上 "Mr."Mr を取り出す
\. ドット(. はエスケープ必要) "Mr." のドット
動作確認コード 実行可能
import re

test_names = [
    "Braund, Mr. Owen Harris",
    "Heikkinen, Miss. Laina",
    "Allen, Mrs. William Henry",
    "Palsson, Master. Gosta Leonard"
]

for name in test_names:
    match = re.search(r',\s*([^\.]+)\.', name)
    print(f"{name:<40} → Title: {match.group(1) if match else None}")

# 出力:
# Braund, Mr. Owen Harris          → Title: Mr
# Heikkinen, Miss. Laina           → Title: Miss
# Allen, Mrs. William Henry        → Title: Mrs
# Palsson, Master. Gosta Leonard   → Title: Master
💡
なぜ Title が有効な特徴量か: Title は性別・年齢・社会的地位と強く相関します。「Master」(未成年男子)は生存率が高く、「Mr」(成人男子)は最も低い。Sex 単独より細かい情報を提供します。

解答2: より良い年齢補完

年齢補完の粒度比較 — なぜグループ別が良いか 方法1: 全体中央値 全員 → 中央値28歳 1等客男性もも3等客子供も 全員28歳になる → 不正確 方法3: Pclass×Sex 別中央値 1等・男性 → 40歳 3等・女性 → 22歳 2等・男性 → 30歳 グループ内で「典型的な年齢」を使えるため より現実に近い補完が可能
改善版: Pclass × Sex の中央値で補完 推奨パターン
# ─── 方法3: Pclass × Sex の中央値で補完(改善版)───
for df in [train, test]:
    age_med = df.groupby(['Pclass', 'Sex'])['Age'].transform('median')
    df['Age'] = df['Age'].fillna(age_med)

# ─── 方法4: 3段階フォールバック(さらに丁寧)───
for df in [train, test]:
    # まず細粒度 Pclass×Sex×Title で補完
    age_fine   = df.groupby(['Pclass', 'Sex', 'Title'])['Age'].transform('median')
    # 残った NaN を Pclass×Sex で補完
    age_coarse = df.groupby(['Pclass', 'Sex'])['Age'].transform('median')
    # それでも残った NaN を全体中央値で補完
    df['Age'] = (df['Age']
                 .fillna(age_fine)
                 .fillna(age_coarse)
                 .fillna(df['Age'].median()))

# グループ別の年齢中央値を確認
print(train.groupby(['Pclass', 'Sex'])['Age'].median())
# Pclass  Sex   
# 1       female    35.0
# 1       male      40.0
# 2       female    28.0
# 2       male      30.0
# 3       female    21.5
# 3       male      25.0

解答3: FamilyGroup の作成

make_family_group — np.select 使用 実装パターン
import numpy as np

def make_family_group(df):
    """FamilySize を3カテゴリに変換する"""
    conditions = [
        df['FamilySize'] == 1,                                    # Alone
        (df['FamilySize'] >= 2) & (df['FamilySize'] <= 4),       # Small
        df['FamilySize'] >= 5                                       # Large
    ]
    choices = ['Alone', 'Small', 'Large']
    df['FamilyGroup'] = np.select(conditions, choices, default='Alone')
    return df

# train, test に適用
for df in [train, test]:
    make_family_group(df)

# 分布確認
print(train['FamilyGroup'].value_counts())

# グループ別生存率確認(なぜこの分け方が有効か)
print("\n--- FamilyGroup 別生存率 ---")
print(train.groupby('FamilyGroup')['Survived'].mean().sort_values(ascending=False))
# Small  0.58  ← 小家族は互いに助け合えて生存率高
# Alone  0.30  ← 孤独な旅行者は低め
# Large  0.16  ← 大家族は脱出困難で最も低い

🪜 Step-by-Step: ノートブック活用サイクル

1
コンペ参加直後(Gold ノートブックを2〜3件精読)
Notebooks タブ → Votes 降順 → 上位ノートブックを精読する
2
EDA ノートブックで洞察を吸収
データの隠れた構造・ドメイン知識を学ぶ
3
ベースラインをフォークして改良
動くコードをフォークし、特徴量を追加・変更しながら CV スコアを改善する
4
Discussion で追加ヒントを収集
「重要な特徴量」「リーク情報」スレッドを探す
5
コンペ終了後: 上位 Writeup を精読
上位者の考え方・手法を次のコンペへの学習として記録する

🛠️ よくある特徴量エンジニアリングパターン

パターン1: 文字列から情報抽出
Name列から敬称・長さを抽出。正規表現を活用。
パターン2: 組み合わせ特徴量
SibSp + Parch + 1 = FamilySize。合計・積・比率で新特徴量を作成。
パターン3: Bin化(離散化)
連続値をカテゴリに変換。pd.cut / pd.qcut を使用。
パターン4: 交互作用特徴量
Pclass + Sex の組み合わせを文字列連結で新しいカテゴリを作成。
パターン5: グループ集計
チケット番号ごとの人数など、groupby で集計した値を特徴量として追加。
パターン6: 欠損値を特徴量化
欠損しているかどうか自体が情報になる場合がある(Cabin の有無など)。
# 6パターンの実装例(Titanic)
for df in [train, test]:
    # パターン1: 文字列から抽出
    df['NameLength'] = df['Name'].str.len()

    # パターン2: 組み合わせ
    df['FamilySize'] = df['SibSp'] + df['Parch'] + 1

    # パターン3: Bin化
    df['AgeBin'] = pd.cut(df['Age'], bins=[0, 12, 18, 35, 60, 100],
                          labels=['Child', 'Teen', 'Adult', 'Middle', 'Senior'])

    # パターン4: 交互作用
    df['Pclass_Sex'] = df['Pclass'].astype(str) + '_' + df['Sex']

    # パターン5: グループ集計
    df['Ticket_Count'] = df.groupby('Ticket')['Ticket'].transform('count')

    # パターン6: 欠損値を特徴量化
    df['Has_Cabin'] = df['Cabin'].notna().astype(int)  # Cabin がある = 裕福 → 生存率高

📊 Titanic 生存率の可視化(特徴量の有効性確認)

Titanic — 主要特徴量別の生存率 生存率 (%) 100% 75% 50% 25% 74% 女性 19% 男性 63% 1等 47% 2等 24% 3等 58% Small 30% Alone 16% Large Sex Pclass FamilyGroup

Sex > Pclass > FamilyGroup の順で生存率への影響が大きい。特徴量の有効性をこの視点で確認する。

生存率の差 = 特徴量の有効性

Sex: 女性 (74%) - 男性 (19%)
差: 55pt
55pt差
Pclass: 1等 (63%) - 3等 (24%)
差: 39pt
39pt差
FamilyGroup: Small (58%) - Large (16%)
差: 42pt
42pt差

グループ間の差が大きいほど、その特徴量はモデルにとって有益。

📋 Titanic で有効な特徴量(上位ノートブックより)

特徴量重要度作り方なぜ有効か
Sex 最高 そのまま or Label Encoding 女性優先避難で生存率が大きく違う
Pclass そのまま(序数) 客室の位置・脱出しやすさと相関
Title Name から正規表現で抽出 性別・年齢・社会的地位を内包
FamilySize SibSp + Parch + 1 家族人数と生存率が非線形関係
IsAlone FamilySize == 1 単独旅行者の生存率が低い
AgeBin Age を区間に分割 子供(Master)は優先避難
Has_Cabin Cabin.notna().astype(int) Cabin 記録あり = 裕福 → 生存率高
Ticket_Count 低〜中 groupby('Ticket').transform('count') 同チケットグループ → 家族構成の代理

よくある誤解・ミス

誤解・ミスなぜ起こるか正しい理解
ノートブックをそのままコピーして提出 手間を省こうとする 理解なしのコピーはスキルが身につかない。手法を学んで再実装する
votes が多ければ正しいと思い込む 人気 = 正確と誤解 コンペ固有の問題がある場合も。必ず CV スコアで自分の環境で検証する
EDA ノートブックを飛ばす コーディングを急ぐ EDA は特徴量のアイデア源。飛ばすと重要な洞察を見逃す
test データで transform を再計算する データリークを意識しない test に train の統計量を適用しないと情報漏洩。train で計算した値を test にマッピングする
特徴量を増やすほど良いと思う 多い = 良いという直感 不要な特徴量はノイズになる。CV スコアで取捨選択する

🚀 次のステップ

  • 発展: クロスバリデーションの設計(Stratified K-Fold / Group K-Fold の使い分け)
  • 実践: Titanic コンペの上位ノートブックを1件精読し、自分のコードに1つ手法を追加してみる
  • 次回予告 (Day 080): CV の設計戦略 — Titanic で Stratified K-Fold を使った正確なモデル評価

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

← Day 078: Kaggleのコンペ構造 📋 一覧へ戻る