🐛

欠損値の補完や取り扱いについて(python)

2022/08/27に公開

分析に利用するデータには多くの場合、なんらかの理由により記録されなかった値、欠損値 (missing data) が含まれる

欠損値があると統計的処理や、機械学習の処理がそのまま適用できなかったり、結果にバイアスが生じてしまうので、データ分析の際にはなんらかの処理をする必要がある。

さまざま処理の方法についてまとめた

データの準備など

import numpy as np
import pandas as pd
from sklearn import datasets

diabetes = datasets.load_diabetes()
df = pd.DataFrame(diabetes['data'],columns=diabetes['feature_names'])
display(df.iloc[:,:5].head())
age sex bmi bp s1
0 0.038076 0.050680 0.061696 0.021872 -0.044223
1 -0.001882 -0.044642 -0.051474 -0.026328 -0.008449
2 0.085299 0.050680 0.044451 -0.005671 -0.045599
3 -0.089063 -0.044642 -0.011595 -0.036656 0.012191
4 0.005383 -0.044642 -0.036385 0.021872 0.003935

欠損値の有無は 'isnull' メソッドで確認できる

print(df.isnull().any(axis=0))
age    False
sex    False
bmi    False
bp     False
s1     False
s2     False
s3     False
s4     False
s5     False
s6     False
dtype: bool

今回のデータは欠損値がないので意図的に欠損値を作成する

df.iloc[0:2,0] = np.nan
df.iloc[1:3,1] = np.nan
df.iloc[2:4,2] = np.nan

display(df.iloc[:,:5].head())
age sex bmi bp s1
0 NaN 0.050680 0.061696 0.021872 -0.044223
1 NaN NaN -0.051474 -0.026328 -0.008449
2 0.085299 NaN NaN -0.005671 -0.045599
3 -0.089063 -0.044642 NaN -0.036656 0.012191
4 0.005383 -0.044642 -0.036385 0.021872 0.003935

欠損値処理方法

欠損値を持つデータを除去する

(もしくは欠損値を持つ変数を除去する)

# 欠損値を持つデータを除去する
df_ = df.dropna(axis=0)
print(f'{df.shape}=>{df_.shape}')

# 欠損値を持つ変数を除去する
df_ = df.dropna(axis=1)
print(f'{df.shape} => {df_.shape}')
(442, 10)=>(438, 10)
(442, 10) => (442, 7)

なんらかの統計量で補完する

平均値や中央値、最頻値など

# 平均値で補完
df_mean = df.copy()
for col in df.columns:
    df_mean[col] = df_mean[col].fillna(df_mean[col].mean())

print('平均値で補完')
display(df_mean.iloc[:,:3].head())

# 中央値で補完
df_median = df.copy()
for col in df.columns:
    df_median[col] = df_median[col].fillna(df_median[col].median())

print('中央値で補完')
display(df_median.iloc[:,:3].head())

# 最頻値で補完
df_mode = df.copy()
for col in df.columns:
    df_mode[col] = df_mode[col].fillna(df_mode[col].mode()[0])

print('最頻値で補完')
display(df_mode.iloc[:,:3].head())

平均値で補完

age sex bmi
0 -0.000082 0.050680 0.061696
1 -0.000082 -0.000014 -0.051474
2 0.085299 -0.000014 -0.000075
3 -0.089063 -0.044642 -0.000075
4 0.005383 -0.044642 -0.036385

中央値で補完

age sex bmi
0 0.005383 0.050680 0.061696
1 0.005383 -0.044642 -0.051474
2 0.085299 -0.044642 -0.007284
3 -0.089063 -0.044642 -0.007284
4 0.005383 -0.044642 -0.036385

最頻値で補完

age sex bmi
0 0.016281 0.050680 0.061696
1 0.016281 -0.044642 -0.051474
2 0.085299 -0.044642 -0.030996
3 -0.089063 -0.044642 -0.030996
4 0.005383 -0.044642 -0.036385

予測値で補完する

例として、ランダムフォレストでの予測値で補完してみる。

from sklearn.ensemble import RandomForestRegressor

# 欠損値のある変数を列挙
null_cols = df.columns[df.isnull().any(axis=0)]

df_rf = df.copy()
for col in null_cols:

    df_temp = df_rf.copy()

    # データの抜き出し
    X = df_temp.drop(col,axis=1)
    y = df_temp[col]

    y_train = y.dropna()
    y_test = y.loc[y.isnull()]

    X_train = X.loc[y_train.index].fillna(-999)
    X_test = X.loc[y_test.index].fillna(-999)

    # ランダムフォレストでの欠損値予測
    rf = RandomForestRegressor()
    rf.fit(X_train,y_train)
    y_pred = rf.predict(X_test)

    # 予測値で補完する
    df_rf.loc[y_test.index,col] = y_pred

display(df_rf.iloc[:,:3].head())
age sex bmi
0 0.031319 0.050680 0.061696
1 -0.021062 -0.037969 -0.051474
2 0.085299 0.010645 0.003904
3 -0.089063 -0.044642 -0.013406
4 0.005383 -0.044642 -0.036385

欠損値の有無の情報使って特徴量を生成する

欠損値がランダムに出現するのではなく何らか理由があって出現している場合には、欠損値の有無やその数などを特徴量とすることで、予測精度が向上する場合もある。

# 欠損値のある変数を列挙しておく
null_cols = df.columns[df.isnull().any(axis=0)]

# 欠損値の有無を特徴量にする
df_null = df.copy()
for col in null_cols:
    df_null[f'{col}_null'] = 0
    df_null.loc[df_null[col].isnull(), f'{col}_null'] = 1

print("欠損値の有無")
display(df_null.iloc[:,-3:].head())

# 欠損値の個数を特徴量にする
df_ncount = df.copy()

df_ncount['null_count'] = 0
df_ncount['null_count'] = df.isnull().sum(axis=1).values

print("欠損値の個数")
display(df_ncount.iloc[:,-1:].head())

欠損値の有無

age_null sex_null bmi_null
0 1 0 0
1 1 1 0
2 0 1 1
3 0 0 1
4 0 0 0

欠損値の個数

null_count
0 1
1 2
2 2
3 1
4 0

その他(メモ)

  1. 時系列データの処理の場合には、前後の値で補完することが多い。
    • fillna(method='ffill'):前方補完
    • fillna(method='bfill'):後方補完
    • df.interpolate():線形補完
  2. XGboostなどGBDT系のライブラリでは欠損値があってもOK

その他にも色々な補完方法があるみたい。
Pythonでの欠損値補完(代入法) scikit-learnとpandas

参考

書籍

以上

Discussion