分析に利用するデータには多くの場合、なんらかの理由により記録されなかった値、欠損値 (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 |
その他(メモ)
- 時系列データの処理の場合には、前後の値で補完することが多い。
fillna(method='ffill')
:前方補完fillna(method='bfill')
:後方補完df.interpolate()
:線形補完
- XGboostなどGBDT系のライブラリでは欠損値があってもOK
その他にも色々な補完方法があるみたい。
https://qiita.com/FukuharaYohei/items/9830d5760595619352a5#iterativeimputer%E9%96%A2%E6%95%B0
参考
参考HP
関連書籍
Kaggleで勝つデータ分析の技術
Python実践データ分析100本ノック
本質を捉えたデータ分析のための分析モデル入門 統計モデル、深層学習、強化学習等
Pythonによるあたらしいデータ分析の教科書