データ前処理
「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分