欠損値の補完でリークする理由:平均値が20から40に変わる教材で確認

欠損値を平均で埋める作業にも、評価データの情報が入り込む可能性があります。機械学習の前処理では、学習用と評価用を分けた後に補完規則を学習するのが基本です。架空の温度の4行で、分ける順番の違いを手計算します。

欠損は0と同じではない

記録されていない値を0にすると、実際に0だった測定値と区別できません。教材の温度なら、センサーが記録しなかったことと0度を測ったことは別です。最初に、空欄、測定不能、対象外などが同じ記号になっていないか確認します。今回扱うのは「数値が欠けている」ことだけで、欠損の理由を推測する処理ではありません。

用途温度の教材値
学習10、欠損、30
評価80

全体の平均で埋めると、評価行が規則を変える

観測された全行の平均は(10+30+80)/3=40です。この40で学習側の欠損を埋めると、評価用の80を見てから学習データを作ったことになります。

学習用だけの平均なら(10+30)/2=20です。学習行は10、20、30となります。将来の入力に欠損があっても、この学習済み規則では20を使います。評価側の分布を見て平均を決め直しません。数字の大小ではなく、規則を決める際に何を参照したかが問題です。

fitとtransformを分けて読む

import numpy as np
from sklearn.impute import SimpleImputer

train = np.array([[10.0], [np.nan], [30.0]])
test = np.array([[80.0], [np.nan]])
imputer = SimpleImputer(strategy="mean")
train_filled = imputer.fit_transform(train)
test_filled = imputer.transform(test)

assert imputer.statistics_[0] == 20.0
assert train_filled[:, 0].tolist() == [10.0, 20.0, 30.0]
assert test_filled[:, 0].tolist() == [80.0, 20.0]

fitは学習行から補完に必要な値を決める段階、transformはその値を使って変換する段階です。SimpleImputerは、平均、中央値などの方法を指定できます。上は平均を学ぶ演習で、どのデータでも平均補完が最良になるという推奨ではありません。[出典1]

交差検証でも「分割ごと」に補完を学ぶ

学習データ全体で補完してから交差検証に入ると、各分割の評価側を先に参照します。各分割の学習側で補完規則を学ぶ順序が必要です。前処理とモデルをPipelineで組み合わせれば、この順序を管理しやすくなります。[出典2]

時系列なら、将来に当たる行を前処理のfitへ含めないことも確認します。利用者ごとの繰り返し記録がある問題では、同じ利用者の行が学習と評価に分かれる影響も別に点検します。

補完を決めるためのチェック表

質問残す記録
なぜ欠けているか未入力・測定不能・対象外を分けられるか
どこでfitしたか参照した行IDと期間
何で埋めたか列ごとの方法と学習した統計量
評価を見て変更したか調整用データと最終評価データの分離
本番でも同じか列順、欠損記号、保存した変換器の版
列全体が欠損したら使用ライブラリの扱いと後工程の入力列数

欠損の多い列を残すか、補完するか、別の方法を使うかは問題ごとの判断です。「欠損をなくした」ことだけを完成条件にせず、評価の情報が入り込んでいないことと、元の意味を壊していないことを確認してください。

出典

  1. scikit-learn: SimpleImputer。
  2. scikit-learn: Common pitfalls and recommended practices(前処理のリーク)。