LESSON

特徴量エンジニアリング

「モデルの比較ができた。次は特徴量エンジニアリングだ。」

田中VPoEが力を込めて言う。

「Kaggleのトップ層が口を揃えて言うことがある。『モデルの選択よりも特徴量エンジニアリングの方が遥かに重要だ』と。業務知識をデータに変換する技術を身につけよう。」

特徴量エンジニアリングの原則

特徴量エンジニアリングの3つのソース:
1. ドメイン知識 → 業務の専門知識から特徴量を生成
2. 統計的分析  → EDAで発見したパターンを特徴量化
3. 自動生成    → 交互作用項、多項式特徴量の自動生成

ドメイン知識に基づく特徴量

サービス加入数

import pandas as pd
import numpy as np

df = pd.read_csv('WA_Fn-UseC_-Telco-Customer-Churn.csv')
df['TotalCharges'] = pd.to_numeric(df['TotalCharges'], errors='coerce').fillna(0)

# サービス加入数(ロックイン効果の指標)
service_cols = ['OnlineSecurity', 'OnlineBackup', 'DeviceProtection',
                'TechSupport', 'StreamingTV', 'StreamingMovies']
df['num_services'] = df[service_cols].apply(
    lambda row: (row == 'Yes').sum(), axis=1
)

# 離反率との関係を確認
print(df.groupby('num_services')['Churn'].apply(
    lambda x: (x == 'Yes').mean()
))

月額料金とtenureの交互作用

# 月額料金あたりの利用月数(コスパ感の指標)
df['charge_per_tenure'] = np.where(
    df['tenure'] > 0,
    df['MonthlyCharges'] / df['tenure'],
    df['MonthlyCharges']
)

# 総支払額と月額の比率(実質利用期間の指標)
df['total_monthly_ratio'] = np.where(
    df['MonthlyCharges'] > 0,
    df['TotalCharges'] / df['MonthlyCharges'],
    0
)

契約の安定性スコア

# 契約安定性スコア(複数指標の組み合わせ)
def contract_stability_score(row):
    score = 0
    # 長期契約 +2
    if row['Contract'] == 'Two year':
        score += 2
    elif row['Contract'] == 'One year':
        score += 1
    # 自動引き落とし +1
    if row['PaymentMethod'] in ['Bank transfer (automatic)', 'Credit card (automatic)']:
        score += 1
    # ペーパーレスでない +1(離反率が低い傾向)
    if row['PaperlessBilling'] == 'No':
        score += 1
    return score

df['stability_score'] = df.apply(contract_stability_score, axis=1)

tenureグループ

# tenureをビニング(顧客ライフサイクルステージ)
df['tenure_group'] = pd.cut(
    df['tenure'],
    bins=[-1, 6, 12, 24, 48, 72],
    labels=['0-6m', '7-12m', '13-24m', '25-48m', '49-72m']
)

# 新規顧客フラグ
df['is_new_customer'] = (df['tenure'] <= 6).astype(int)

# 長期顧客フラグ
df['is_loyal_customer'] = (df['tenure'] >= 48).astype(int)

交互作用特徴量

# 高リスクの組み合わせを特徴量化
# EDAで発見した高リスクパターン
df['high_risk_combo'] = (
    (df['Contract'] == 'Month-to-month') &
    (df['InternetService'] == 'Fiber optic') &
    (df['tenure'] <= 12)
).astype(int)

# Fiber optic × セキュリティなし
df['fiber_no_security'] = (
    (df['InternetService'] == 'Fiber optic') &
    (df['OnlineSecurity'] == 'No')
).astype(int)

# 単身 × 短期契約
df['single_monthly'] = (
    (df['Partner'] == 'No') &
    (df['Dependents'] == 'No') &
    (df['Contract'] == 'Month-to-month')
).astype(int)

月額料金の相対指標

# 同じインターネットサービスタイプ内での料金順位
df['charge_rank_in_service'] = df.groupby('InternetService')['MonthlyCharges'].rank(pct=True)

# 平均との差分
avg_charge = df.groupby('InternetService')['MonthlyCharges'].transform('mean')
df['charge_diff_from_avg'] = df['MonthlyCharges'] - avg_charge

特徴量の評価

from sklearn.feature_selection import mutual_info_classif

# 数値化した新特徴量の情報利得を計算
new_features = ['num_services', 'charge_per_tenure', 'total_monthly_ratio',
                'stability_score', 'is_new_customer', 'is_loyal_customer',
                'high_risk_combo', 'fiber_no_security', 'single_monthly',
                'charge_rank_in_service', 'charge_diff_from_avg']

y = (df['Churn'] == 'Yes').astype(int)
X_new = df[new_features].fillna(0)

mi_scores = mutual_info_classif(X_new, y, random_state=42)
mi_df = pd.DataFrame({
    'feature': new_features,
    'mutual_info': mi_scores
}).sort_values('mutual_info', ascending=False)

print("新特徴量の情報利得:")
print(mi_df.to_string(index=False))

特徴量エンジニアリングの効果測定

# ベースライン(元の特徴量のみ)vs 拡張特徴量
from xgboost import XGBClassifier
from sklearn.metrics import roc_auc_score

# 元の特徴量でのスコア
model_base = XGBClassifier(n_estimators=100, random_state=42, use_label_encoder=False, eval_metric='auc')
model_base.fit(X_train_original, y_train)
score_base = roc_auc_score(y_val, model_base.predict_proba(X_val_original)[:, 1])

# 拡張特徴量でのスコア
model_enhanced = XGBClassifier(n_estimators=100, random_state=42, use_label_encoder=False, eval_metric='auc')
model_enhanced.fit(X_train_enhanced, y_train)
score_enhanced = roc_auc_score(y_val, model_enhanced.predict_proba(X_val_enhanced)[:, 1])

print(f"ベースライン AUC-ROC: {score_base:.4f}")
print(f"拡張特徴量 AUC-ROC:  {score_enhanced:.4f}")
print(f"改善幅: +{score_enhanced - score_base:.4f}")

まとめ

項目ポイント
ドメイン特徴量num_services, stability_score
交互作用high_risk_combo, fiber_no_security
ビニングtenure_group, is_new_customer
相対指標charge_rank_in_service, charge_diff_from_avg
評価方法情報利得(Mutual Information)で有効性を確認

チェックリスト

  • ドメイン知識から3つ以上の特徴量を生成できる
  • 交互作用特徴量を作成できる
  • 特徴量の有効性を情報利得で評価できる
  • 特徴量追加前後の性能改善を測定できる

次のステップへ

特徴量エンジニアリングで性能が向上した。次はハイパーパラメータチューニングで、モデルの性能をさらに引き出そう。

推定読了時間: 30分