Scikit-learn の impute で欠損値を埋める

[Scikit-learn の impute](https://scikit-learn.org/stable/modules/impute.html) は、機械学習の前処理として欠損データを埋めるのに使われます。簡単なデータを利用して挙動を確認してみました。

# テスト用データ作成


```Python
data = {
    'A': [a for a in range(10)],
    'B': [a * 2 for a in range(10)],
    'C': [a * 3 for a in range(10)],
    'D': [a * 4 for a in range(10)],
        }
```


```Python
import pandas as pd

data = pd.DataFrame(data)
data
```




<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>A</th>
      <th>B</th>
      <th>C</th>
      <th>D</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>0</td>
      <td>0</td>
      <td>0</td>
      <td>0</td>
    </tr>
    <tr>
      <th>1</th>
      <td>1</td>
      <td>2</td>
      <td>3</td>
      <td>4</td>
    </tr>
    <tr>
      <th>2</th>
      <td>2</td>
      <td>4</td>
      <td>6</td>
      <td>8</td>
    </tr>
    <tr>
      <th>3</th>
      <td>3</td>
      <td>6</td>
      <td>9</td>
      <td>12</td>
    </tr>
    <tr>
      <th>4</th>
      <td>4</td>
      <td>8</td>
      <td>12</td>
      <td>16</td>
    </tr>
    <tr>
      <th>5</th>
      <td>5</td>
      <td>10</td>
      <td>15</td>
      <td>20</td>
    </tr>
    <tr>
      <th>6</th>
      <td>6</td>
      <td>12</td>
      <td>18</td>
      <td>24</td>
    </tr>
    <tr>
      <th>7</th>
      <td>7</td>
      <td>14</td>
      <td>21</td>
      <td>28</td>
    </tr>
    <tr>
      <th>8</th>
      <td>8</td>
      <td>16</td>
      <td>24</td>
      <td>32</td>
    </tr>
    <tr>
      <th>9</th>
      <td>9</td>
      <td>18</td>
      <td>27</td>
      <td>36</td>
    </tr>
  </tbody>
</table>
</div>




```Python
import numpy as nan
data2 = pd.DataFrame(data)
#data2['B'][3] = np.nan
data2.loc.__setitem__(((2), ("B")), np.nan)
data2.loc.__setitem__(((3), ("C")), np.nan)
data2.loc.__setitem__(((5), ("C")), np.nan)
data2.loc.__setitem__(((6), ("D")), np.nan)
data2.loc.__setitem__(((7), ("D")), np.nan)
data2
```




<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>A</th>
      <th>B</th>
      <th>C</th>
      <th>D</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>0</td>
      <td>0.0</td>
      <td>0.0</td>
      <td>0.0</td>
    </tr>
    <tr>
      <th>1</th>
      <td>1</td>
      <td>2.0</td>
      <td>3.0</td>
      <td>4.0</td>
    </tr>
    <tr>
      <th>2</th>
      <td>2</td>
      <td>NaN</td>
      <td>6.0</td>
      <td>8.0</td>
    </tr>
    <tr>
      <th>3</th>
      <td>3</td>
      <td>6.0</td>
      <td>NaN</td>
      <td>12.0</td>
    </tr>
    <tr>
      <th>4</th>
      <td>4</td>
      <td>8.0</td>
      <td>12.0</td>
      <td>16.0</td>
    </tr>
    <tr>
      <th>5</th>
      <td>5</td>
      <td>10.0</td>
      <td>NaN</td>
      <td>20.0</td>
    </tr>
    <tr>
      <th>6</th>
      <td>6</td>
      <td>12.0</td>
      <td>18.0</td>
      <td>NaN</td>
    </tr>
    <tr>
      <th>7</th>
      <td>7</td>
      <td>14.0</td>
      <td>21.0</td>
      <td>NaN</td>
    </tr>
    <tr>
      <th>8</th>
      <td>8</td>
      <td>16.0</td>
      <td>24.0</td>
      <td>32.0</td>
    </tr>
    <tr>
      <th>9</th>
      <td>9</td>
      <td>18.0</td>
      <td>27.0</td>
      <td>36.0</td>
    </tr>
  </tbody>
</table>
</div>




```Python
%matplotlib inline
import matplotlib.pyplot as plt
plt.plot(data2)
```

![Imputer_4_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/1108d962-8812-7318-46b3-a464d3969c7d.png)



以上のようにして、欠損値を１つ含むB列、２つ含むC列、連続した欠損値を含むD列を作成しました。

# SimpleImputer

SimpleImputerクラスは、欠損値を入力するための基本的な計算法を提供します。欠損値は、指定された定数値を用いて、あるいは欠損値が存在する各列の統計量（平均値、中央値、または最も頻繁に発生する値）を用いて計算することができます。

## default(mean)

デフォルトは平均値で埋めます。


```Python
from sklearn.impute import SimpleImputer

imp = SimpleImputer() #missing_values=np.nan, strategy='mean')
data3 = pd.DataFrame(imp.fit_transform(data2))
data3
```




<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>0</th>
      <th>1</th>
      <th>2</th>
      <th>3</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>0.0</td>
      <td>0.000000</td>
      <td>0.000</td>
      <td>0.0</td>
    </tr>
    <tr>
      <th>1</th>
      <td>1.0</td>
      <td>2.000000</td>
      <td>3.000</td>
      <td>4.0</td>
    </tr>
    <tr>
      <th>2</th>
      <td>2.0</td>
      <td>9.555556</td>
      <td>6.000</td>
      <td>8.0</td>
    </tr>
    <tr>
      <th>3</th>
      <td>3.0</td>
      <td>6.000000</td>
      <td>13.875</td>
      <td>12.0</td>
    </tr>
    <tr>
      <th>4</th>
      <td>4.0</td>
      <td>8.000000</td>
      <td>12.000</td>
      <td>16.0</td>
    </tr>
    <tr>
      <th>5</th>
      <td>5.0</td>
      <td>10.000000</td>
      <td>13.875</td>
      <td>20.0</td>
    </tr>
    <tr>
      <th>6</th>
      <td>6.0</td>
      <td>12.000000</td>
      <td>18.000</td>
      <td>16.0</td>
    </tr>
    <tr>
      <th>7</th>
      <td>7.0</td>
      <td>14.000000</td>
      <td>21.000</td>
      <td>16.0</td>
    </tr>
    <tr>
      <th>8</th>
      <td>8.0</td>
      <td>16.000000</td>
      <td>24.000</td>
      <td>32.0</td>
    </tr>
    <tr>
      <th>9</th>
      <td>9.0</td>
      <td>18.000000</td>
      <td>27.000</td>
      <td>36.0</td>
    </tr>
  </tbody>
</table>
</div>




```Python
%matplotlib inline
import matplotlib.pyplot as plt
plt.plot(data3)
```


![Imputer_7_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/3466fd2a-8a24-82a5-894d-f8876707e067.png)



以上のように、データの種類によっては、平均値で埋めるのは不自然になったりしますね。

## median

欠損値を中央値で埋めることもできます。


```Python
from sklearn.impute import SimpleImputer

imp = SimpleImputer(missing_values=np.nan, strategy='median')
data4 = pd.DataFrame(imp.fit_transform(data2))
data4
```




<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>0</th>
      <th>1</th>
      <th>2</th>
      <th>3</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>0.0</td>
      <td>0.0</td>
      <td>0.0</td>
      <td>0.0</td>
    </tr>
    <tr>
      <th>1</th>
      <td>1.0</td>
      <td>2.0</td>
      <td>3.0</td>
      <td>4.0</td>
    </tr>
    <tr>
      <th>2</th>
      <td>2.0</td>
      <td>10.0</td>
      <td>6.0</td>
      <td>8.0</td>
    </tr>
    <tr>
      <th>3</th>
      <td>3.0</td>
      <td>6.0</td>
      <td>15.0</td>
      <td>12.0</td>
    </tr>
    <tr>
      <th>4</th>
      <td>4.0</td>
      <td>8.0</td>
      <td>12.0</td>
      <td>16.0</td>
    </tr>
    <tr>
      <th>5</th>
      <td>5.0</td>
      <td>10.0</td>
      <td>15.0</td>
      <td>20.0</td>
    </tr>
    <tr>
      <th>6</th>
      <td>6.0</td>
      <td>12.0</td>
      <td>18.0</td>
      <td>14.0</td>
    </tr>
    <tr>
      <th>7</th>
      <td>7.0</td>
      <td>14.0</td>
      <td>21.0</td>
      <td>14.0</td>
    </tr>
    <tr>
      <th>8</th>
      <td>8.0</td>
      <td>16.0</td>
      <td>24.0</td>
      <td>32.0</td>
    </tr>
    <tr>
      <th>9</th>
      <td>9.0</td>
      <td>18.0</td>
      <td>27.0</td>
      <td>36.0</td>
    </tr>
  </tbody>
</table>
</div>




```Python
%matplotlib inline
import matplotlib.pyplot as plt
plt.plot(data4)
```


![Imputer_10_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/ce9de746-4f10-c40b-c405-f7815d24d65e.png)



平均値と同様、中央値で埋める場合も、データの内容によっては不自然な埋め型になってしまうことがありますね。

## most_frequent

最頻値で埋めることもできます。


```
from sklearn.impute import SimpleImputer

imp = SimpleImputer(missing_values=np.nan, strategy='most_frequent')
data5 = pd.DataFrame(imp.fit_transform(data2))
data5
```




<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>0</th>
      <th>1</th>
      <th>2</th>
      <th>3</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>0.0</td>
      <td>0.0</td>
      <td>0.0</td>
      <td>0.0</td>
    </tr>
    <tr>
      <th>1</th>
      <td>1.0</td>
      <td>2.0</td>
      <td>3.0</td>
      <td>4.0</td>
    </tr>
    <tr>
      <th>2</th>
      <td>2.0</td>
      <td>0.0</td>
      <td>6.0</td>
      <td>8.0</td>
    </tr>
    <tr>
      <th>3</th>
      <td>3.0</td>
      <td>6.0</td>
      <td>0.0</td>
      <td>12.0</td>
    </tr>
    <tr>
      <th>4</th>
      <td>4.0</td>
      <td>8.0</td>
      <td>12.0</td>
      <td>16.0</td>
    </tr>
    <tr>
      <th>5</th>
      <td>5.0</td>
      <td>10.0</td>
      <td>0.0</td>
      <td>20.0</td>
    </tr>
    <tr>
      <th>6</th>
      <td>6.0</td>
      <td>12.0</td>
      <td>18.0</td>
      <td>0.0</td>
    </tr>
    <tr>
      <th>7</th>
      <td>7.0</td>
      <td>14.0</td>
      <td>21.0</td>
      <td>0.0</td>
    </tr>
    <tr>
      <th>8</th>
      <td>8.0</td>
      <td>16.0</td>
      <td>24.0</td>
      <td>32.0</td>
    </tr>
    <tr>
      <th>9</th>
      <td>9.0</td>
      <td>18.0</td>
      <td>27.0</td>
      <td>36.0</td>
    </tr>
  </tbody>
</table>
</div>




```Python
%matplotlib inline
import matplotlib.pyplot as plt
plt.plot(data5)
```


![Imputer_13_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/c99ea769-e1d4-18f5-dced-fb609024f057.png)



最頻値がない場合は最初の値で埋めるようですね。

## constant

あらかじめ決まった数値を設定してそれで埋めることもできます。


```Python
from sklearn.impute import SimpleImputer

imp = SimpleImputer(missing_values=np.nan, strategy='constant', fill_value=99)
data6 = pd.DataFrame(imp.fit_transform(data2))
data6
```




<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>0</th>
      <th>1</th>
      <th>2</th>
      <th>3</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>0.0</td>
      <td>0.0</td>
      <td>0.0</td>
      <td>0.0</td>
    </tr>
    <tr>
      <th>1</th>
      <td>1.0</td>
      <td>2.0</td>
      <td>3.0</td>
      <td>4.0</td>
    </tr>
    <tr>
      <th>2</th>
      <td>2.0</td>
      <td>99.0</td>
      <td>6.0</td>
      <td>8.0</td>
    </tr>
    <tr>
      <th>3</th>
      <td>3.0</td>
      <td>6.0</td>
      <td>99.0</td>
      <td>12.0</td>
    </tr>
    <tr>
      <th>4</th>
      <td>4.0</td>
      <td>8.0</td>
      <td>12.0</td>
      <td>16.0</td>
    </tr>
    <tr>
      <th>5</th>
      <td>5.0</td>
      <td>10.0</td>
      <td>99.0</td>
      <td>20.0</td>
    </tr>
    <tr>
      <th>6</th>
      <td>6.0</td>
      <td>12.0</td>
      <td>18.0</td>
      <td>99.0</td>
    </tr>
    <tr>
      <th>7</th>
      <td>7.0</td>
      <td>14.0</td>
      <td>21.0</td>
      <td>99.0</td>
    </tr>
    <tr>
      <th>8</th>
      <td>8.0</td>
      <td>16.0</td>
      <td>24.0</td>
      <td>32.0</td>
    </tr>
    <tr>
      <th>9</th>
      <td>9.0</td>
      <td>18.0</td>
      <td>27.0</td>
      <td>36.0</td>
    </tr>
  </tbody>
</table>
</div>




```Python
%matplotlib inline
import matplotlib.pyplot as plt
plt.plot(data6)
```



![Imputer_16_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/31aa1f57-55ba-6d19-e56f-09381856d185.png)



まあ、なんて不自然なんでしょ。

# KNNImputer

KNNImputerクラスは、k-Nearest Neighborsアプローチを使用して欠損値を埋めます。デフォルトでは、欠落値をサポートするユークリッド距離メトリックであるnan_euclidean_distancesが、最近傍を見つけるために使用されます。隣人の特徴は，一様に平均化されるか，各隣人までの距離によって重み付けされます．サンプルに1つ以上の特徴が欠落している場合，そのサンプルの隣人は，入力される特定の特徴に応じて異なることがあります．利用可能な隣人の数がn_neighborsよりも少なく、訓練セットへの定義された距離がない場合、その特徴の訓練セット平均が入力中に使用されます。定義された距離を持つ隣人が少なくとも1つある場合、残りの隣人の加重平均または非加重平均が入力時に使用されます。

## n_neighbors=2

考慮に入れる隣人の数を明示的に n_neighbors=2 にしてみましょう。


```Python
from sklearn.impute import KNNImputer
imputer = KNNImputer(n_neighbors=2)
data7 = pd.DataFrame(imputer.fit_transform(data2))
data7
```




<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>0</th>
      <th>1</th>
      <th>2</th>
      <th>3</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>0.0</td>
      <td>0.0</td>
      <td>0.0</td>
      <td>0.0</td>
    </tr>
    <tr>
      <th>1</th>
      <td>1.0</td>
      <td>2.0</td>
      <td>3.0</td>
      <td>4.0</td>
    </tr>
    <tr>
      <th>2</th>
      <td>2.0</td>
      <td>4.0</td>
      <td>6.0</td>
      <td>8.0</td>
    </tr>
    <tr>
      <th>3</th>
      <td>3.0</td>
      <td>6.0</td>
      <td>9.0</td>
      <td>12.0</td>
    </tr>
    <tr>
      <th>4</th>
      <td>4.0</td>
      <td>8.0</td>
      <td>12.0</td>
      <td>16.0</td>
    </tr>
    <tr>
      <th>5</th>
      <td>5.0</td>
      <td>10.0</td>
      <td>15.0</td>
      <td>20.0</td>
    </tr>
    <tr>
      <th>6</th>
      <td>6.0</td>
      <td>12.0</td>
      <td>18.0</td>
      <td>18.0</td>
    </tr>
    <tr>
      <th>7</th>
      <td>7.0</td>
      <td>14.0</td>
      <td>21.0</td>
      <td>26.0</td>
    </tr>
    <tr>
      <th>8</th>
      <td>8.0</td>
      <td>16.0</td>
      <td>24.0</td>
      <td>32.0</td>
    </tr>
    <tr>
      <th>9</th>
      <td>9.0</td>
      <td>18.0</td>
      <td>27.0</td>
      <td>36.0</td>
    </tr>
  </tbody>
</table>
</div>




```Python
%matplotlib inline
import matplotlib.pyplot as plt
plt.plot(data7)
```



![Imputer_19_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/f3770fc4-55a2-ac5c-3880-68422c675083.png)



２連続で欠損しているような場合、うまく埋められないようですね。

## default(n_neighbors=5)

デフォルトでは隣人を５人まで考慮するようです。


```Python
from sklearn.impute import KNNImputer
imputer = KNNImputer()
data8 = pd.DataFrame(imputer.fit_transform(data2))
data8
```




<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>0</th>
      <th>1</th>
      <th>2</th>
      <th>3</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>0.0</td>
      <td>0.0</td>
      <td>0.0</td>
      <td>0.0</td>
    </tr>
    <tr>
      <th>1</th>
      <td>1.0</td>
      <td>2.0</td>
      <td>3.0</td>
      <td>4.0</td>
    </tr>
    <tr>
      <th>2</th>
      <td>2.0</td>
      <td>5.2</td>
      <td>6.0</td>
      <td>8.0</td>
    </tr>
    <tr>
      <th>3</th>
      <td>3.0</td>
      <td>6.0</td>
      <td>12.0</td>
      <td>12.0</td>
    </tr>
    <tr>
      <th>4</th>
      <td>4.0</td>
      <td>8.0</td>
      <td>12.0</td>
      <td>16.0</td>
    </tr>
    <tr>
      <th>5</th>
      <td>5.0</td>
      <td>10.0</td>
      <td>16.2</td>
      <td>20.0</td>
    </tr>
    <tr>
      <th>6</th>
      <td>6.0</td>
      <td>12.0</td>
      <td>18.0</td>
      <td>23.2</td>
    </tr>
    <tr>
      <th>7</th>
      <td>7.0</td>
      <td>14.0</td>
      <td>21.0</td>
      <td>23.2</td>
    </tr>
    <tr>
      <th>8</th>
      <td>8.0</td>
      <td>16.0</td>
      <td>24.0</td>
      <td>32.0</td>
    </tr>
    <tr>
      <th>9</th>
      <td>9.0</td>
      <td>18.0</td>
      <td>27.0</td>
      <td>36.0</td>
    </tr>
  </tbody>
</table>
</div>




```Python
%matplotlib inline
import matplotlib.pyplot as plt
plt.plot(data8)
```



![Imputer_22_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/2fd6a7df-3a05-40c5-239f-1a57d1bc1555.png)



D列の埋め方は比較的マシになりましたが、その代わりB列C列の埋め方に少し悪影響が出ましたね。

# まとめ

欠損値の埋め方に完璧な方法はたぶん存在しないので、データの特徴をよく考えながら、準最適な方法を選びましょう！