「とりあえず平均値で埋めとく」が分析を歪める典型例。欠損には性質の違いがあり、それに応じた処理を選ぶ必要があります。本記事では MCAR / MAR / MNAR の分類と、削除・補完・フラグ化の使い分けを共有します。
欠損の 3 種類
| 分類 | 意味 | 例 | 対処 |
|---|---|---|---|
| MCAR(完全ランダム) | 欠損が完全にランダム | 通信エラーで一部記録ロス | 削除 OK、平均補完 OK |
| MAR(条件付きランダム) | 他の観測値で欠損が説明可能 | 高齢者ほど ID なし | 回帰補完 / MICE |
| MNAR(非ランダム) | 欠損自体に意味がある | 高所得者ほど年収を答えない | フラグ化推奨、削除/補完は危険 |
対処法の比較
- Listwise deletion:欠損行を削除。サンプル数激減のリスク
- Pairwise deletion:使う列のみで欠損行を除外。集計のたびに N が変わる
- 平均 / 中央値補完:簡単だが分散が縮む、相関が歪む
- 回帰補完:他の列から予測、ただし元の関係を強化してしまう
- MICE(Multiple Imputation):複数の補完値を生成し、不確実性を保つ
- 欠損フラグ追加:「欠損していた」というシグナル自体を学習に使う ── では強力
Python での実装
1. 欠損率と欠損パターンの可視化
Python
import pandas as pdimport numpy as npimport matplotlib.pyplot as pltimport seaborn as snsimport japanize_matplotlib # noqa: F401
# 欠損率を確認print(df.isna().mean().sort_values(ascending=False))
# 欠損パターンのヒートマップ(行=サンプル、列=特徴量、色=欠損)fig, ax = plt.subplots(figsize=(10, 6))sns.heatmap(df.isna(), cbar=False, yticklabels=False, cmap="Greys", ax=ax)ax.set_title("欠損パターン(黒=欠損)"); plt.tight_layout(); plt.show()
# 列同士の欠損相関(一緒に欠損するか)missing_corr = df.isna().corr()print(missing_corr.round(2))2. 5 種類の補完手法を比較
Python
from sklearn.impute import SimpleImputer, KNNImputerfrom sklearn.experimental import enable_iterative_imputer # noqafrom sklearn.impute import IterativeImputer
# 各手法を辞書でimputers = { "削除": lambda d: d.dropna(), "平均補完": lambda d: d.fillna(d.mean(numeric_only=True)), "中央値補完": lambda d: d.fillna(d.median(numeric_only=True)), "KNN(k=5)": lambda d: pd.DataFrame( KNNImputer(n_neighbors=5).fit_transform(d.select_dtypes("number")), columns=d.select_dtypes("number").columns, index=d.index, ), "MICE": lambda d: pd.DataFrame( IterativeImputer(max_iter=10, random_state=42).fit_transform(d.select_dtypes("number")), columns=d.select_dtypes("number").columns, index=d.index, ),}
results = {}for name, fn in imputers.items(): imputed = fn(df.copy()) if "income" in imputed.columns: results[name] = { "n": len(imputed), "mean": imputed["income"].mean(), "std": imputed["income"].std(), }print(pd.DataFrame(results).T.round(0))3. 「欠損フラグ列」を追加(ML で強力)
Python
# 補完前にフラグ列を作る(これが最重要)def add_missing_flags(df: pd.DataFrame, cols: list[str]) -> pd.DataFrame: for col in cols: df[f"{col}_was_missing"] = df[col].isna().astype(int) return df
# 1. フラグ追加df = add_missing_flags(df, ["age", "income", "tenure"])
# 2. その後で補完df = df.fillna(df.median(numeric_only=True))
# 3. ML モデルにはフラグ列も特徴量として渡す# → 「欠損していたユーザー」がモデル予測に使えるシグナルになる# 例:「年収未回答 = 高所得層」という相関を学習可能(MNAR 対応)4. 列ごとに最適手法を自動選定
Python
def auto_impute(df: pd.DataFrame, target_col: str | None = None) -> pd.DataFrame: """欠損率と型に応じて自動で手法を選ぶ""" df = df.copy() for col in df.columns: missing_pct = df[col].isna().mean()
if missing_pct == 0: continue if missing_pct > 0.5: print(f"[skip] {col}: 欠損 {missing_pct:.0%} → 列削除を検討") df = df.drop(columns=[col]) continue
# フラグ追加 df[f"{col}_was_missing"] = df[col].isna().astype(int)
if df[col].dtype.kind in "ifu": # 数値 if missing_pct < 0.05: df[col] = df[col].fillna(df[col].median()) else: # 5% 以上は KNN from sklearn.impute import KNNImputer num_cols = df.select_dtypes("number").columns df[num_cols] = KNNImputer(n_neighbors=5).fit_transform(df[num_cols]) break else: # カテゴリ df[col] = df[col].fillna(df[col].mode()[0]) return df
cleaned = auto_impute(df)ふくふくの進め方
「欠損だらけで困っている」というご相談には、欠損パターンの分析(1 週間)→ 列ごとの処理選定 → パイプライン化を 1〜2 週間で。MNAR を見落とすと分析が壊れるので、最初の診断が最重要です。
次回予告
EP.03 は外れ値の検出と除去。「3σ から外れたら除去」が破綻する話と、IQR / MAD / Winsorize の使い分け。
この記事の感想を教えてください
あなたの 1 クリックで、本当にこの記事は更新されます。「もっと詳しく」「続編希望」が一定数集まった記事は、 ふくふくが 実際に内容を拡充したり続編記事を公開 します。 送信したリアクションはお使いのブラウザに記録され、再カウントされません。