🌲

【7日目】欠損処理をやってみる【2021アドベントカレンダー】

2021/12/07に公開

2021年1人アドベントカレンダー(機械学習)、7日目の記事になります。

https://qiita.com/advent-calendar/2021/solo_advent_calendar

テーマは 欠損値処理 になります。

dropna、fillna、SimpleImputer、DataWig を比較してみます。

Colab のコードはこちら Open In Colab

欠損の確認

まずどのくらい欠損値があるかを確認します。

X_train, X_test, y_train, y_test = train_test_split(
                                                    df.dropna(subset=["Global_Sales"]).drop(["Global_Sales",  "NA_Sales", "PAL_Sales", "JP_Sales", "Other_Sales"], axis=1), 
                                                    df.dropna(subset=["Global_Sales"])["Global_Sales"],  
                                                    test_size=0.3,
                                                    shuffle=True, 
                                                    random_state=SEED
                                                    ) 

print("X_train")
display(pd.DataFrame(X_train.isnull().sum()).T)

print("X_test")
display(pd.DataFrame(X_test.isnull().sum()).T)

print("y_train")
print(y_train.isnull().sum())

print("y_test")
print(y_test.isnull().sum())

dropna を使って欠損値を除去する

dropna を使うことで、欠損値を含む行や列を削除することができます。

引数に 下記を設定することで削除対象を変更できます。

  • how="any" ・・・ 1つでも欠損値があるもの
  • how="all" ・・・ すべて欠損値であるもの
  • axis=1 ・・・ 列
  • axis=0 ・・・ 行
X_train_drop = X_train.dropna(axis=1, how='any')
X_test_drop = X_test.dropna(axis=1, how='any')

https://note.nkmk.me/python-pandas-nan-dropna-fillna/

fillna で median (中央値) で補完する

fillna を使うことで欠損値を補完することができます。

平均値(df.mean())や中央値(df.median())だとカテゴリ変数を処理できないので、簡便的に最頻値(df.mode())を使ってみます。
(実際はデータの分布に合ったものを使いましょう。)

X_train_md = X_train.fillna(X_train.mode())
X_test_md = X_test.fillna(X_test.mode())

https://note.nkmk.me/python-pandas-nan-dropna-fillna/

fillna で method を ffill にする

引数に method='ffill' を使うことで 直前の値 で欠損値を補完することができます。

ただし頭から欠損値が続いている場合は直前の値がないので補完されません。

また、method='bfill' を使うと 直後の値 で欠損値を補完することができます。

X_train_ff = X_train.fillna(method='ffill')
X_test_ff = X_test.fillna(method='ffill')

https://note.nkmk.me/python-pandas-nan-dropna-fillna/

SimpleImputer

SimpleImputer を使えば カテゴリーエンコーディングの様に欠損処理を行うことができます。

なお、strategy には mean(平均値)、median(中央値)、most_frequent(最頻値)もありますが、カテゴリーデータには使えません。

strategy に constant を設定した場合は、欠損値は fill_value で設定した値に置き換えることができます。
(default は 文字列 "missing_value")

imp = SimpleImputer(
    missing_values=np.nan, 
    strategy='constant',
    fill_value=-9999
    )

X_train_imp_cst = pd.DataFrame(
                            imp.fit_transform(X_train),
                            columns = X_train.columns
                        )

X_test_imp_cst = pd.DataFrame(
                            imp.transform(X_test),
                            columns = X_test.columns
                        )

https://scikit-learn.org/stable/modules/generated/sklearn.impute.SimpleImputer.html

DataWig

DataWig は欠損の状況を学習し、予測して補完するライブラリです。

そのため、欠損の分布状況を踏まえた補完ができますが、学習が必要となるので学習データとテストデータの重複を防ぐためには、欠損の学習にのみ使うデータを用意する必要が生じます。

null_columns =X_train.dropna(how="all", axis=1).isnull().sum()[X_train.isnull().sum() > 0].index.to_list()

# 学習
EPOCH = 5

X_train_wig = X_train.dropna(how="all", axis=1).copy()
X_test_wig = X_test[X_train.dropna(how="all", axis=1).columns].copy()

for column in null_columns:

    print(column, end="")

    imputer = datawig.SimpleImputer(
        input_columns=X_train.columns,
        output_column=column,
    )

    imputer.fit(
        train_df=X_train,
        num_epochs=EPOCH
    )

    # 欠損値に相当する項目の予測
    preds_train = imputer.predict(X_train)
    X_train_wig[column] = preds_train[column +  "_imputed"]

    preds_test = imputer.predict(X_test)
    X_test_wig[column] = preds_test[column +  "_imputed"]

    print(" is done!")

https://datawig.readthedocs.io/en/latest/

RMSE 比較

print("dropna RMSE:", round(rmse_drop, 3))
print("fillna median RMSE:", round(rmse_md, 3))
print("fillna ffill RMSE:", round(rmse_ff, 3))
print("SimpleImputer constant RMSE:", round(rmse_imp_cst, 3))
print("DataWig RMSE:", round(rmse_wig, 3))

出力:
dropna RMSE: 0.206
fillna median RMSE: 0.226
fillna ffill RMSE: 0.228
SimpleImputer constant RMSE: 1.222
DataWig RMSE: 0.227

7日目は以上になります、最後までお読みいただきありがとうございました。

Discussion