【Python】データ分析における欠損値対応の手法メモ

#はじめに

データ分析において、欠損値の処理は1つの重要なポイントだと思っています。
Kaggleでもスピード重視でこのパートを雑に行うとパフォーマンスが上がりません。
この記事では、コンペ等の分析の際によく使う欠損値の見つけ方と対処方法をざっくりと紹介します。
　
**前処理より前の簡単な分析に関する過去記事もよろしければご覧ください。**
https://qiita.com/ryo111/items/f62a43da6764f58823a5


#ライブラリ、データの準備

まずはデータ分析の主要ライブラリを読み込みます。

```py
import numpy as np
import pandas as pd
```

データはKaggleのHousePriceのTrainデータを使用します。
https://www.kaggle.com/c/house-prices-advanced-regression-techniques

```py
df = pd.read_csv('House Price/train.csv')
```
とりあえず頭の5行を見てみます。

```py
df.head()
```

<img width="695" alt="Screen Shot 2019-02-12 at 22.52.59.png" src="https://qiita-image-store.s3.amazonaws.com/0/278569/557f2a38-2038-f0a6-27af-4f111f0f3ac5.png">
列は全て表示できませんが、81列あることが分かります。

#欠損値の確認

さっそく欠損値を確認します。

```py
df.isnull().sum()
```
<img width="239" alt="Screen Shot 2019-02-12 at 23.01.07.png" src="https://qiita-image-store.s3.amazonaws.com/0/278569/2ebaecdb-363f-62ac-f677-73ddc7f79810.png">
このデータの場合は列数が多いので全て表示できません。
0も邪魔なので0より大きい数に絞り、さらにソートをかけるプログラムを加えます。

```py
train.isnull().sum()[train.isnull().sum()>0].sort_values()
```
<img width="179" alt="Screen Shot 2019-02-12 at 23.05.57.png" src="https://qiita-image-store.s3.amazonaws.com/0/278569/c1894a83-1fb8-8e16-f21e-4ad29fb4b0d6.png">
分かりやすく表示できました。かなりの欠損データがあるようです。

　
さらに欠損値を含む列のデータタイプを確認します。

```py
na_columns = df.isnull().sum()[df.isnull().sum()>0].sort_values().index.tolist()
df[na_columns].dtypes
```
<img width="193" alt="Screen Shot 2019-02-12 at 23.44.45.png" src="https://qiita-image-store.s3.amazonaws.com/0/278569/64b3a5ea-9e55-330e-c303-5784b76e5e56.png">

'MasVnrArea', 'GarageYrBlt', 'LotFrontage' が数値データで他はオブジェクトデータのようです。

#欠損値の確認(ビジュアル編)

### msno.barで正常値の棒グラフを表示

missingnoというライブラリを活用し、正常値（非欠損値）を棒グラフ化することで、
全体の欠損値の分布や分量を理解できます。

```py
#未インストールの方は
pip install missingno
```

```py
import missingno as msno #msnoをインポート
msno.bar(df);
```
<img width="1241" alt="Screen Shot 2019-02-14 at 12.52.55.png" src="https://qiita-image-store.s3.amazonaws.com/0/278569/d47a6cc2-0033-2d9f-d459-43488c71ef15.png">

上部には正常値の数量が表示されます。
Alley、PoolQC、MiscFeatureなんかは、ほぼ欠損値だということが一目でわかります。

### ヒートマップで欠損値を可視化

seabornのヒートマップを使ってビジュアルで欠損値の分布を確認できます。
今回は列数が大きいので、グラフ自体のサイズを大きくするよう調整しています。

```py
import seaborn as sns #seabornをインポート

plt.figure(figsize=(16,20)) #サイズ調整

sns.heatmap(df.isnull(), cbar=False); #ヒートマップ表示
```
<img width="940" alt="Screen Shot 2019-02-14 at 12.38.59.png" src="https://qiita-image-store.s3.amazonaws.com/0/278569/b418ecd2-4d3a-8e1a-8fd3-a8fdc9661e70.png">

**seabornのグラフ活用を解説した別記事もあります。**
https://qiita.com/ryo111/items/bf24c8cf508ad90cfe2e


#欠損値の処理

欠損値の処理方法はデータの内容に応じて様々です。
Kaggleなどのコンペティションであればデータの説明を、実データであればデータの集計背景を詳しく確認し、
カットのするのか、平均で埋めるのか、など適切な処理方法を選ぶ必要があります。
（ここでは様々な処理を行うコードを紹介しますが、状況に応じた処理の選択方法は別記事で解説予定です。）
　

### 欠損データを削除する
欠損データが少なく、正常なデータの量が十分に確保できる場合は削除が一番手っ取り早いです。

　
**■欠損値を含む"行"を削除**

```py
df = df.dropna() 
```
↑欠損のある行が多いとデータ量が減るので注意

**■欠損値を含む"列"を削除**

```py
df = df.dropna(axis=1)
```
↑同様に欠損のある列が多いとデータ量が減るので注意

**■指定した列の欠損値が含まれている行だけを削除**

```py
df = df.dropna(subset=['列名'])
```

**■指定した列をまるごと削除**　

```py
df = df.drop("列名", axis=1)
```
↑ある列の変数を分析に使わないと判断した場合に使います
　
### 欠損データに別の値を代入する


**■欠損値を任意の値で埋める**

```py
#0で埋める場合
df = df.fillna(0)
```

```py
#文字で埋める場合
df = df.fillna('任意の文字列')
```

```py
#列を選んで適用することも可能
df['列名'] = df['列名'].fillna(0)
```


**■欠損値を列の平均値で埋める**

```py
df = df.fillna(df.mean())
```

**■欠損値を列の中央値で埋める**

```py
df = df.fillna(df.median())
```

**■欠損値を列の最頻値で埋める**

```py
df = df.fillna(df.mode())
```


**■欠損値を列の1つ手前の値で埋める**

```py
df = df.fillna(method='ffill')
```

**■欠損値を列の直後の値で埋める**

```py
df = df.fillna(method='bfill')
```

**■列ごとに指定の値で欠損値を埋める**

```py
df = df.fillna({'列名1': 0, '列名2': 5000, '列名3': 35})
```

#まとめ
欠損値の見つけ方、捉え方を説明した後、簡単に基本的な処理方法を紹介しました。
欠損値処理は、データの特性に応じた柔軟な対応が求められるパートです。
自身も今後、様々なタイプのデータに触れることで対応能力を高めていきたいと考えています。



###参考
Missing data visualization module for Python.
https://github.com/ResidentMario/missingno