【kaggle】中級機械学習 - 欠損値【備忘翻訳】

# はじめに
当記事はkaggleのLearnの[Intro to Machine Learning](https://www.kaggle.com/learn/intro-to-machine-learning)の[Missing Values](https://www.kaggle.com/code/alexisbcook/missing-values)を翻訳して備忘としたものです。
拙い英語力なので間違い等あればご指摘いただけたらと思います。

まとめ:[【kaggle】翻訳記事まとめ【備忘翻訳】](https://qiita.com/leafeon00000/items/71e57af5b2d9b0b7c846)
前:[【kaggle】機械学習イントロ - ランダムフォレスト【備忘翻訳】](https://qiita.com/leafeon00000/private/424f21de7a0685d08914)
次:[【kaggle】中級機械学習 - カテゴリ変数【備忘翻訳】](https://qiita.com/leafeon00000/private/e23aca220cb83168db4f)

:::note warn
当記事に含まれるコードはkaggle内のnotebook内で正常に動作します。動作を試したい場合はkaggleのnotebookで試してください。
:::

----

# 欠損値
欠損値は発生します。実際のデータセットではよくあるこの課題に備えてください。

このチュートリアルでは、欠損値を処理する3つのアプローチを学習します。次に、実際のデータセットでこれらのアプローチの有効性を比較します。

## イントロ
データに欠損値が生じる原因は数多くあります。例えば、
* ベッドルームが２つしかない家には、３つ目のベッドルームのサイズの値は含まれません。
* 回答者は収入を記入しないことが可能です。

ほとんどの機械学習ライブラリ(scikit-learnを含む)では、欠損値のあるデータを使用してモデルを構築しようとするとエラーが発生します。そのため、以下のいずれかの対策をする必要があります。

## 3つのアプローチ
### 1) 単純な選択肢: 欠損値のある列を削除する
最も簡単な選択肢は、欠損値のある列を削除することです。
![Sax80za.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547651/0969976c-50bf-efa9-c223-26f16485e24e.png)
削除された列のほとんどの値が欠損していない限り、このアプローチではモデルは多くの(潜在的に有用な)情報にアクセスできなくなります。極端な例として、1つの重要な列にデータが1つ欠けている10,000行のデータセットを考えてみましょう。このアプローチでは、その列が完全に削除されてしまいます！

### 2) よりよい選択肢: 補完
**補完(imputation)** では欠損値を何らかの数値で埋めます。例えば、各列の平均値で埋めます。
![4BpnlPA.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547651/7220680d-2889-9782-1da6-8432c45eefaa.png)
ほとんどの場合、保管された値は正確には正しくありませんが、通常は列を完全に削除した場合よりも正確なモデルが得られます。

### 3) 補完の拡張
補完は標準的なアプローチであり、通常はうまく機能します。ただし、代入された値は、データセットで収集されなかった実際の値よりも系統的に上、または下になる可能性があります。または、欠損値のある行が他の何らかの方法で一意である可能性があります。その場合、モデルは元々どの値が欠損していたかを考慮して、より適切な予測を行うことになります。
![UWOyg4a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547651/064e9571-fb6c-97b8-ed3c-824eca9e5d9b.png)
このアプローチでは、依然と同様に欠損値を補完します。さらに、元のデータセットでデータが欠損している列ごとに、保管されたデータの場所を示す新しい列を追加します。

場合によっては、これによって結果が大きく改善されますが、全くや国立たない場合もあります。

## 例
この例ではメルボルンの住宅データセットを使用します。モデルは、部屋数や土地の面積などの情報を使用して住宅価格を予測します。

データの読み取りについては特に取り上げません。`X_train`、`X_valid`、`y_train`、`y_valid`にトレーニングデータと検証データがすでにある段階だと考えてください。

<details><summary>データの読み取り</summary>

```python
import pandas as pd
from sklearn.model_selection import train_test_split

# Load the data
data = pd.read_csv('../input/melbourne-housing-snapshot/melb_data.csv')

# Select target
y = data.Price

# To keep things simple, we'll use only numerical predictors
melb_predictors = data.drop(['Price'], axis=1)
X = melb_predictors.select_dtypes(exclude=['object'])

# Divide data into training and validation subsets
X_train, X_valid, y_train, y_valid = train_test_split(X, y, train_size=0.8, test_size=0.2,
                                                      random_state=0)
```

</details>

### 各アプローチの品質を測定する関数の定義

欠損値を処理する様々なアプローチを比較するために、関数`score_dataset()`を定義します。この関数はランダムフォレストモデルからの平均絶対誤差(MAE)を返却します。

---

### アプローチ1のスコア(欠損値のある列の削除)

トレーニングセットと検証セットの両方で操作する必要があるため、両方のDataFrameで同じ列を削除するように注意します。

```python
# Get names of columns with missing values
cols_with_missing = [col for col in X_train.columns
                     if X_train[col].isnull().any()]

# Drop columns in training and validation data
reduced_X_train = X_train.drop(cols_with_missing, axis=1)
reduced_X_valid = X_valid.drop(cols_with_missing, axis=1)

print("MAE from Approach 1 (Drop columns with missing values):")
print(score_dataset(reduced_X_train, reduced_X_valid, y_train, y_valid))
```

```console:console
MAE from Approach 1 (Drop columns with missing values):
183550.22137772635
```

### アプローチ2のスコア(補完)
次に`SimpleImputer`を使用して、欠損値を各列の平均値に置き換えます。

単純ですが、平均値で補完すると、一般的にパフォーマンスが非常によくなります(ただし、データセットによります)。統計学者たちは補完値を決めるために複雑な手法を試してきましたが(例えば補完代入など)、複雑な手法では、その結果を高度な機械学習モデルに入力しても、あまりメリットはありません。

```python
from sklearn.impute import SimpleImputer

# Imputation
my_imputer = SimpleImputer()
imputed_X_train = pd.DataFrame(my_imputer.fit_transform(X_train))
imputed_X_valid = pd.DataFrame(my_imputer.transform(X_valid))

# Imputation removed column names; put them back
imputed_X_train.columns = X_train.columns
imputed_X_valid.columns = X_valid.columns

print("MAE from Approach 2 (Imputation):")
print(score_dataset(imputed_X_train, imputed_X_valid, y_train, y_valid))
```

```console:console
MAE from Approach 2 (Imputation):
178166.46269899711
```

**アプローチ2**のMAEは**アプローチ1**よりも低いため、このデータセットでは**アプローチ2**のパフォーマンスのほうが優れていることがわかります。

### アプローチ3のスコア(補完の拡張)
次に欠損値を補完し、どの値が補完されたかを追跡します。

```python
# Make copy to avoid changing original data (when imputing)
X_train_plus = X_train.copy()
X_valid_plus = X_valid.copy()

# Make new columns indicating what will be imputed
for col in cols_with_missing:
    X_train_plus[col + '_was_missing'] = X_train_plus[col].isnull()
    X_valid_plus[col + '_was_missing'] = X_valid_plus[col].isnull()

# Imputation
my_imputer = SimpleImputer()
imputed_X_train_plus = pd.DataFrame(my_imputer.fit_transform(X_train_plus))
imputed_X_valid_plus = pd.DataFrame(my_imputer.transform(X_valid_plus))

# Imputation removed column names; put them back
imputed_X_train_plus.columns = X_train_plus.columns
imputed_X_valid_plus.columns = X_valid_plus.columns

print("MAE from Approach 3 (An Extension to Imputation):")
print(score_dataset(imputed_X_train_plus, imputed_X_valid_plus, y_train, y_valid))
```

```console:console
MAE from Approach 3 (An Extension to Imputation):
178927.503183954
```

示された通り、**アプローチ3**のパフォーマンスは**アプローチ2**のパフォーマンスよりもわずかに劣っています。

### ではなぜ列を削除するよりも補完のほうがパフォーマンスがいいのでしょうか？
トレーニングデータには10864行と12列があり、3つの列に欠損データが含まれています。各列で、データの半分未満しか欠損していません。したがって、列を削除すると多くの有用な情報が削除されるため、補完のパフォーマンスが向上するのは当然です。

```python
# Shape of training data (num_rows, num_columns)
print(X_train.shape)

# Number of missing values in each column of training data
missing_val_count_by_column = (X_train.isnull().sum())
print(missing_val_count_by_column[missing_val_count_by_column > 0])
```

```console:console
(10864, 12)
Car               49
BuildingArea    5156
YearBuilt       4307
dtype: int64
```

## まとめ
よくあることですが、欠損値を補完すると(アプローチ2およびアプローチ3)、欠損値のある列を単純に削除した場合(アプローチ1)に比べて、より良い結果が得られます。