LESSON

演習:Credit Card Fraudデータを分析しよう

「理論は十分だ。実際のデータで手を動かして、不均衡データの感覚を身につけてくれ。」

田中VPoEがKaggleのページを開く。

「まずはEDAから始めて、サンプリング手法の効果を実験的に確かめよう。」

ミッション概要

Kaggle Credit Card Fraud Detectionデータセットを使い、EDA、サンプリング手法の比較、評価指標の実践を行う。


Mission 1: EDA(30分)

データセットの全体像を把握し、不正取引の特徴を発見する。

タスク:

  1. データの基本統計量を確認し、欠損値の有無を報告する
  2. クラス分布を確認し、不均衡比率を計算する
  3. 正常/不正それぞれの取引金額(Amount)の分布を可視化し、特徴を述べる
  4. 時間帯(Time)ごとの不正取引件数を可視化し、パターンを分析する
  5. V1〜V28のうち、正常/不正で分布が大きく異なる特徴量を3つ以上特定する
解答例
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

df = pd.read_csv('creditcard.csv')

# 1. 基本統計量
print(df.describe())
print(f"\n欠損値:\n{df.isnull().sum().sum()}")  # 0件

# 2. クラス分布
print(f"\nクラス分布:\n{df['Class'].value_counts()}")
ratio = df['Class'].value_counts()[0] / df['Class'].value_counts()[1]
print(f"不均衡比率: 1:{ratio:.0f}")

# 3. 取引金額の分布
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
df[df['Class'] == 0]['Amount'].hist(bins=50, ax=axes[0], alpha=0.7)
axes[0].set_title('正常取引の金額分布')
axes[0].set_xlim(0, 500)
df[df['Class'] == 1]['Amount'].hist(bins=50, ax=axes[1], alpha=0.7, color='red')
axes[1].set_title('不正取引の金額分布')
plt.tight_layout()
plt.show()

# 特徴: 不正取引は比較的小額に集中(平均122ドル vs 正常88ドル)
# ただし、高額の不正取引も存在

# 4. 時間帯分析
df['Hour'] = (df['Time'] / 3600).astype(int) % 24
fraud_by_hour = df[df['Class'] == 1].groupby('Hour').size()
fraud_by_hour.plot(kind='bar', color='crimson')
plt.title('時間帯別不正取引件数')
plt.show()

# 5. 特徴量の分布比較
from scipy import stats

significant_features = []
for col in [f'V{i}' for i in range(1, 29)]:
    stat, p_value = stats.mannwhitneyu(
        df[df['Class'] == 0][col],
        df[df['Class'] == 1][col]
    )
    if p_value < 0.001:
        effect_size = abs(
            df[df['Class'] == 0][col].mean() - df[df['Class'] == 1][col].mean()
        )
        significant_features.append((col, effect_size, p_value))

significant_features.sort(key=lambda x: x[1], reverse=True)
print("分布が大きく異なる特徴量(効果量上位):")
for feat, effect, p in significant_features[:5]:
    print(f"  {feat}: 効果量={effect:.3f}, p値={p:.2e}")
# 典型的にはV14, V12, V10, V17等が有意

Mission 2: サンプリング手法の比較(30分)

複数のサンプリング手法を適用し、ベースラインモデル(ロジスティック回帰)での性能を比較する。

タスク:

  1. データを学習/テストに分割する(8
    、層化分割)
  2. 以下の5つの条件でロジスティック回帰を学習し、テストデータで評価する
    • サンプリングなし
    • ランダムオーバーサンプリング
    • SMOTE
    • ランダムアンダーサンプリング
    • SMOTE + Tomek Links
  3. 各条件でPR-AUC、F1-Score、Recall、Precisionを計算する
  4. 結果を比較し、最も効果的なサンプリング手法を選定する
解答例
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import (
    average_precision_score, f1_score,
    recall_score, precision_score
)
from imblearn.over_sampling import RandomOverSampler, SMOTE
from imblearn.under_sampling import RandomUnderSampler
from imblearn.combine import SMOTETomek

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

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42, stratify=y
)

scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

samplers = {
    'なし': None,
    'Random Over': RandomOverSampler(random_state=42),
    'SMOTE': SMOTE(random_state=42),
    'Random Under': RandomUnderSampler(random_state=42),
    'SMOTE+Tomek': SMOTETomek(random_state=42),
}

results = []
for name, sampler in samplers.items():
    if sampler:
        X_res, y_res = sampler.fit_resample(X_train_scaled, y_train)
    else:
        X_res, y_res = X_train_scaled, y_train

    model = LogisticRegression(max_iter=1000, random_state=42)
    model.fit(X_res, y_res)

    y_prob = model.predict_proba(X_test_scaled)[:, 1]
    y_pred = model.predict(X_test_scaled)

    results.append({
        'Method': name,
        'PR-AUC': average_precision_score(y_test, y_prob),
        'F1': f1_score(y_test, y_pred),
        'Recall': recall_score(y_test, y_pred),
        'Precision': precision_score(y_test, y_pred),
    })

results_df = pd.DataFrame(results)
print(results_df.to_string(index=False))

# SMOTEがPR-AUCとF1のバランスが良い傾向
# Random Underはデータ量が減るため性能が不安定

Mission 3: 評価指標の実践(30分)

最もPR-AUCが高かったモデルを使い、評価指標を深掘りする。

タスク:

  1. PR曲線とROC曲線を並べて可視化する
  2. 閾値を0.1刻みで変化させ、Recall/Precision/F1の変化をプロットする
  3. ビジネスコスト関数(FN=50,000円、FP=500円)を定義し、コスト最小化閾値を求める
  4. F1最適化閾値とコスト最適化閾値の違いを分析し、どちらを採用すべきか提案する
解答例
from sklearn.metrics import (
    precision_recall_curve, roc_curve,
    average_precision_score, roc_auc_score
)

# 最良モデルの予測確率を使用(例: SMOTE + LR)
y_prob = best_model_prob

# 1. PR曲線とROC曲線
fig, axes = plt.subplots(1, 2, figsize=(12, 5))

precision, recall, pr_thresholds = precision_recall_curve(y_test, y_prob)
axes[0].plot(recall, precision)
axes[0].set_title(f'PR Curve (AUC={average_precision_score(y_test, y_prob):.4f})')
axes[0].set_xlabel('Recall')
axes[0].set_ylabel('Precision')

fpr, tpr, roc_thresholds = roc_curve(y_test, y_prob)
axes[1].plot(fpr, tpr)
axes[1].plot([0, 1], [0, 1], 'r--')
axes[1].set_title(f'ROC Curve (AUC={roc_auc_score(y_test, y_prob):.4f})')
axes[1].set_xlabel('FPR')
axes[1].set_ylabel('TPR')
plt.tight_layout()
plt.show()

# 2-3. 閾値探索
thresholds = np.arange(0.01, 1.0, 0.01)
metrics = {'threshold': [], 'recall': [], 'precision': [],
           'f1': [], 'cost': []}

for t in thresholds:
    y_pred_t = (y_prob >= t).astype(int)
    fn = ((y_test == 1) & (y_pred_t == 0)).sum()
    fp = ((y_test == 0) & (y_pred_t == 1)).sum()

    metrics['threshold'].append(t)
    metrics['recall'].append(recall_score(y_test, y_pred_t))
    metrics['precision'].append(precision_score(y_test, y_pred_t, zero_division=0))
    metrics['f1'].append(f1_score(y_test, y_pred_t))
    metrics['cost'].append(fn * 50000 + fp * 500)

metrics_df = pd.DataFrame(metrics)

# F1最適化閾値
best_f1_idx = metrics_df['f1'].idxmax()
best_f1_threshold = metrics_df.loc[best_f1_idx, 'threshold']

# コスト最適化閾値
best_cost_idx = metrics_df['cost'].idxmin()
best_cost_threshold = metrics_df.loc[best_cost_idx, 'threshold']

print(f"F1最適化閾値: {best_f1_threshold:.2f}")
print(f"コスト最適化閾値: {best_cost_threshold:.2f}")

# 4. 分析
# コスト最適化閾値の方がF1閾値より低い傾向
# → 見逃しコストが高いため、Recall重視の閾値が選ばれる
# → ビジネス判断としてはコスト最適化閾値を採用すべき

達成度チェック

  • データのEDAを実施し、不正取引の特徴を3つ以上発見できた
  • 5つのサンプリング条件で学習・評価を実施できた
  • サンプリング手法間の性能差を定量的に比較できた
  • PR曲線とROC曲線を正しく可視化できた
  • ビジネスコスト最小化閾値を算出できた
  • F1閾値とコスト閾値の違いを分析・説明できた

推定所要時間: 90分