// PART 02 · ARTICLE 06 / 07
DATA-PREP PITFALLS L2

データリーケージを防ぐ前処理

— train で fit、test には適用のみ

10 min read· L2· 2026.05.20 update· by Editor

データリーケージは、予測モデル研究で見落とされやすい落とし穴です。 特に、標準化、欠測補完、カテゴリ変数処理、特徴量選択は、 何気なく全データで実行すると、評価データの情報が学習側に混ざります。 本記事では、前処理を Pipeline と ColumnTransformer に入れ、 train で fit し、test には transform だけを適用する考え方を整理します。

// CONTEXT

前処理は、モデルに入る前の準備作業です。 しかし、その準備段階で評価データを見てしまうと、 モデル性能が過大評価されます。 これは、リハ研究の表形式データでも頻繁に起こります。

// 01 · LEARN OUTCOMESこの記事で学ぶこと

  • 前処理がデータリーケージの発生源になりやすい理由を説明できます。
  • fit と transform の違いを、train/test 分割と関連づけて理解できます。
  • Pipeline と ColumnTransformer による安全な前処理実装を確認できます。
  • 同一患者の複数評価や多施設データで、GroupKFold を使う意味を整理できます。

// 02 · CONCLUSION結論

リーケージは、悪意がなくても起こります。 たとえば、全データの平均と標準偏差で StandardScaler を fit してから、 train/test に分けるだけでも問題になります。 test の分布を使って train を整えたことになるためです。

重要なのは、前処理を「解析前の一括作業」と考えないことです。 前処理も、モデル学習の一部です。 そのため、学習データだけで規則を作り、 評価データにはその規則を適用するだけにします。

// 03 · FIGURE図で見る NG と OK

// NG PATTERN vs OK PATTERN NG PATTERN 全データで fit してから split All data (train + test 混在) imputer / scaler / select .fit(all_data) ← test 情報も学習 train test model.fit evaluate ⚠ LEAKAGE AUC が高く見えるが 外部検証で崩れる OK PATTERN split 後、fold 内だけで fit All data train fold valid / test preprocessor .fit(train) → transform .transform(valid) model.fit evaluate ✓ SAFE 評価データの情報が 前処理器に入らない CV ループでは、各 fold の train 部分だけで前処理器を fit し、valid/test には transform のみを適用する。
Fig 1. NG pattern vs OK pattern. NG は全データで補完・標準化・選択を fit してから split する。 OK は split 後に、各 train fold 内だけで fit し、validation/test には transform のみを行う。

図1では、前処理の順番を比較します。 NG パターンでは、最初に全データで欠測補完や標準化を行います。 その後に train/test に分割すると、test 側の情報が前処理器に入ります。 これが、典型的な前処理リーケージです。

OK パターンでは、最初にデータを分割します。 さらに CV では、各 fold の train 部分だけで前処理器を fit します。 validation fold や test set には、その前処理器を transform として使います。 この順番にすると、評価データの情報を学習側に入れにくくなります。

// PIPELINE + COLUMNTRANSFORMER Pipeline 入力データ train fold X (DataFrame) ColumnTransformer num 年齢, FIM, SIAS, … SimpleImputer(median) → StandardScaler cat 性別, 病型, 施設 ID, … SimpleImputer(most_freq) → OneHotEncoder text (optional) 主訴・所見など TfidfVectorizer / Tokenizer → 各前処理は train fold 内だけで .fit() MODEL Logistic / RF / GBM / NN cross_val_score(pipe, X, y, cv=k) は fold ごとに pipe.fit(X_train_fold) を呼び、valid には transform だけを適用する。
Fig 2. Pipeline + ColumnTransformer の構造。数値列、カテゴリ列、テキスト列を分岐し、 各前処理を fold 内で fit してからモデルへ渡す。

図2では、ColumnTransformer の役割を示します。 数値列には欠測補完と標準化を行います。 カテゴリ列には欠測補完と One-Hot Encoding を行います。 テキスト列がある場合は、別の前処理に分けられます。 それらを Pipeline に入れることで、CV の各 fold 内で処理できます。

// 04 · CLINICALリハ研究で起こる場面

// CASE 1 · 脳卒中コホート N=300

退院時歩行自立を予測する研究を考えます。 入院時 FIM、SIAS、年齢、発症から入院までの日数を使います。 全データで StandardScaler を fit してから CV すると、 validation 側の平均と分散が train 側に混ざります。 その結果、AUC が高く見える場合があります。

// CASE 2 · 多施設データの Target Encoding

施設 ID をそのまま One-Hot にすると、列数が増えます。 Target Encoding を使う選択肢もあります。 ただし、施設ごとの転帰率を全データで計算すると、 validation fold のアウトカム情報を使うことになります。 out-of-fold の形で、fold 内だけで計算します。

// CASE 3 · 欠測補完 + 標準化 + One-Hot

回復期病棟のデータでは、握力や SIAS に欠測が混ざります。 性別、病型、退院前生活場所などのカテゴリ列もあります。 これらを pandas で先に一括処理すると、 評価データの情報を使いやすくなります。 ColumnTransformer に入れると、列ごとに安全に分岐できます。

// CASE 4 · 歩行加速度の集約特徴量

同一患者から複数日の加速度データを得る研究では、 患者単位の平均、最大値、変動係数などを作ります。 test 患者の値を train 側の集約に含めると、 患者情報の混入になります。 同一患者の複数評価がある場合は、GroupKFold を使います。

// 05 · THEORYfit と transform の考え方

リーケージは、予測時点で使えない情報が、 説明変数や前処理の規則に混ざることです。 たとえば、入院時に退院時 FIM や入院期間は分かりません。 それらを説明変数に入れると、時点のリーケージになります。

前処理でも同じことが起こります。 標準化では、平均と標準偏差を推定します。 欠測補完では、中央値や最頻値を推定します。 これらの値を test set も含めて推定すると、 test set の情報で train set を整えることになります。

観測時点 t で使える情報: X_t
観測時点 t より後の情報: X_future, Y_future

安全な予測モデル:
  model uses X_t only

リーケージがある予測モデル:
  model or preprocessing uses X_future or Y_future

scikit-learn では、fit と transform を分けて考えます。 fit は、前処理器が規則を学習する段階です。 transform は、その規則をデータに適用する段階です。

fit(X_train):
  parameters = phi(X_train)

transform(X_new):
  X_new_transformed = apply(parameters, X_new)

fit_transform(X_train):
  parameters = phi(X_train)
  X_train_transformed = apply(parameters, X_train)

CV では、この処理を fold ごとに繰り返します。 cross_val_score(pipe, X, y, cv=k) は、 内部で pipe.fit(X_train_fold) を fold ごとに呼びます。 Pipeline に前処理を入れておけば、各 fold の train 部分だけで fit されます。

for each fold:
  X_train_fold, X_valid_fold = split(X)
  preprocessing.fit(X_train_fold)
  X_train_ready = preprocessing.transform(X_train_fold)
  X_valid_ready = preprocessing.transform(X_valid_fold)
  model.fit(X_train_ready, y_train_fold)
  evaluate(model, X_valid_ready, y_valid_fold)

ここで重要なのは、validation fold に対して fit しないことです。 validation fold は、未知データの代わりです。 未知データの分布を使って前処理を作ると、 本来より評価が甘くなります。

// 06 · IMPLEMENTATIONPython 実装例

以下は、教育用の仮想データ example_rehab_dataset.csv を想定した例です。 数値列には中央値補完と標準化を行います。 カテゴリ列には最頻値補完と One-Hot Encoding を行います。 それらを ColumnTransformer で結合し、LogisticRegression へ渡します。

# 教育用の仮想データを想定します。
# 実データでは、個人情報保護、倫理審査、施設ルール、
# 共同研究契約、データ利用規約を確認してから解析します。

import pandas as pd
import numpy as np

from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import roc_auc_score
from sklearn.model_selection import (
    StratifiedKFold,
    GroupKFold,
    cross_val_score,
    train_test_split,
)
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder, StandardScaler

RANDOM_STATE = 42

# 例: 入院時情報から退院時歩行自立を予測する仮想データ
df = pd.read_csv("example_rehab_dataset.csv")

# 予測時点で取得できない列は、説明変数から除外します。
# discharge_fim_total, length_of_stay, followup_6m_fim などは、
# 入院時予測モデルでは未来情報になる可能性があります。
target = "walking_independent_discharge"
leaky_columns = [
    "discharge_fim_total",
    "discharge_fim_motor",
    "length_of_stay",
    "followup_6m_fim",
]

X = df.drop(columns=[target] + leaky_columns, errors="ignore")
y = df[target]

numeric_features = [
    "age",
    "days_from_onset",
    "admission_fim_total",
    "admission_fim_motor",
    "admission_fim_cognition",
    "sias_lower_limb",
    "grip_strength",
]

categorical_features = [
    "sex",
    "stroke_type",
    "facility_id",
    "prehospital_living_place",
]

numeric_features = [c for c in numeric_features if c in X.columns]
categorical_features = [c for c in categorical_features if c in X.columns]

numeric_pipe = Pipeline(
    steps=[
        ("imputer", SimpleImputer(strategy="median")),
        ("scaler", StandardScaler()),
    ]
)

categorical_pipe = Pipeline(
    steps=[
        ("imputer", SimpleImputer(strategy="most_frequent")),
        ("onehot", OneHotEncoder(handle_unknown="ignore")),
    ]
)

preprocess = ColumnTransformer(
    transformers=[
        ("num", numeric_pipe, numeric_features),
        ("cat", categorical_pipe, categorical_features),
    ],
    remainder="drop",
)

pipe = Pipeline(
    steps=[
        ("preprocess", preprocess),
        ("model", LogisticRegression(max_iter=2000, class_weight="balanced")),
    ]
)

cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=RANDOM_STATE)
auc_scores = cross_val_score(pipe, X, y, cv=cv, scoring="roc_auc")
print("CV AUC:", np.round(auc_scores, 3))
print("Mean AUC:", round(auc_scores.mean(), 3))

# 最終評価用の hold-out test を使う場合も、fit は train のみです。
X_train, X_test, y_train, y_test = train_test_split(
    X,
    y,
    test_size=0.2,
    stratify=y,
    random_state=RANDOM_STATE,
)

pipe.fit(X_train, y_train)
y_pred = pipe.predict_proba(X_test)[:, 1]
print("Hold-out AUC:", round(roc_auc_score(y_test, y_pred), 3))

# 同一患者の複数評価がある場合は、患者単位で分割します。
if "patient_id" in df.columns:
    groups = df["patient_id"]
    group_cv = GroupKFold(n_splits=5)
    group_scores = cross_val_score(
        pipe,
        X,
        y,
        cv=group_cv,
        groups=groups,
        scoring="roc_auc",
    )
    print("GroupKFold AUC:", np.round(group_scores, 3))

# NG 例: 以下は全データで前処理を fit してから分割しています。
# from sklearn.preprocessing import StandardScaler
# from sklearn.impute import SimpleImputer
# imputer = SimpleImputer(strategy="median")
# scaler = StandardScaler()
# X_num = imputer.fit_transform(X[numeric_features])
# X_num = scaler.fit_transform(X_num)
# X_train, X_test, y_train, y_test = train_test_split(X_num, y)
# この書き方では test 側の分布が補完器と scaler に混入します。

この実装では、前処理器とモデルが 1 つの Pipeline になっています。 そのため、cross_val_score の各 fold で、 補完器、標準化器、One-Hot Encoder が train fold のみで fit されます。 validation fold には transform のみが適用されます。

実務では、Target Encoding、特徴量選択、PCA、欠測フラグ作成なども、 同じ考え方で Pipeline 内に入れます。 pandas で一括処理したい場面もありますが、 評価データを含む処理になっていないかを確認します。

// 07 · MYTHSよくある誤解

誤解 1:Pipeline は便利だが、なくても同じ
手動で fit と transform を管理すると、どこかで評価データを混ぜやすくなります。 Pipeline は、前処理とモデルを 1 つの学習手順として扱うための仕組みです。
誤解 2:train で fit すれば、CV では再 fit しなくてよい
CV では、各 fold の train 部分だけで fit します。 外側の train 全体で fit した前処理器を、内側の validation に使うと、 validation の独立性が弱くなります。
誤解 3:pandas で前処理してから sklearn に渡すのが自然
pandas は探索や集計には便利です。 しかし、全データに対する一括処理になりやすい面があります。 モデル評価に関わる前処理は、Pipeline 化を検討します。
誤解 4:Target Encoding は少しのリーケージなら問題になりにくい
小規模な医療データでは、少しのリーケージでも評価が変わります。 施設 ID、薬剤名、疾患サブタイプなどで Target Encoding を使う場合は、 out-of-fold の計算にします。

// 08 · WRITING論文 Methods での書き方

TRIPOD+AI では、予測モデルのデータ処理、分割、検証、 欠測処理、モデル構築手順を明確に記載することが重要です。 前処理がリーケージを起こしていないかは、査読でも確認されやすい点です。 報告では、何を、どのデータで fit したかを明記します。 [5]

METHODS に含めたい項目
  • train/test 分割の時点、割合、層化の有無。
  • CV の種類、fold 数、乱数 seed。
  • 同一患者の複数行がある場合の GroupKFold。
  • 数値列、カテゴリ列、テキスト列の前処理内容。
  • 欠測補完、標準化、エンコードの fit 対象。
  • 特徴量選択を CV 内で行ったか。
  • 予測時点で取得できない列の除外基準。

たとえば、次のように書くと、前処理の流れが伝わりやすくなります。

All preprocessing steps, including missing-value imputation,
standardization, and one-hot encoding, were performed within
scikit-learn Pipeline and ColumnTransformer objects.
During cross-validation, preprocessing parameters were estimated
using the training fold only and then applied to the validation fold.

査読者が気にしやすいのは、全データで前処理していないかです。 もう 1 つは、予測時点の整合性です。 入院時予測モデルに、退院時評価、入院期間、退院先、 退院後追跡値が入っていないかを確認します。

また、多施設データでは施設 ID の扱いも重要です。 施設差を変数として使う場合もあります。 一方で、施設ごとの転帰率を全データで計算すると、 Target Encoding のリーケージになります。 施設別分布の違いは、外部検証や施設単位分割とも関係します。

// 09 · CHECKLIST前処理リーケージ確認リスト

  • 01欠測補完器を全データで fit していない。
  • 02Scaler を train fold 内だけで fit している。
  • 03One-Hot Encoder は Pipeline 内で処理している。
  • 04特徴量選択を CV の外で行っていない。
  • 05Target Encoding は out-of-fold で計算している。
  • 06同一患者の複数評価を別 fold に分けていない。
  • 07予測時点で取得できない列を説明変数から外している。
  • 08Methods に fit 対象と検証手順を書ける状態になっている。

// 10 · QUIZ理解度チェック

  1. Q1欠測補完でリーケージを起こしやすい処理はどれですか。
    • train fold の中央値で補完器を fit する
    • validation fold には transform のみ行う
    • 全データで中央値を計算してから CV する
    • Pipeline に補完器を入れる
    SHOW ANSWER
    C. validation/test の情報が補完器に入ります。
  2. Q2scikit-learn Pipeline の利点として適切なものはどれですか。
    • モデル性能を必ず改善する
    • CV 内で前処理とモデル学習を一体化できる
    • 欠測があるデータを全て削除できる
    • 外部検証を不要にできる
    SHOW ANSWER
    B. 性能改善そのものを保証する仕組みではありません。
  3. Q3同一患者から複数行の評価データがある場合、何を検討しますか。
    • GroupKFold
    • MinMaxScaler の全データ fit
    • test set のアウトカム確認
    • 退院時情報の追加
    SHOW ANSWER
    A. 同一患者のデータが train と test に分かれないようにします。
  4. Q4入院時予測モデルで注意する時点のリーケージはどれですか。
    • 年齢
    • 入院時 FIM
    • 発症から入院までの日数
    • 入院期間
    SHOW ANSWER
    D. 入院期間は退院後に確定する情報です。

// REF参考文献

  1. Kaufman S, Rosset S, Perlich C. Leakage in data mining: Formulation, detection, and avoidance. ACM Transactions on Knowledge Discovery from Data 2012;6(4):15:1-15:21.
  2. Pedregosa F, Varoquaux G, Gramfort A, et al. Scikit-learn: Machine learning in Python. Journal of Machine Learning Research 2011;12:2825-2830.
  3. Kuhn M, Johnson K. Feature Engineering and Selection. Boca Raton: CRC Press; 2019.
  4. Steyerberg EW. Clinical Prediction Models. 2nd ed. Cham: Springer; 2019.
  5. Collins GS, Moons KGM, Dhiman P, et al. TRIPOD+AI statement: updated guidance for reporting clinical prediction model studies that use regression or machine learning methods. BMJ 2024;385:e078378.