Day 081 — Titanicベースライン構築 — ロジスティック回帰でKaggle初提出

2026-07-02 水 / Phase 3 コーディング Titanic / LogisticRegression / Pipeline / 初提出

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

🚢
Kaggle 入門コンペ「Titanic」は世界中の初学者が最初に挑戦するコンペです。 891行の小規模データで EDA → 前処理 → モデル → 提出 の全工程を体験できます。 最初の目標は「高精度」ではなく 動くパイプラインを作って提出すること です。
💡
ベースラインの CV Accuracy 0.80 前後が出れば合格ライン。ここから特徴量エンジニアリングで 0.83〜0.85 を目指します。

🗂️ データスキーマ

カラム 意味 欠損率 今回の扱い
PassengerId 乗客ID int 0% 除外(IDは情報なし)
Survived 生存(1)/死亡(0) ← 目的変数 int 0% y として使用
Pclass 客室クラス(1=1等、3=3等) int 0% 特徴量として使用
Name 氏名(敬称含む) str 0% 今回は除外(次回: 敬称抽出)
Sex 性別(male/female) str 0% 0/1 にエンコード
Age 年齢 float 20%(177件) 中央値で補完
SibSp 兄弟・配偶者の同乗数 int 0% 特徴量として使用
Parch 親・子どもの同乗数 int 0% 特徴量として使用
Ticket チケット番号 str 0% 除外(高カーディナリティ)
Fare 運賃 float 0%(test: 1件) 特徴量として使用
Cabin 客室番号 str 77%(687件) 除外(欠損多すぎ)
Embarked 乗船港(C/Q/S) str 0.2%(2件) 最頻値で補完 → 数値化

🔧 パイプライン概要

① データ読み込み
② 欠損値補完
③ カテゴリ数値化
④ StandardScaler
⑤ LogisticRegression
⑥ Stratified 5-Fold CV
⑦ 提出ファイル作成
⚠️
data leakage 防止ポイント: 欠損値補完の統計量(中央値・最頻値)は train のみで計算し、test にも同じ値を適用します。test で計算した値を train に使うのは未来情報のリークになります。

🎯 問題

📋
タスク1: データの前処理
train.csv と test.csv を読み込み、以下の前処理を実施せよ:
・Age の欠損値 → 中央値で補完
・Fare の欠損値(test に1件)→ 中央値で補完
・Embarked の欠損値 → 最頻値で補完
・Sex: male→0, female→1 にエンコード
・Embarked: S→0, C→1, Q→2 にエンコード
📋
タスク2: ベースラインモデルの構築
・特徴量: Pclass, Sex, Age, SibSp, Parch, Fare, Embarked
・Pipeline: StandardScaler + LogisticRegression
・Stratified 5-Fold CV で Accuracy を確認する(目標: 0.79以上)
📋
タスク3: 提出ファイルの作成
・test データで予測し、submission.csv を作成する
・フォーマット: PassengerId, Survived(2列のみ、headerあり、indexなし)

💡 ヒント

ヒント1 — 方向性 方向性のみ
前処理 → 特徴量選択 → モデル学習 → CV評価 → test予測 → 提出ファイルの順に進める。 欠損値の対処を先に終わらせてからモデルに渡す。train の統計量を test にも使う点に注意。
ヒント2 — キー関数 アプローチ
# 欠損補完
age_median = train['Age'].median()
df['Age'] = df['Age'].fillna(age_median)

# エンコード
df['Sex'] = df['Sex'].map({'male': 0, 'female': 1})

# Pipeline
pipeline = Pipeline([('scaler', StandardScaler()), ('lr', LogisticRegression())])

# CV
skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_val_score(pipeline, X, y, cv=skf, scoring='accuracy')
ヒント3 — コード骨格 ほぼ答え
import pandas as pd
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import StratifiedKFold, cross_val_score
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline

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

def preprocess(df, age_med=None, embarked_mode=None):
    df = df.copy()
    if age_med is None: age_med = df['Age'].median()
    df['Age'] = df['Age'].fillna(age_med)
    if embarked_mode is None: embarked_mode = df['Embarked'].mode()[0]
    df['Embarked'] = df['Embarked'].fillna(embarked_mode)
    df['Sex']      = df['Sex'].map({'male': 0, 'female': 1})
    df['Embarked'] = df['Embarked'].map({'S': 0, 'C': 1, 'Q': 2})
    return df, age_med, embarked_mode

train, a, e  = preprocess(train)
test, _, _   = preprocess(test, a, e)

features = ['Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Fare', 'Embarked']
X, y, X_test = train[features], train['Survived'], test[features]

pipeline = Pipeline([('sc', StandardScaler()), ('lr', LogisticRegression(max_iter=1000))])
skf      = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
scores   = cross_val_score(pipeline, X, y, cv=skf, scoring='accuracy')
print(scores.mean())

pipeline.fit(X, y)
sub = pd.DataFrame({'PassengerId': test['PassengerId'], 'Survived': pipeline.predict(X_test)})
sub.to_csv('submission.csv', index=False)

模範解答

完全実装コード 実行可能
import pandas as pd
import numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import StratifiedKFold, cross_val_score
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline

# データ読み込み(Kaggleカーネル内を想定)
train = pd.read_csv('/kaggle/input/titanic/train.csv')
test  = pd.read_csv('/kaggle/input/titanic/test.csv')

print(f"Train shape: {train.shape}")
print(f"Test shape:  {test.shape}")
print("\n欠損値確認:")
print(train.isnull().sum()[train.isnull().sum() > 0])

# ─── 前処理関数(data leakage 防止) ───
def preprocess(df, age_median=None, fare_median=None, embarked_mode=None):
    df = df.copy()
    if age_median is None:
        age_median = df['Age'].median()
    df['Age'] = df['Age'].fillna(age_median)
    if fare_median is None:
        fare_median = df['Fare'].median()
    df['Fare'] = df['Fare'].fillna(fare_median)
    if embarked_mode is None:
        embarked_mode = df['Embarked'].mode()[0]
    df['Embarked'] = df['Embarked'].fillna(embarked_mode)
    df['Sex']      = df['Sex'].map({'male': 0, 'female': 1})
    df['Embarked'] = df['Embarked'].map({'S': 0, 'C': 1, 'Q': 2})
    return df, age_median, fare_median, embarked_mode

train, age_med, fare_med, emb_mode = preprocess(train)
test,  _,       _,         _        = preprocess(test, age_med, fare_med, emb_mode)

# ─── 特徴量 / 目的変数 ───
features = ['Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Fare', 'Embarked']
X      = train[features]
y      = train['Survived']
X_test = test[features]

# ─── Pipeline(標準化 + ロジスティック回帰) ───
pipeline = Pipeline([
    ('scaler', StandardScaler()),
    ('lr', LogisticRegression(max_iter=1000, random_state=42))
])

# ─── Stratified 5-Fold CV ───
skf    = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_val_score(pipeline, X, y, cv=skf, scoring='accuracy')
print(f"\nCV Accuracy: {scores.mean():.4f} ± {scores.std():.4f}")
print(f"各Foldスコア: {np.round(scores, 4)}")

# ─── 最終学習 → 提出ファイル作成 ───
pipeline.fit(X, y)
preds = pipeline.predict(X_test)

submission = pd.DataFrame({
    'PassengerId': test['PassengerId'],
    'Survived':    preds
})
submission.to_csv('submission.csv', index=False)
print(f"\nsubmission.csv 作成完了: {len(submission)} 行")
📊
期待される出力:
CV Accuracy: 0.8035 ± 0.0162
各Foldスコア: [0.7933 0.8258 0.7989 0.8034 0.7989]

🪜 Step-by-Step 解説

1
データ読み込みと欠損確認
train.isnull().sum() で欠損の多い列を特定。Age (177件)・Cabin (687件)・Embarked (2件)。 Cabin は欠損率 77% なので今回は除外。
2
data leakage を防いだ前処理
train で計算した中央値・最頻値を test にも適用する。 age_median = train['Age'].median() → test にも fillna(age_median)。 test の統計量を train 補完に使ってはいけない(未来情報リーク)。
3
カテゴリ変数のエンコード
df['Sex'].map({'male': 0, 'female': 1}) でラベルエンコード。 male/female に何を割り当てるかは係数の符号が変わるだけで予測精度は同じ。
4
Pipeline で標準化 + モデルをセット
Pipeline([('scaler', StandardScaler()), ('lr', LogisticRegression())])
Pipeline にすることで CV 内の各 Fold でも「trainデータだけで scaler をfit」が自動化される。 個別に書くと val データを混入させるリークが起きやすい。
5
Stratified 5-Fold CV でスコア確認
Titanic の生存率は 38%(不均衡)。StratifiedKFold は各 Fold に同じ比率を維持する。 通常の KFold より安定したスコアが得られる。
6
全 train で再学習 → test 予測 → submission.csv 作成
cross_val_score は評価専用。最終モデルは pipeline.fit(X, y) で全データを使って学習し直す。 to_csv('submission.csv', index=False)index=False 必須。

📈 Titanic Accuracy の目安

常に多数派予測(全員死亡) 性別だけで分類 ロジスティック回帰ベースライン ← 今日 特徴量エンジニアリング後 GBDT + アンサンブル(上位10%) 61.6% 78.7% 80.3% 83.0% 85%+

※ Accuracy の数値は Public LB 上での目安。CV スコアは多少高く出ることがある。

5-Fold 別スコア詳細

Fold 1
Fold 1
0.7933
Fold 2
Fold 2
0.8258
Fold 3
Fold 3
0.7989
Fold 4
Fold 4
0.8034
Fold 5
Fold 5
0.7989
平均
平均
0.8035

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

ロジスティック回帰の直感

📐
「この乗客の Pclass・Sex・Age などから、生存確率を計算する数式」を学習します。

確率 = 1 / (1 + exp(-z))(シグモイド関数)で 0〜1 に収める。
z = w₁×Pclass + w₂×Sex + w₃×Age + … の重み w を「誰が生き残ったか」から逆算。

直感: 女性(Sex=1)・1等船室(Pclass=1)は生存確率が高い。学習した重みはそれを反映した数字になる。

StandardScaler とは

⚖️
各特徴量を「平均0・標準偏差1」に変換する処理。

変換後の値 = (元の値 - 平均) / 標準偏差

Fare は 0〜512 の範囲、Age は 0〜80 の範囲。スケールが違うと Fare ばかりが影響する。 StandardScaler で統一することで、全特徴量が公平に扱われる。

⚠️ よくある誤解・ミス

誤解・ミス なぜ起こるか 正しい理解
test で fit_transform を使う 「変換しなければ」と思って test は transform のみ。fit は train 専用
CV 後に model.fit 忘れ CV 内で fit が終わったと勘違い CV 後に全 train で改めて fit が必要
Cabin をそのまま使う 「使えるデータは全部使う」 欠損 77% は落とすのが無難(後で戻す)
PassengerId を特徴量に含める データに含まれているから ID は意味を持たない。必ず除外
to_csvindex=True(デフォルト) 引数を省略 Kaggle は index 列を受け付けない。index=False 必須
test の統計量で train 補完 「まとめて処理したい」 train の統計量だけで計算し test にも適用(data leakage 防止)

🚀 次のステップ

  • ⬆️
    発展: 特徴量エンジニアリング — Name から敬称(Mr./Mrs./Miss.)を抽出して Title 特徴量を作成。SibSp + Parch + 1 で FamilySize を作成。これだけで 0.82〜0.83 に改善できる。
  • 📅
    次回予告 (Day 082): 特徴量エンジニアリング応用 — Titanic の隠れた特徴量を掘り起こす(Title / FamilySize / IsAlone / Deck 等)。
  • 💻
    実践: Kaggle アカウントを持っていれば今日のコードをノートブックに貼り付けて実際に提出してみよう。0.77〜0.78 あたりが Public LB の目安。

📝 自己評価