欠損値処理
田中VPoE「NetShop社のデータを見てみたら、案の定、欠損値がたくさんある。顧客の年齢が未入力だったり、商品カテゴリが空だったり。」
あなた「欠損値がある行は全部削除すればいいんじゃないですか?」
田中VPoE「それは危険だ。欠損が多いと分析対象が大幅に減るし、欠損がランダムでない場合はバイアスが生じる。欠損のパターンを理解してから対処法を選ぶ必要があるんだ。」
欠損値とは
欠損値(Missing Value)は、データが記録されていない値のことです。Pandasでは NaN(Not a Number)として表現されます。
import pandas as pd
import numpy as np
# 欠損値の確認
df.isnull().sum() # カラムごとの欠損数
df.isnull().mean() * 100 # カラムごとの欠損率(%)
# 欠損値のあるカラムだけ表示
missing = df.isnull().sum()
missing[missing > 0].sort_values(ascending=False)
欠損パターンの分析
欠損が発生するメカニズムには3つのパターンがあります:
| パターン | 英語 | 説明 | 例 |
|---|---|---|---|
| 完全にランダム | MCAR | 欠損が他の変数と無関係 | データ入力のランダムエラー |
| 条件付きランダム | MAR | 欠損が観測変数に依存 | 若い顧客ほど年齢を入力しない |
| ランダムでない | MNAR | 欠損が欠損値自体に依存 | 高所得者ほど所得を入力しない |
パターンの確認方法
# 欠損の有無でグループ分け
has_age = df[df['age'].notna()]
no_age = df[df['age'].isna()]
# 他の変数の分布を比較
print("年齢あり - 平均購入額:", has_age['amount'].mean())
print("年齢なし - 平均購入額:", no_age['amount'].mean())
# 欠損パターンの可視化
import seaborn as sns
import matplotlib.pyplot as plt
# 欠損値のヒートマップ
plt.figure(figsize=(12, 6))
sns.heatmap(df.isnull(), cbar=True, yticklabels=False)
plt.title('欠損値パターン')
plt.show()
欠損値の対処法
1. 削除
# 欠損を含む行を削除
df_clean = df.dropna()
# 特定カラムの欠損を含む行を削除
df_clean = df.dropna(subset=['customer_id', 'amount'])
# 欠損率が高いカラムを削除
threshold = 0.5 # 50%以上欠損
cols_to_drop = df.columns[df.isnull().mean() > threshold]
df_clean = df.drop(columns=cols_to_drop)
削除が適切な場合:
- 欠損がMCARで、欠損率が5%未満
- 分析に不要なカラムの欠損
- レコード数が十分に多い場合
2. 定数補完
# 固定値で補完
df['category'] = df['category'].fillna('不明')
df['amount'] = df['amount'].fillna(0)
3. 統計量による補完
# 平均値で補完
df['age'] = df['age'].fillna(df['age'].mean())
# 中央値で補完(外れ値に強い)
df['amount'] = df['amount'].fillna(df['amount'].median())
# 最頻値で補完(カテゴリカル変数向け)
df['category'] = df['category'].fillna(df['category'].mode()[0])
4. グループ別補完
# カテゴリ別の中央値で補完
df['price'] = df.groupby('category')['price'].transform(
lambda x: x.fillna(x.median())
)
# 地域別の平均年齢で補完
df['age'] = df.groupby('region')['age'].transform(
lambda x: x.fillna(x.mean())
)
5. 前方補完・後方補完(時系列)
# 前方補完(直前の値で埋める)
df['value'] = df['value'].fillna(method='ffill')
# 後方補完(直後の値で埋める)
df['value'] = df['value'].fillna(method='bfill')
# 線形補間
df['value'] = df['value'].interpolate(method='linear')
6. KNN補完(高度な手法)
from sklearn.impute import KNNImputer
imputer = KNNImputer(n_neighbors=5)
df_numeric = df.select_dtypes(include=[np.number])
df_imputed = pd.DataFrame(
imputer.fit_transform(df_numeric),
columns=df_numeric.columns
)
補完戦略の選び方
| データの特性 | 推奨手法 | 理由 |
|---|---|---|
| 欠損率5%未満・MCAR | 行削除 | データ損失が少ない |
| カテゴリカル変数 | 最頻値 or 「不明」カテゴリ | 分布を保持 |
| 正規分布の数値 | 平均値 | 代表値として適切 |
| 歪んだ分布の数値 | 中央値 | 外れ値に頑健 |
| グループ内差が大きい | グループ別補完 | グループの特性を反映 |
| 時系列データ | 前方補完 or 線形補間 | 時間的な連続性を保持 |
| 変数間の相関が強い | KNN補完 | 多変量の関係を考慮 |
欠損値処理の注意点
- 欠損フラグの作成:補完した場合、元々欠損だったことを示すフラグ列を追加しておく
df['age_was_missing'] = df['age'].isnull().astype(int)
df['age'] = df['age'].fillna(df['age'].median())
-
補完前後の分布確認:補完によって分布が大きく変わっていないか確認する
-
ドキュメント化:どの手法で補完したかを分析レポートに記録する
まとめ
| 項目 | ポイント |
|---|---|
| 欠損パターン | MCAR/MAR/MNARの3パターンを理解する |
| まず分析 | 欠損率と欠損パターンを確認してから対処法を選ぶ |
| 削除 | MCARで少量の場合のみ |
| 補完 | データの特性に応じて適切な手法を選択 |
| 記録 | 欠損フラグの追加と処理内容のドキュメント化 |
チェックリスト
- 欠損値の3パターン(MCAR/MAR/MNAR)を説明できる
- 欠損率の確認方法を知っている
- データの特性に応じた補完手法を選択できる
- 欠損フラグの重要性を理解している
次のステップへ
欠損値処理を学びました。次は、複数のデータセットの結合と変形について学びましょう。
推定読了時間:30分