ついにシリーズ最終回です。
第1回で欠損のメカニズム(MCAR / MAR / MNAR)を紹介し、削除戦略(第3〜4回)、単変量補完(第5〜7回)、時系列補完(第8回)、KNN補完(第9回)と進んできました。
- 第1回:欠損値とは何か — MCAR / MAR / MNAR を理解する
- 第2回:欠損値の検出と可視化 — pandas と missingno で現状把握
- 第3回:削除戦略① — リストワイズ削除(完全ケース分析・CCA)
- 第4回:削除戦略② — 特徴量削除とペアワイズ削除
- 第5回:単変量補完① — 定数・任意値での補完(pandas と SimpleImputer)
- 第6回:単変量補完② — 平均・中央値・最頻値での補完
- 第7回:単変量補完③ — ランダムサンプル補完で分布を保つ
- 第8回:時系列データの欠損値補完 — LOCF / NOCB / 線形補間
- 第9回:多変量補完① — KNN Imputer で近傍から補完する
最終回となる今回は、さらに高度な多変量補完である MICE と MissForest、そして補完と併用すると効果的な 欠損インジケータ を扱います。
そして最後に、これまでの全手法を実際の予測タスクで総合比較 し、「結局どの手法を使えばいいのか」という問いに指針を示して締めくくります。
MICE(Multivariate Imputation by Chained Equations)
MICE は「連鎖方程式による多変量補完」と訳されます。
少し難しそうな名前ですが、考え方は理解しやすいものです。
MICE の基本的な考え方
MICE は、「欠損のある各列を、他の列を使って順番に予測する」 ことを繰り返す手法です。
流れを言葉で説明すると次のようになります。
1. まず、すべての欠損を仮の値(平均値など)で埋める
2. 1つの列を選び、その列の 本来の欠損箇所 を一旦「未知」に戻す
3. 他の列を説明変数として、その列を 回帰モデルで予測 し、欠損箇所を予測値で埋め直す
4. 次の列について同じことを行う
5. すべての列を1巡したら、また最初の列に戻り、これを 何度も繰り返す(反復)
6. 補完値が安定したら終了
scikit-learn での MICE 実装:IterativeImputer
scikit-learn では IterativeImputer クラスが MICE の考え方を実装しています。
これは現在も「実験的機能」という位置づけのため、使う前に専用のインポート文が必要です。
以下、コードです。
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
import warnings
warnings.filterwarnings('ignore')
from sklearn.model_selection import train_test_split
# 実験的機能の有効化(IterativeImputer を使う前に必須)
from sklearn.experimental import enable_iterative_imputer
from sklearn.impute import IterativeImputer
# Titanicデータセットの読み込み
df = sns.load_dataset('titanic')
numeric_cols = ['age', 'fare', 'pclass', 'sibsp', 'parch']
df_num = df[numeric_cols].copy()
# 学習用とテスト用に分割
df_train, df_test = train_test_split(
df_num,
test_size=0.2,
random_state=42
)
# MICE(IterativeImputer)で補完
mice_imputer = IterativeImputer(max_iter=10, random_state=42)
train_imputed = mice_imputer.fit_transform(df_train)
test_imputed = mice_imputer.transform(df_test)
# MICE の出力を DataFrame に戻す
df_train_mice = pd.DataFrame(
train_imputed,
columns=numeric_cols,
index=df_train.index
)
df_test_mice = pd.DataFrame(
test_imputed,
columns=numeric_cols,
index=df_test.index
)
print("-"*50)
print(f"補完前の欠損(学習): {df_train['age'].isnull().sum()}")
print(f"補完後の欠損(学習): {df_train_mice['age'].isnull().sum()}")
print(f"補完後の age の統計:")
print(df_train_mice['age'].describe().round(2))
print("-"*50)
print(f"補完後の欠損(テスト): {df_test_mice['age'].isnull().sum()}")
print(f"補完後の age の統計:")
print(df_test_mice['age'].describe().round(2))
from sklearn.experimental import enable_iterative_imputer:この行を実行しないとIterativeImputerがインポートできません。# noqaは「この行の文法警告を無視する」というコメント記法ですmax_iter=10:反復回数の上限(連鎖を10巡する)random_state=42:再現性の確保(第7回で学んだ通り重要)fit_transform/transform:これまで同様、データ漏洩を防ぐ流れ
以下、実行結果です。
-------------------------------------------------- 補完前の欠損(学習): 140 補完後の欠損(学習): 0 補完後の age の統計: count 712.00 mean 29.05 std 13.68 min -6.43 25% 22.00 50% 27.82 75% 36.00 max 80.00 Name: age, dtype: float64 -------------------------------------------------- 補完後の欠損(テスト): 0 補完後の age の統計: count 179.00 mean 30.34 std 13.29 min 0.83 25% 22.00 50% 27.83 75% 37.00 max 71.00 Name: age, dtype: float64
age の欠損が、他の列(fare, pclass など)との関係を使って補完されます。
MissForest(ランダムフォレストによる補完)
MissForest は、MICE の「各列を他の列で予測する」という枠組みの中で、予測モデルに ランダムフォレスト(Random Forest) を使う手法です。
MissForest の利点
ランダムフォレストやを使うことで、線形回帰では捉えられない 非線形な関係 や 特徴量同士の複雑な相互作用 も学習できます。
また、数値変数とカテゴリ変数の両方を扱える実装が多いのも利点です。
処理の流れは MICE とほぼ同じです。
1. 連続変数は平均、カテゴリ変数は最頻値で初期補完
2. データを「観測部分」と「欠損部分」に分け、観測部分でランダムフォレストを学習
3. 学習したモデルで欠損部分を予測し、埋め直す
4. これを、補完値の変化が小さくなるまで反復(通常5〜6回程度で収束)
実装方法その1:専用ライブラリ MissForest
専用のライブラリをインストールして使う方法があります。MissForestです。
以下でインストールできます。
pip install MissForest
では、実際に実施してみましょう。
以下、コードです。
from missforest import MissForest
# MissForestで補完
imputer = MissForest()
train_mf = imputer.fit_transform(df_train)
test_mf = imputer.transform(df_test)
# MissForest の出力を DataFrame に戻す
df_train_mf = pd.DataFrame(
train_mf,
columns=numeric_cols,
index=df_train.index
)
df_test_mf = pd.DataFrame(
test_mf,
columns=numeric_cols,
index=df_test.index
)
print()
print("-"*50)
print(f"補完前の欠損(学習): {df_train['age'].isnull().sum()}")
print(f"補完後の欠損(学習): {df_train_mf['age'].isnull().sum()}")
print(f"補完後の age の統計:")
print(df_train_mf['age'].describe().round(2))
print("-"*50)
print(f"補完後の欠損(テスト): {df_test_mf['age'].isnull().sum()}")
print(f"補完後の age の統計:")
print(df_test_mf['age'].describe().round(2))
以下、実行結果です。
100%|██████████| 5/5 [00:01<00:00, 3.56it/s] 100%|██████████| 5/5 [00:00<00:00, 391.44it/s] 100%|██████████| 5/5 [00:00<00:00, 502.52it/s] -------------------------------------------------- 補完前の欠損(学習): 140 補完後の欠損(学習): 0 補完後の age の統計: count 712.00 mean 29.65 std 13.32 min 0.42 25% 22.00 50% 28.90 75% 36.00 max 80.00 Name: age, dtype: float64 -------------------------------------------------- 補完後の欠損(テスト): 0 補完後の age の統計: count 179.00 mean 30.66 std 13.41 min 0.83 25% 22.00 50% 29.66 75% 37.00 max 71.00 Name: age, dtype: float64
実行すると、ランダムフォレストの予測力を使った高精度な補完が実現できます。
実装方法その2:scikit-learn で MissForest 相当を再現する
実は、第1節で使った IterativeImputer の 内部モデルをランダムフォレストに差し替える だけで、MissForest と同等の補完が実現できます。
以下、コードです。
from sklearn.ensemble import RandomForestRegressor
# 内部モデルにランダムフォレストを指定 = MissForest相当
missforest_like = IterativeImputer(
estimator=RandomForestRegressor(n_estimators=100, random_state=42),
max_iter=10,
random_state=42
)
train_mf2 = missforest_like.fit_transform(df_train)
test_mf2 = missforest_like.transform(df_test)
# 出力を DataFrame に戻す
df_train_mf2 = pd.DataFrame(
train_mf2,
columns=numeric_cols,
index=df_train.index
)
df_test_mf2 = pd.DataFrame(
test_mf2,
columns=numeric_cols,
index=df_test.index
)
print("-"*50)
print(f"補完前の欠損(学習): {df_train['age'].isnull().sum()}")
print(f"補完後の欠損(学習): {df_train_mf2['age'].isnull().sum()}")
print(f"補完後の age の統計:")
print(df_train_mf2['age'].describe().round(2))
print("-"*50)
print(f"補完後の欠損(テスト): {df_test_mf2['age'].isnull().sum()}")
print(f"補完後の age の統計:")
print(df_test_mf2['age'].describe().round(2))
estimator=RandomForestRegressor(...):IterativeImputerの内部予測モデルをランダムフォレストに変更n_estimators=100:ランダムフォレストの決定木の本数- これにより MICE の枠組みでランダムフォレストを使う = MissForest 相当の補完になる
以下、実行結果です。
-------------------------------------------------- 補完前の欠損(学習): 140 補完後の欠損(学習): 0 補完後の age の統計: count 712.00 mean 29.46 std 13.56 min 0.42 25% 21.00 50% 28.91 75% 37.13 max 80.00 Name: age, dtype: float64 -------------------------------------------------- 補完後の欠損(テスト): 0 補完後の age の統計: count 179.00 mean 30.67 std 13.59 min 0.83 25% 21.00 50% 29.55 75% 38.00 max 71.00 Name: age, dtype: float64
IterativeImputer の estimator を差し替えるだけで、さまざまなアルゴリズムベースの補完を試せるのが scikit-learn の柔軟なところです。
欠損インジケータ(Missing Indicator)
ここまでは「欠損値をいかに正確な値で埋めるか」を考えてきました。
しかし、発想を変えて 「欠損していたという事実そのもの」を特徴量として残す というテクニックがあります。
これが 欠損インジケータ(missing indicator) です。
欠損インジケータとは
欠損のある列について、「その値が欠損していたかどうか」を表す新しい列(0 / 1のフラグ) を追加します。
たとえば age 列に対して age_missing 列を作り、age が欠損していた行は1、そうでない行は0とします。
なぜこれが有効なのでしょうか?
それは、「欠損していたこと自体が予測に役立つ情報を持つ」場合がある からです。
第1回で触れた MNAR を思い出してください。「高所得者ほど年収を答えない」なら、「年収が欠損していた」という事実は「高所得者である可能性」を示唆します。
この情報を、補完値とは別に 明示的にモデルへ伝えられる のが欠損インジケータの強みです。
pandas での簡単な実装
以下、コードです。
df_ind = df_num.copy()
# age が欠損していた行を 1、そうでない行を 0 とするフラグ列を追加
df_ind['age_missing'] = np.where(df_ind['age'].isnull(), 1, 0)
print("age_missing 列の内訳:")
print(df_ind['age_missing'].value_counts())
print(df_ind[['age', 'age_missing']].head(10))
np.where(条件, 真のときの値, 偽のときの値):条件に応じて0/1を割り当てる- このフラグ列を作ったあと、
age列自体は中央値や KNN などで補完する
以下、実行結果です。
age_missing 列の内訳:
age_missing
0 714
1 177
Name: count, dtype: int64
age age_missing
0 22.0 0
1 38.0 0
2 26.0 0
3 35.0 0
4 35.0 0
5 NaN 1
6 54.0 0
7 2.0 0
8 27.0 0
9 14.0 0
age_missing 列が追加され、欠損だった行が1になっていることがわかります。
scikit-learn での実装
scikit-learn には専用の MissingIndicator クラスもあります。
以下、コードです。
from sklearn.impute import MissingIndicator
indicator = MissingIndicator()
mask_train = indicator.fit_transform(df_train)
# どの列にインジケータが作られたか確認
print(f"インジケータ対象の列インデックス: {indicator.features_}")
print(f"インジケータ行列の形状: {mask_train.shape}")
MissingIndicator():欠損のある列に対して真偽値のフラグ行列を生成indicator.features_:どの列にインジケータが作られたかを示す
以下、実行結果です。
インジケータ対象の列インデックス: [0] インジケータ行列の形状: (712, 1)
全手法の総合比較実験
これまで学んだ主要な手法を使って Titanic の 生存予測 を行い、どの補完手法が最も良い精度を出すかを実際に比較してみましょう。
以下、コードです。
from sklearn.impute import SimpleImputer, KNNImputer
from sklearn.pipeline import Pipeline
from sklearn.metrics import accuracy_score, f1_score
from sklearn.experimental import enable_iterative_imputer
from sklearn.impute import IterativeImputer
from sklearn.ensemble import RandomForestRegressor, RandomForestClassifier
# データ準備:数値特徴量で生存を予測
df_all = sns.load_dataset('titanic')
features = ['age', 'fare', 'pclass', 'sibsp', 'parch']
X = df_all[features].copy()
y = df_all['survived'].copy()
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
# 比較する補完手法を定義
imputers = {
'Median': SimpleImputer(strategy='median'),
'KNN (k=5)': KNNImputer(n_neighbors=5),
'MICE': IterativeImputer(
max_iter=1000,
random_state=42
),
'MissForest-like': IterativeImputer(
estimator=RandomForestRegressor(
n_estimators=1000,
random_state=42
),
max_iter=10,
random_state=42
),
}
results = []
for name, imputer in imputers.items():
# 補完 → ランダムフォレスト分類器のパイプライン
pipe = Pipeline([
('impute', imputer),
('model', RandomForestClassifier(
n_estimators=1000,
random_state=42,
class_weight='balanced'
)),
])
pipe.fit(X_train, y_train)
pred = pipe.predict(X_test)
acc = accuracy_score(y_test, pred)
f1 = f1_score(y_test, pred)
results.append({
'Method': name,
'Accuracy': round(acc, 4),
'F1': round(f1, 4)
})
results_df = pd.DataFrame(results)
print(results_df)
stratify=y:生存/非生存の比率を学習・テストで揃える(層化分割)Pipeline:補完 → モデル学習を1つにまとめる- CCA だけは行削除のため別処理にしている
以下、実行です。
Method Accuracy F1 0 Median 0.6257 0.5315 1 KNN (k=5) 0.6089 0.5000 2 MICE 0.6089 0.5000 3 MissForest-like 0.6369 0.5517
各補完手法での Accuracy(正解率)と F1 スコアが一覧で表示されます。
欠損インジケータ(missing indicator) 込みで予測モデルを作って比較してみます。
scikit-learn の補完手法であれば、add_indicator=Trueとすることで MissingIndicator をその中に簡単に組み込むことができます。
以下、コードです。
from sklearn.impute import SimpleImputer, KNNImputer, MissingIndicator
from sklearn.pipeline import Pipeline, FeatureUnion
from sklearn.metrics import accuracy_score, f1_score
from sklearn.experimental import enable_iterative_imputer
from sklearn.impute import IterativeImputer
from sklearn.ensemble import RandomForestRegressor, RandomForestClassifier
# データ準備:数値特徴量で生存を予測
df_all = sns.load_dataset('titanic')
features = ['age', 'fare', 'pclass', 'sibsp', 'parch']
X = df_all[features].copy()
y = df_all['survived'].copy()
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
# 比較する補完手法を定義
# add_indicator=True を使うことで、補完後の特徴量に「欠損していたかどうか」の
# 欠損インジケータ列を自動で追加して予測に利用する
imputers = {
'Median + Indicator': SimpleImputer(
strategy='median',
add_indicator=True
),
'KNN (k=5) + Indicator': KNNImputer(
n_neighbors=5,
add_indicator=True
),
'MICE + Indicator': IterativeImputer(
max_iter=1000,
random_state=42,
add_indicator=True
),
'MissForest-like + Indicator': IterativeImputer(
estimator=RandomForestRegressor(
n_estimators=1000,
random_state=42
),
max_iter=10,
random_state=42,
add_indicator=True
),
}
results = []
for name, imputer in imputers.items():
# 補完 + 欠損インジケータ → ランダムフォレスト分類器のパイプライン
pipe = Pipeline([
('impute', imputer),
('model', RandomForestClassifier(
n_estimators=10000,
random_state=42,
class_weight='balanced'
)),
])
pipe.fit(X_train, y_train)
pred = pipe.predict(X_test)
acc = accuracy_score(y_test, pred)
f1 = f1_score(y_test, pred)
results.append({
'Method': name,
'Accuracy': round(acc, 4),
'F1': round(f1, 4)
})
results_df = pd.DataFrame(results)
print(results_df)
以下、実行結果です。
Method Accuracy F1 0 Median + Indicator 0.6369 0.5324 1 KNN (k=5) + Indicator 0.6313 0.5217 2 MICE + Indicator 0.6369 0.5324 3 MissForest-like + Indicator 0.6480 0.5532
欠損値をネイティブに扱えるアルゴリズム
ここまで「欠損値を削除・補完してからモデルに渡す」前提で話してきました。
しかし実は、欠損値をそのまま受け取れるアルゴリズム も存在します。
| アルゴリズム | 欠損値の扱い |
|---|---|
| XGBoost | 学習時に欠損を振り分ける最適な分岐方向を自動学習。missing パラメータで指定可 |
| LightGBM | 既定で NaN を欠損として扱う。zero_as_missing で挙動を変更可能 |
| ナイーブベイズ |
たとえば XGBoost は、欠損値を「どちらの枝に送ると最も予測に有利か」を学習時に自動で決めます。
欠損値処理の選び方
シリーズ全体の集大成として、「どの状況でどの手法を使うべきか」 を一覧にまとめます。
| メカニズム(第1回) | 推奨される処理 | 該当回 |
|---|---|---|
| MCAR(完全にランダム) | 欠損率が低ければ削除、または平均・中央値・ランダムサンプル補完 | 第3, 6, 7回 |
| MAR(他変数に依存) | KNN・MICE・MissForest などの多変量補完 | 第9, 10回 |
| MNAR(値自体に依存) | 欠損インジケータの併用、専用のモデリング手法 |
実務での意思決定は、おおよそ次の順序で考えます。
1. まず欠損率を確認(第2回)。極端に高い列(70〜80%超)は列削除を検討(第4回)
2. 欠損メカニズムを推定(第1〜2回)。グループ別の欠損率などから MAR の兆候を探る
3. 欠損率が5%未満で MCAR なら、対象列を絞った行削除(CCA)でも可(第3回)
4. 時系列データなら、LOCF/NOCB/線形補間を検討(第8回)
5. それ以外は補完。まずは中央値補完をベースラインにし(第6回)、多変量補完(KNN/MICE/MissForest相当)と精度を比較(第9, 10回)
6. 欠損自体が情報を持ちそうなら、欠損インジケータを併用(第10回)
7. XGBoost等を使うなら、補完せず欠損のまま渡す選択肢も検討(第10回)
最も重要なポイントを振り返ります。
- 欠損メカニズムをまず見極める:機械的に「平均で埋める」「削除する」をしない
- データ漏洩を防ぐ:補完値は必ず学習データだけから計算し、テストデータにも同じ値を適用する(
fit_transform/transform、Pipelineの活用)
- 再現性を確保する:ランダム性のある手法は
random_stateを固定する
- 複数手法を比較する:唯一の正解はない。自分のデータで実際に試して決める
最後に
全10回にわたる「欠損値処理シリーズ」は、これで一旦終了です。
- 第1〜2回:欠損のメカニズム(MCAR/MAR/MNAR)と、その検出・可視化
- 第3〜4回:削除戦略(リストワイズ・特徴量削除・ペアワイズ)
- 第5〜7回:単変量補完(定数・平均/中央値/最頻値・ランダムサンプル)
- 第8回:時系列データの補完(LOCF/NOCB/線形補間)
- 第9〜10回:多変量補完(KNN・MICE・MissForest)と総合比較
- 第1回:欠損値とは何か — MCAR / MAR / MNAR を理解する
- 第2回:欠損値の検出と可視化 — pandas と missingno で現状把握
- 第3回:削除戦略① — リストワイズ削除(完全ケース分析・CCA)
- 第4回:削除戦略② — 特徴量削除とペアワイズ削除
- 第5回:単変量補完① — 定数・任意値での補完(pandas と SimpleImputer)
- 第6回:単変量補完② — 平均・中央値・最頻値での補完
- 第7回:単変量補完③ — ランダムサンプル補完で分布を保つ
- 第8回:時系列データの欠損値補完 — LOCF / NOCB / 線形補間
- 第9回:多変量補完① — KNN Imputer で近傍から補完する
- 第10回:多変量補完② — MICE・MissForest と欠損インジケータ、選び方ガイド
欠損値処理は地味な前処理に見えて、実はモデルの性能を大きく左右する、データサイエンスの腕の見せどころです。
「まずメカニズムを見極め、データ漏洩を防ぎ、複数手法を比較する」という姿勢は、Titanic データだけでなく、皆さんが今後出会うあらゆる実データで役立つはずです。
ここまでお付き合いいただき、ありがとうございました。

