LESSON

データ前処理

「EDAで見えた課題をデータ前処理で解決する段階だ。」

田中VPoEが前処理パイプラインの図を描く。

「前処理はMLパイプラインの土台。ここを雑にやると、どんなに良いモデルを使っても性能は出ない。型変換、欠損値処理、エンコーディング、スケーリング。一つずつ確実にやろう。」

前処理パイプラインの全体像

前処理の流れ:
1. 型変換        → TotalChargesをfloatに変換
2. 欠損値処理    → 11件の欠損を処理
3. 不要列の削除  → customerIDの除外
4. エンコーディング → カテゴリカル変数の数値化
5. スケーリング  → 数値変数の正規化
6. データ分割    → 訓練/検証/テストの分割

Step 1: 型変換

import pandas as pd
import numpy as np

df = pd.read_csv('WA_Fn-UseC_-Telco-Customer-Churn.csv')

# TotalChargesの型変換
df['TotalCharges'] = pd.to_numeric(df['TotalCharges'], errors='coerce')
print(f"TotalCharges欠損数: {df['TotalCharges'].isnull().sum()}")
# → 11件の欠損

# 欠損の正体を確認
print(df[df['TotalCharges'].isnull()][['tenure', 'MonthlyCharges', 'TotalCharges']])
# → すべてtenure=0の新規顧客

Step 2: 欠損値処理

# 方法1: tenure=0の顧客はTotalCharges=0として補完
df['TotalCharges'] = df['TotalCharges'].fillna(0)

# 方法2: 削除(11件なので影響は軽微)
# df = df.dropna(subset=['TotalCharges'])

print(f"処理後の欠損: {df.isnull().sum().sum()}")

欠損値処理の判断基準

状況推奨手法理由
欠損が少数(< 1%)削除 or 補完影響が軽微
意味のある欠損ドメイン知識で補完tenure=0→TotalCharges=0
ランダム欠損中央値/平均値で補完分布を崩さない
大量欠損(> 30%)列の削除を検討情報量が少ない

Step 3: 不要列の削除と目的変数の変換

# customerIDは予測に不要
df = df.drop('customerID', axis=1)

# 目的変数を数値化
df['Churn'] = (df['Churn'] == 'Yes').astype(int)

# SeniorCitizenは既に0/1
print(f"SeniorCitizenの値: {df['SeniorCitizen'].unique()}")

Step 4: カテゴリカル変数のエンコーディング

方法の選択

エンコーディング適用ケースメリットデメリット
Label Encoding順序あり2値シンプル順序のない多値には不適切
One-Hot Encoding順序なしカテゴリ関係を正確に表現次元が増える
Target Encoding高カーディナリティ次元を増やさないリーケージのリスク
from sklearn.preprocessing import LabelEncoder

# 2値変数のラベルエンコーディング
binary_cols = ['gender', 'Partner', 'Dependents', 'PhoneService',
               'PaperlessBilling']
le = LabelEncoder()
for col in binary_cols:
    df[col] = le.fit_transform(df[col])

# 多値変数のOne-Hotエンコーディング
multi_cols = ['MultipleLines', 'InternetService', 'OnlineSecurity',
              'OnlineBackup', 'DeviceProtection', 'TechSupport',
              'StreamingTV', 'StreamingMovies', 'Contract', 'PaymentMethod']

df = pd.get_dummies(df, columns=multi_cols, drop_first=True)

print(f"エンコーディング後のカラム数: {df.shape[1]}")

”No internet service” の扱い

# 方法1: そのままOne-Hot(上記の方法)
# 方法2: "No"に統合してからエンコーディング
internet_dependent = ['OnlineSecurity', 'OnlineBackup', 'DeviceProtection',
                      'TechSupport', 'StreamingTV', 'StreamingMovies']

for col in internet_dependent:
    df[col] = df[col].replace('No internet service', 'No')

Step 5: スケーリング

from sklearn.preprocessing import StandardScaler

# 数値変数のスケーリング
numeric_cols = ['tenure', 'MonthlyCharges', 'TotalCharges']

scaler = StandardScaler()
df[numeric_cols] = scaler.fit_transform(df[numeric_cols])

print(f"スケーリング後の統計量:")
print(df[numeric_cols].describe().round(2))

スケーリング手法の比較

手法変換式適用ケース
StandardScaler(x - mean) / std正規分布に近いデータ
MinMaxScaler(x - min) / (max - min)範囲を[0,1]に制限したい場合
RobustScaler(x - median) / IQR外れ値が多いデータ

注意: 決定木ベースのモデル(XGBoost, LightGBM)はスケーリング不要。ロジスティック回帰やSVMでは必須。

Step 6: データ分割

from sklearn.model_selection import train_test_split

X = df.drop('Churn', axis=1)
y = df['Churn']

# 訓練:検証:テスト = 60:20:20
X_train, X_temp, y_train, y_temp = train_test_split(
    X, y, test_size=0.4, random_state=42, stratify=y
)
X_val, X_test, y_val, y_test = train_test_split(
    X_temp, y_temp, test_size=0.5, random_state=42, stratify=y_temp
)

print(f"訓練: {X_train.shape[0]}件 (離反率: {y_train.mean():.3f})")
print(f"検証: {X_val.shape[0]}件 (離反率: {y_val.mean():.3f})")
print(f"テスト: {X_test.shape[0]}件 (離反率: {y_test.mean():.3f})")

前処理パイプラインのまとめ

from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer

# 再現可能なパイプラインの構築
numeric_transformer = Pipeline(steps=[
    ('scaler', StandardScaler())
])

preprocessor = ColumnTransformer(transformers=[
    ('num', numeric_transformer, numeric_cols)
], remainder='passthrough')

# パイプラインの保存
import joblib
joblib.dump(preprocessor, 'preprocessor.pkl')
joblib.dump(scaler, 'scaler.pkl')

まとめ

項目ポイント
型変換TotalChargesをfloatに変換(11件欠損発生)
欠損値tenure=0の新規顧客 → TotalCharges=0で補完
エンコーディング2値はLabel、多値はOne-Hot
スケーリングStandardScaler(ロジスティック回帰用)
データ分割60:20
、stratifyで層化抽出

チェックリスト

  • TotalChargesの型変換と欠損値処理ができる
  • カテゴリカル変数のエンコーディング手法を選択できる
  • 適切なスケーリング手法を選択できる
  • stratifyを使った層化抽出の分割ができる
  • scikit-learnのPipelineを構築できる

次のステップへ

データの前処理が完了した。次は演習で、ここまで学んだEDAと前処理を実際のデータに対して実践してみよう。

推定読了時間: 30分