Pythonでの欠損値補完(代入法) scikit-learnとpandas

機械学習での前処理で欠損値補完を何となく平均値や中央値などでやっていましたが、少し踏み込んで調べてみました。欠損値も機械学習で補完する方法できないかな、程度に思っていましたが、やはりありますね。
欠損値補完は統計学の世界では[「代入法」](https://ja.wikipedia.org/wiki/%E4%BB%A3%E5%85%A5%E6%B3%95_(%E7%B5%B1%E8%A8%88%E5%AD%A6))、欠損値は[「欠測データ(missing data)」](https://ja.wikipedia.org/wiki/%E6%AC%A0%E6%B8%AC%E3%83%87%E3%83%BC%E3%82%BF)と呼ぶようです。
章立て・内容は以下のScikit-Learnユーザガイドをベースにしています。

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

Scikit-Learnユーザガイドとは別にWikipediaの「代入法」はもっと大きな概念ですし、日本語なのでわかりやすいです。

https://ja.wikipedia.org/wiki/%E4%BB%A3%E5%85%A5%E6%B3%95_(%E7%B5%B1%E8%A8%88%E5%AD%A6)

# 欠損値補完方法(代入法)
## 種類
### 補完元の特徴量基準での分類
補完に使う特徴量を基準とすると大きく以下の2種類の方法があります。

1. **単変量補完**(Univariate feature imputation): 欠損値が生じている特徴量の欠損していない値(平均値など)を使って補完
2. **多変量補完**(Multivariate feature imputation): 欠損値が生じている特徴量以外の特徴量も含めて使って補完

### 補完先特徴量での分類
補完する対象の特徴量を基準とすると大きく以下の2種類の方法があります。

1. **単一代入法**(Single Imputation): 代入(補完)する欠損値を**1つのみ**提供する作成
2. **多重代入法**(Multiple Imputation): 代入(補完)する欠損値を**複数**作成し、複数のデータセットで機械学習等をし、結果を統合する

このサイトも参考にしました。

https://surveymethods.com/when-to-use-single-imputation-or-multiple-imputation/

## 単変量補完
Scikit-Learnでは[`SimpleImputer`関数](https://scikit-learn.org/stable/modules/generated/sklearn.impute.SimpleImputer.html)を使って単変量欠損値補完をします。

### 平均値補完
以下のデータで、平均値で欠損値補完をします。

|index|特徴量1|特徴量2|
|:-:|:-:|:-:|
|0|1|NaN -> 5|
|1|3|5|
|2|NaN -> 2|NaN -> 5|
|平均|2=$\frac{1+3}{2}$|5=$\frac{5}{1}$|

#### 平均値補完: Scikit-learn `SimpleImputer`関数

`SimpleImputer`関数はデフォルトで平均値補完です。String型の特徴量を含んでいるとデフォルト設定(平均値補完)ではエラーとなるので注意しましょう。

```python
import numpy as np
import pandas as pd
from sklearn.impute import SimpleImputer

df_train = pd.DataFrame([[1, np.nan, 'cat1'],
                        [3, 5, 'cat1'],
                        [np.nan, np.nan, np.nan]])
print(SimpleImputer().fit_transform(df_train.iloc[:,[0,1]]))
```


```:出力結果
[[1. 5.]
 [3. 5.]
 [2. 5.]]
```

#### 平均値補完: Pandas `fillna`関数
DataFrame使った平均値補完はこう書きます。訓練時はこれでいいんですが、推論時はやりにくそうな気がします。String型の特徴量を含んでいると欠損値は`NaN`のままです。

```python
print(df_train.fillna(df_train.mean()))
```

### 最頻値補完
カテゴリ型の特徴量の場合はパラメータ`strategy`に`most_frequent`を使うのが一般的かと思います。

|index|特徴量|
|:-:|:-:|
|0|cat1|
|1|cat1|
|2|NaN -> cat1|
|最頻値|cat1|

#### 最頻値補完: Scikit-learn `SimpleImputer`関数
平均値補完で使ったDataFrameをそのまま使います。

```python
print(SimpleImputer(strategy='most_frequent').fit_transform(df_train.iloc[:,[2]]))
```

```:出力結果
[['cat1']
 ['cat1']
 ['cat1']]
```

`None`の場合は`missing_values`で指定しましょう。こんなことあるかわかりませんが、`None`と`np.nan`の両方を対象に一括補完はできないようです。指定できるのは1つ。

```python
> SimpleImputer(missing_values=None, strategy='most_frequent').fit_transform(df_train.iloc[:,[2]])
[['cat1']
 ['cat1']
 ['cat1']]
```

#### 最頻値補完: Pandas `fillna`関数
(実質Seriesだけど)DataFrame使った平均値補完はこう書きます。やはり、訓練時はこれでいいんですが、推論時はやりにくそうな気がします。`SimpleImputer`関数と違って`None`も補完対象としてくれます。

```python
print(df_train[2].fillna(df_train[2].mode()[0]))
```

```:出力結果
0    cat1
1    cat1
2    cat1
Name: 2, dtype: object
```

### `SimpleImputer`と`add_indicator`オプション
`SimpleImputer`に`add_indicator`オプションがあります。欠損値だったデータを1としてマーキングしてくれます。以下は平均値補完のデータ例です。これにより、欠損値だったけど平均値等で補完したものかがわかり、1つの特徴量として機械学習モデルに使えます。

|index|特徴量|Indicator|
|:-:|:-:|:-:|
|0|1|0|
|1|3|0|
|2|NaN -> 2|1|

パラメータ`add_indicator`に`True`を渡すだけです。Numpy配列で返り、特徴量が増えるので、複数特徴量に使った場合に後処理が少し面倒な気がします。
同じことが後述する[`MissingIndicator `関数](https://scikit-learn.org/stable/modules/generated/sklearn.impute.MissingIndicator.html)を使ってできます。

```python
print(SimpleImputer(add_indicator=True).fit_transform(df_train.iloc[:,[0]]))
```

```:出力結果
[[1. 0.]
 [3. 0.]
 [2. 1.]]
```

## 多変量補完
Scikit-Learnでは[`IterativeImputer`関数](https://scikit-learn.org/stable/modules/generated/sklearn.impute.IterativeImputer.html)を使うか[`KNNImputer`関数](https://scikit-learn.org/stable/modules/generated/sklearn.impute.KNNImputer.html)を使います。

### [`IterativeImputer`関数](https://scikit-learn.org/stable/modules/generated/sklearn.impute.IterativeImputer.html)
[`IterativeImputer`関数](https://scikit-learn.org/stable/modules/generated/sklearn.impute.IterativeImputer.html)は、2021年6月の最新Scikit-LearnのVer0.24.2では**experimental(実験的)**な扱いのため、ご注意ください。以下はその記述の抜粋。

> Note This estimator is still **experimental** for now: default parameters or details of behaviour might change without any deprecation cycle. Resolving the following issues would help stabilize IterativeImputer: convergence criteria (#14338), default estimators (#13286), and use of random state (#15611). To use it, you need to explicitly import enable_iterative_imputer.

適当な配列を作ってみました。特徴量1の2倍が特徴量2の配列で、両者ともに1つずつ欠損値があります。
**1回の関数呼出で両特徴量を一気に欠損値補完**してくれるのが非常に嬉しいです。

|index|特徴量1|特徴量2|
|:-:|:-:|:-:|
|0|1|2|
|1|2|4|
|2|3|6|
|3|4|NaN|
|4|NaN|10|

#### Default(ベイジアンリッジ)実行
Defaultのestimatorはベイジアンリッジ(`BayesianRidge`)です。調べていないですが、本当はおそらくデータ標準化を事前にしておいた方がいいのでしょう。今回は、その辺は無視しておきます。
`from sklearn.experimental import enable_iterative_imputer`は、おまじない部分で、**experimental**だということを強く意識させたいのでしょう。
ちなみに**string型の項目使うとエラー起きます**。

```python
import numpy as np
import pandas as pd
from sklearn.experimental import enable_iterative_imputer
from sklearn.impute import IterativeImputer

print(IterativeImputer().fit_transform([[1, 2], 
                                        [2, 4], 
                                        [3, 6], 
                                        [np.nan, 8], 
                                        [5, np.nan]]))
```

欠損値部分はだいたい合っていますね。

```:出力結果
[[1.         2.        ]
 [2.         4.        ]
 [3.         6.        ]
 [4.00399557 8.        ]
 [5.         9.9920119 ]]
```

#### RandomForestRegressor
何かとお手軽なランダムフォレストを使ってみます。

```python
from sklearn.ensemble import RandomForestRegressor
print(IterativeImputer(RandomForestRegressor()).fit_transform([[1, 2], 
                                                               [2, 4], 
                                                               [3, 6], 
                                                               [np.nan, 8], 
                                                               [5, np.nan]]))
```

ベイジアンリッジより結果が悪いですが、データが少量なのでそんなものでしょう。

```:出力結果
[[1.   2.  ]
 [2.   4.  ]
 [3.   6.  ]
 [4.33 8.  ]
 [5.   7.22]]
```

#### DataFrame化
DataFrame化パターンも書いておきます。
[KaggleのTitanicチャレンジ](https://www.kaggle.com/c/titanic)の[データ](https://www.kaggle.com/c/titanic/data?select=train.csv)を使います。説明変数の数値系項目だけを読み込みます。

```python
import pandas as pd

df = pd.read_csv('./titanic/train.csv', usecols=[2, 5, 6, 7, 9])
print(df.info())
```

Ageに欠損値があるのがわかります。

```:df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 891 entries, 0 to 890
Data columns (total 5 columns):
 #   Column  Non-Null Count  Dtype  
---  ------  --------------  -----  
 0   Pclass  891 non-null    int64  
 1   Age     714 non-null    float64
 2   SibSp   891 non-null    int64  
 3   Parch   891 non-null    int64  
 4   Fare    891 non-null    float64
dtypes: float64(2), int64(3)
memory usage: 34.9 KB
```

`df_imputed.columns = df.columns`で列名をコピーしています。

```python
df_imputed = pd.DataFrame(IterativeImputer().fit_transform(df))
df_imputed.columns = df.columns
print(df_imputed.info())
```

欠損値を埋めてくれているのがわかります。

```:df_imputed.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 891 entries, 0 to 890
Data columns (total 5 columns):
 #   Column  Non-Null Count  Dtype  
---  ------  --------------  -----  
 0   Pclass  891 non-null    float64
 1   Age     891 non-null    float64
 2   SibSp   891 non-null    float64
 3   Parch   891 non-null    float64
 4   Fare    891 non-null    float64
dtypes: float64(5)
memory usage: 34.9 KB
```

#### `IterativeImputer`と`add_indicator`オプション
`IterativeImputer`にも`SimpleImputer`と同じく`add_indicator`オプションがあります。Default値は`False`ですが`True`を渡します。先程と違い、欠損値がない特徴量の列も3列目に加えました。

```python
print(IterativeImputer(add_indicator=True).fit_transform([[1, 2, 1],
                                                          [2, 4, 2], 
                                                          [3, 6, 3], 
                                                          [np.nan, 8, 4], 
                                                          [5, np.nan, 5]]))
```

**欠損値がある特徴量の数だけ列が増えます**(欠損のない特徴量に対する列は増えない)。DataFrameの場合、列名つけ直すのが少し面倒そうです。

```:出力結果
[[1.         2.         1.         0.         0.        ]
 [2.         4.         2.         0.         0.        ]
 [3.         6.         3.         0.         0.        ]
 [4.0000001  8.         4.         1.         0.        ]
 [5.         9.99999974 5.         0.         1.        ]]
```

#### 多重代入法(Multiple Imputation)で使う
多重代入法(Multiple Imputation)で使うには、パラメータ`sample_posterior`に`True`を渡す必要があり、その前提としてestimatorの`predict`関数が`return_std`パラメータを使える必要があります。予測値の標準偏差を返せる、という前提を満たすestimatorは非常に限られるっぽく、軽く調べた限りでは以下の関数くらいです(5分程度しか調べていないです)。

- [`BayesianRidge`関数](https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.BayesianRidge.html)
- [`GaussianProcessRegressor`関数](https://scikit-learn.org/stable/modules/generated/sklearn.gaussian_process.GaussianProcessRegressor.html)
- [`ARDRegression`関数](https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.ARDRegression.html)

多重代入法を試しはしませんでしたが、これで動きそう、というリンクだけ載せておきます。

https://scikit-learn.org/stable/auto_examples/impute/plot_missing_values.html

https://stackoverflow.com/questions/58613108/imputing-missing-values-using-sklearn-iterativeimputer-class-for-mice/58615845#58615845

#### 0 or 1 の場合
boolean系の変数へどうするのか？と思いました。[ユーザガイド「6.4.3. Multivariate feature imputation」](https://scikit-learn.org/stable/modules/impute.html#multivariate-feature-imputation)のサンプルコードでやっているように`np.round`っぽいです。

```python
>>> import numpy as np
>>> from sklearn.experimental import enable_iterative_imputer
>>> from sklearn.impute import IterativeImputer
>>> imp = IterativeImputer(max_iter=10, random_state=0)
>>> imp.fit([[1, 2], [3, 6], [4, 8], [np.nan, 3], [7, np.nan]])
IterativeImputer(random_state=0)
>>> X_test = [[np.nan, 2], [6, np.nan], [np.nan, 6]]
>>> # the model learns that the second feature is double the first
>>> print(np.round(imp.transform(X_test)))
[[ 1.  2.]
 [ 6. 12.]
 [ 3.  6.]]
```

ここでもそんなQAあり。

https://datascience.stackexchange.com/questions/87605/iterativeimputer-returning-0-and-other-wierd-results

### [`KNNImputer`関数](https://scikit-learn.org/stable/modules/generated/sklearn.impute.KNNImputer.html)
KNNで欠損値補完するのであれば、[`KNNImputer`関数](https://scikit-learn.org/stable/modules/generated/sklearn.impute.KNNImputer.html)があります。こちらはexperimentalではないので、少し安心です。
KNNについては、以前はじパタで勉強したときに[記事「はじパタ全力解説: 第5章 k最近傍法(kNN法)」](https://qiita.com/FukuharaYohei/items/0314f73cebb471deb515)を書きました。そこでは分類の方法と覚えていましたが、回帰でも使えるのですね。

https://datachemeng.com/knn/

`IterativeImputer`のときと同じ配列を使います。特徴量1の2倍が特徴量2の配列で、両者ともに1つずつ欠損値があります。ただ、少量データのKNNなので、実行結果は意味ないですが、プログラムの書き方参考程度で。
こちらも**1回の関数呼出で両特徴量を一気に欠損値補完**してくれるのが非常に嬉しいです。

|index|特徴量1|特徴量2|
|:-:|:-:|:-:|
|0|1|2|
|1|2|4|
|2|3|6|
|3|4|NaN|
|4|NaN|10|


```python
import numpy as np
import pandas as pd
from sklearn.impute import KNNImputer

print(KNNImputer(n_neighbors=2).fit_transform([[1, 2], 
                                        [2, 4], 
                                        [3, 6], 
                                        [np.nan, 8], 
                                        [5, np.nan]]))
```

`IterativeImputer`でベイジアンリッジやランダムフォレスト使ったときに比べるとひどい結果です。ですが、データが悪いだけ。

```:出力結果
[[1.  2. ]
 [2.  4. ]
 [3.  6. ]
 [2.5 8. ]
 [5.  5. ]]
```

少し大きな[カリフォルニア住宅価格のデータセット](https://scikit-learn.org/stable/datasets/real_world.html#california-housing-dataset)で試してみます。

```python
from sklearn.datasets import fetch_california_housing

dataset = fetch_california_housing()
df = pd.DataFrame(dataset.data[:, [6, 7]], 
                       columns=[dataset.feature_names[6], dataset.feature_names[7]])
print(df.info())
```

約2万件あって欠損値がないです。

```:出力結果
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 20640 entries, 0 to 20639
Data columns (total 2 columns):
 #   Column     Non-Null Count  Dtype  
---  ------     --------------  -----  
 0   Latitude   20640 non-null  float64
 1   Longitude  20640 non-null  float64
dtypes: float64(2)
memory usage: 322.6 KB
None
```

約1/10のデータを欠損させます。

```python
# 1/10をTrueにする
mask = ~np.random.default_rng().integers(10, size=df.shape).astype(np.bool_)
df[mask] = np.nan
print(df.info())
```

2000件くらいがNullになりましたね。

```:出力結果
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 20640 entries, 0 to 20639
Data columns (total 2 columns):
 #   Column     Non-Null Count  Dtype  
---  ------     --------------  -----  
 0   Latitude   18496 non-null  float64
 1   Longitude  18610 non-null  float64
dtypes: float64(2)
memory usage: 322.6 KB
None
```

KNNで欠損値補完。

```python
df_imputed = pd.DataFrame(KNNImputer(n_neighbors=2).fit_transform(df))
df_imputed.columns = df.columns
print(df_imputed.info())
```

欠損値なくなっていますね。

```:出力結果
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 20640 entries, 0 to 20639
Data columns (total 2 columns):
 #   Column     Non-Null Count  Dtype  
---  ------     --------------  -----  
 0   Latitude   20640 non-null  float64
 1   Longitude  20640 non-null  float64
dtypes: float64(2)
memory usage: 322.6 KB
None
```

散布図でぱっと見るだけしてみます。本当はパラメータ`add_indicator`などを欠損値レコードをマーキングして色を変えるとか、推論までして精度見るとかすればいいのでしょうか、面倒なので省略。

```python
import matplotlib.pyplot as plt

fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(12, 3))
df.plot.scatter(x='Latitude', y='Longitude', ax=axes[0])
df_imputed.plot.scatter(x='Latitude', y='Longitude', ax=axes[1])
plt.show()
```

左が欠損値補完前で右が後。あまり変わらないですね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/ba397ebe-e68c-7a80-e4c2-bb88a43f4beb.png)

今回、試していませんが`missingpy`というパッケージに`missforest`関数があるようです。

https://github.com/epsilon-machine/missingpy#2-random-forest-imputation-missforest

https://ichi.pro/python-to-missforest-arugorizumu-o-shiyoshite-ketsu-haka-de-ta-o-hokansuru-hoho-260884555877274

# 欠損値のマーキング `MissingIndicator`
Scikit-learnに欠損値をマーキングする[`MissingIndicator`関数](https://scikit-learn.org/stable/modules/generated/sklearn.impute.MissingIndicator.html)があります。欠損値補完する関数の`add_indicator`オプションの機能を切り出した関数です。
「`SimpleImputer`と`add_indicator`オプション」で書きましたが、例えば以下のデータでIndicator列を作り、補完したデータ"2"は他と違うんだよ、という意味を持たせます。そして、以下の用途に使います(他用途もあるかも)。

1. Indicatorも一つの特徴量とする
1. グラフ化してIndocatorによって色など変えて欠損値補完がうまくできているかを確認

|index|特徴量|Indicator|
|:-:|:-:|:-:|
|0|1|False|
|1|3|False|
|2|NaN|True|

## 基本の使い方
では、簡単な使い方です。

```python
import numpy as np
from sklearn.impute import MissingIndicator
X = np.array([[1],
              [3],
              [np.nan]])
print(MissingIndicator().fit_transform(X))
```

`add_indicator`オプションで使ったときと違い、**`False`/`True`で返ってくる**ようです。

```:出力結果
[[False]
 [False]
 [ True]]
```

## 欠損値が無い特徴量がある場合(デフォルト)
`add_indicator`オプションと同じく**デフォルトで欠損値がない特徴量のIndicatorは作られません**。

```python
X = np.array([[1, 1],
              [3, 1],
              [np.nan, 1]])
print(MissingIndicator().fit_transform(X))
```

2列目のIndicatorなし。

```:出力結果
[[False]
 [False]
 [ True]]
```

## 欠損値が無い特徴量がある場合(features='all')
**パラメータ`features`に`all`を指定すると、欠損値がない特徴量のIndicatorが作られます**。

```python
X = np.array([[1, 1],
              [3, 1],
              [np.nan, 1]])
print(MissingIndicator(features='all').fit_transform(X))
```

```:出力結果
[[False False]
 [False False]
 [ True False]]
```

## fitとtransform
訓練とテストでデータ分けた場合です。`fit`と`transform`を分けて実行します。

```python
X_train = np.array([[1, 1],
                    [3, 1],
                    [np.nan, 1]])
X_test = np.array([[1, 1],
                   [np.nan, 1],
                   [3, 1]])
indicator = MissingIndicator()
indicator.fit(X_train)
print(indicator.transform(X_test))
```

2行目に欠損値を示す`True`が来ていますね。

```:出力結果
[[False]
 [ True]
 [False]]
```

## fitとtransform(欠損値がどの特徴量で発生するかわからない場合)
デフォルトパラメータで**`fit`時にある特徴量に欠損値がなく、`transform`時にその特徴量で欠損値が出てしまうとエラー発生**します。

```python
X_train = np.array([[1, 1],
                    [3, 1],
                    [np.nan, 1]])
X_test = np.array([[1, 1],
                   [np.nan, 1],
                   [3, np.nan]])

indicator = MissingIndicator()
indicator.fit(X_train)
print(indicator.transform(X_test))
```

エラー内容。

```python:エラー
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-26-0a974e4754a8> in <module>
     11 indicator = MissingIndicator()
     12 indicator.fit(X_train)
---> 13 print(indicator.transform(X_test))

~/Apps/python/venv/py39/lib/python3.9/site-packages/sklearn/impute/_base.py in transform(self, X)
    809             features_diff_fit_trans = np.setdiff1d(features, self.features_)
    810             if (self.error_on_new and features_diff_fit_trans.size > 0):
--> 811                 raise ValueError("The features {} have missing values "
    812                                  "in transform but have no missing values "
    813                                  "in fit.".format(features_diff_fit_trans))

ValueError: The features [1] have missing values in transform but have no missing values in fit.
```

エラーを起こさせないためには、パラメーター`features`に'all'を渡します。

```python
X_train = np.array([[1, 1],
                    [3, 1],
                    [np.nan, 1]])
X_test = np.array([[1, 1],
                   [np.nan, 1],
                   [3, np.nan]])

indicator = MissingIndicator(features='all')
indicator.fit(X_train)
print(indicator.transform(X_test))
```

全特徴量の欠損値が出力。

```:出力結果
[[False False]
 [ True False]
 [False  True]]
```

またはパラメータ`error_on_new`に`False`を指定。

```python
X_train = np.array([[1, 1],
                    [3, 1],
                    [np.nan, 1]])
X_test = np.array([[1, 1],
                   [np.nan, 1],
                   [3, np.nan]])

indicator = MissingIndicator(error_on_new=False)
indicator.fit(X_train)
print(indicator.transform(X_test))
```

この場合は、`fit`時に欠損値がなかった特徴量のIndicatorは返りません。

```:出力結果
[[False]
 [ True]
 [False]]
```

## 実用
[ユーザガイド「6.4.6. Marking imputed values」](https://scikit-learn.org/stable/modules/impute.html#marking-imputed-values)に記載がありますが、[`FeatureUnion`関数](https://scikit-learn.org/stable/modules/generated/sklearn.pipeline.FeatureUnion.html)や`Pileline`と組み合わせるのが`MissingIndicator`の実用的な使い方のようです。
今回はそこまで試しませんが、いずれ更新するかもしれません。
`FeatureUnion`関数に関しては、すぐに理解できなかったため、以下の記事を書きました。

https://qiita.com/FukuharaYohei/items/b0424e2e5224795dcd16

# 部分削除方法
欠損値補完じゃないけど、部分削除という方法もあります。以下の2つの手法があります。

1. **リストワイズ削除**(完全ケース削除): レコード単位で、1つでも欠損値があればレコード削除
2. **ペアワイズ削除**: 回帰・分類等目的に必要な特徴量に欠損値が生じていればレコード削除

DataFrame上では[`dropna`関数](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.dropna.html)を使うだけです。

```python
df.dropna()
```

もっと詳しい使い方は、この辺の記事見るとすぐに理解できると思います。

https://qiita.com/0NE_shoT_/items/8db6d909e8b48adcb203

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

# 参考リンク
以下の記事を参考にしました。

https://ichi.pro/python-de-no-ketsu-haka-de-ta-hokan-shuho-nikansuru-kuikku-gaido-2020-92431787695134

https://www.haya-programming.com/entry/2019/11/05/225424

https://qiita.com/DS27/items/1a79220f982e0094f1fc


https://ichi.pro/python-to-missforest-arugorizumu-o-shiyoshite-ketsu-haka-de-ta-o-hokansuru-hoho-260884555877274