機械学習における前処理3　欠損値・外れ値・不均衡データ

[Aidemy](https://aidemy.net/)　2020/10/29
#はじめに
　こんにちは、んがょぺです！バリバリの文系ですが、AIの可能性に興味を持ったのがきっかけで、AI特化型スクール「Aidemy」に通い、勉強しています。ここで得られた知識を皆さんと共有したいと思い、Qiitaでまとめています。以前のまとめ記事も多くの方に読んでいただけてとても嬉しいです。ありがとうございます！
　今回は、機械学習の前処理の3つめの投稿になります。どうぞよろしくお願いします。

＊本記事は「Aidemy」での学習内容を「自分の言葉で」まとめたものになります。表現の間違いや勘違いを含む可能性があります。ご了承ください。

今回学ぶこと
・欠損値の取り扱い
・外れ値の取り扱い
・不均衡データの取り扱い

#欠損値の扱い
##欠損値について
・__欠損値__は「NaN」で表される__中身のないデータ__のことである。
・NaNがデータに存在する場合、__全体の平均や標準偏差を計算することができない__。
・欠損値を1つでも含むデータを__全て__削除してしまうとデータに無駄や偏りが生じる。

##欠損値発生のメカニズム
・前項で見たように、欠損値を含むデータは__適切に処理しなければデータ分析が行えない__。よって欠損値に対して適切な前処理を行う必要があるが、その方法は欠損値発生のメカニズムごとに異なる。

・欠損値発生のメカニズムには、以下の3種類がある。
 1.__MCAR:データの欠損する（NaNになる）確率が、データそのものと無関係である（無作為に生じている）時__
　　　　ex)コンピュータの不具合でデータの一部が消失した。
 2.__MAR:データの欠損する確率が、その項目以外の項目から影響があると推測できる時__
　　　　ex)男女を対象にしたアンケートで「性別」の項目が女性であると「年齢」の項目の欠損確率が高くなる。
 3.__NMAR:データの欠損する確率が、その項目から影響があると推測できる時__
　　　　ex)男女を対象にしたアンケートで実際の年齢が高い人ほど「年齢」の項目の欠損確率が高くなる。

##欠損値の対処
・欠損値発生のメカニズムごとに適切な対処法は異なる。

・MCARの場合、欠損値を含むデータを全て削除する__「リストワイズ削除」__を行うことも可能。これによりデータ数が少なくなりすぎる時は__「代入法」__（後述）も使える。
・MARの場合は__「代入法」__を使う。リストワイズ削除が不適切なのは、例で言えば、女性のデータが多く削除されてしまいデータに偏りが生じるからである。
・NMARの場合は適切に対処することが困難であるため、__基本的にはデータを再収集する__。

##代入法
・欠損値の代入法は大きく分けて2種類ある。詳しくは後述。
　1.__単一代入法__:欠損値を補完して完全なデータセットを一つ作り、それを使ってモデルを作る方法。
　2.__多重代入法__:欠損値を補完した完全なデータセットを複数作り、そのそれぞれについてモデルを作り、最後にモデルを一つに統合する方法。

##欠損値の可視化
・データを可視化することで、どこにNaNがあるかを調べる。
・NaNがどの列に何件あるかを簡易的に知りたい時は、pandasの__「データ.isnull().sum()」__を使えば良い。
・欠損値全体の状況を可視化したい時は、__missingnoパッケージ__の__matrix関数__を使うと良い。

・コード![スクリーンショット 2020-10-29 15.04.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698700/80bcfbae-f632-584d-74ff-9e37c2c9965b.png)

・結果（白い部分が欠損値）![スクリーンショット 2020-10-29 15.05.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698700/948b7b71-7cf9-be7d-f645-e7eea9335423.png)


##単一代入法
・__単一代入法__において欠損値を補完する際には、その用途に合わせて三種類の方法がある。

・__平均値代入法__:欠損値NaNに、その項目の平均値を代入する。平均値が増える分全体の分散は小さくなるので、分散や誤差を考慮すべきときには使えない。
・__確率的回帰代入法__:詳しくは扱わないが、分散や誤差を考慮することができる。
・__ホットデック法__:欠損値を含むデータ行（レシピエント）とデータが近いデータ行（ドナー）から値をもらうことで補完する。
・データの近さは、__「最近傍法」__という方法を用いて判定する。

##ホットデック法
・ホットデック法は以下のように記述して実行する。
__knnimpute.knn_impute_few_observed(matrix, missing_mask, k)__

・引数について、「matrix」にはnp.matrixに変換したデータを渡す。「missing_mask」には、matrixのどこに欠損値が含まれるかを__np.isnan(matrix)__で行列化したデータを渡す。「k」は、「最近傍法」のKNNで考慮に入れる近くの点の個数を指定する。

・KNNは「教師あり学習（分類）」で学んだ手法で、特定のデータ点を分類するときに、その近くのk個の点を考慮に入れる手法である。

・また、データをnp.matrixに変換しているので、必要に応じて元のデータ形式に戻す必要がある。

・コード![スクリーンショット 2020-10-29 15.11.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698700/db15c25d-1ae3-c0b5-6711-fbaf689c50f5.png)

##多重代入法について
・単一代入法の場合、補完した値はあくまで予測値であるため、これを使ったモデルの分析結果が必ずしも正確とは言い切れない欠点がある。
・これに対し、多重代入法はデータの補完を複数回行ってデータセットを複数作り、それらの分析結果を一つに統合して最終結果を出すため、__確実性の上がった予測を行うことが可能である__。

・多重代入法で使うモデルはstatsmodelsのmiceを使って以下のように表せる。
__mice.MICE(formula, optimizer, imp_data)__

・各引数について、「formula」には、モデルで予測する変数の式を渡す。例えば、「distance」という変数について、「time」と「speed」で予測する線形重回帰モデルの場合、以下のように表す。
__'distance ~ time + speed'__
・「optimizer」では分析モデルを指定し、「imp_data」には、dataをMICEで扱えるようにするために作成する__「mice.MICEData(data)関数」__を渡す。

・これで作成したMICEモデルに対して__fit()__メソッドを使うことで補完した分析結果を取得できる。（多重代入法の実行）
・fit()の第一引数には、__一度の補完のための試行回数__を指定し、第二引数には__データセットの作成数__を渡す。

・この分析結果に対して、__summary()__メソッドを使うことで結果を確認できる。

・コード![スクリーンショット 2020-10-29 15.13.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698700/a40802e3-fe89-1c45-b1a3-c5bf17e2ea36.png)

#外れ値について
##外れ値によって起こる問題
・__外れ値__とは、__他のデータと著しく離れたデータ__のことを指す。
・外れ値が混在すると、__「分析結果が正確でなくなる」「モデルの学習が遅くなる」__といった問題が生じる。
・よって、外れ値についても前処理の段階で検知し、__除外する必要がある__。

##外れ値の可視化
・外れ値が存在するかどうかは可視化するとわかりやすい。
・可視化にはseabornの__boxplot(x,y,whis)__を使う。描画されるのは「箱ひげ図」であり、外れ値を「♦︎」で示してくれる。
・引数「x」「y」はx軸y軸のデータ、「whis」は外れ値とみなす基準を指定する。

・データが二次元の時は__joinplot(x,y,data)__も使うことができる。散布図のように点がプロットされるので、外れ値を目視で検知する。
・引数「data」にはDataFrameを渡せば良い。

・コード（縦軸の離れ具合がわかれば良いので、y軸のみ指定する）
![スクリーンショット 2020-10-29 15.14.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698700/e8901da2-00ba-d33d-cd45-1edb62d47b1b.png)

・結果![スクリーンショット 2020-10-29 15.15.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698700/eed4d5ce-e5ff-4c74-7105-230a2789e22b.png)

##外れ値の検知 [LOF]
・外れ値が存在することがわかったら、__実際に外れ値を検知する__。
・検知には__「どのような値を外れ値とするか」__といった基準が必要になる。今回は、その基準が予め定められた上で検知を行う、scikit-learnの__「LOF」__という方法を扱う。
・LOFの外れ値判定は以下のように行われる。
　①__近くにデータ点が少ない点__を外れ値と考える。
　②「近く」の判定は__k個の近傍点__を使って行われる。
　③この判定（データの密度）が__周囲と相対的に低い点を外れ値と判定する__。

・LOFは__LocalOutlierFactor(n_neighbors=k)__で使うことができる。
・n_neighborsには近傍点の数kの個数を指定する。

・あとは普通のモデルのような__fit_predict(data)__メソッドでデータを学習させて、外れ値を検出する。
・dataにはDataFrameをそのまま渡すことができる。
・戻り値としては__array([1,1,-1,1,...])__のような配列が返される。このうち「-1」の部分が外れ値とみなされた部分になる。
・これに対して__data[戻り値を格納した変数 == -1]__のように指定すると、外れ値とみなされた行を抽出できる。

・コード![スクリーンショット 2020-10-29 15.21.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698700/4423b970-0dfd-ecd5-da49-847dffe1f166.png)

・結果（外れ値の出力）![スクリーンショット 2020-10-29 15.22.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698700/db1e7d72-ee73-cb5a-c9f0-d315fbbeaf1f.png)

##外れ値の検知 [isolationForest]
・LOFとは別の外れ値検知の方法として、__「isolationForest」__というものがある。
・isolationForestでは、__ランダムにデータを分割し、その深さに値を設定していく__ということを繰り返して外れ値を検知する。
・isolationForestは距離や密度に依存しないため、計算が複雑でなく__省メモリ__である。また、__大規模データであっても計算をスケールしやすい__といった特徴もある。

・isolationForestもLOFと同様「IsolationForest()関数」を使うことで外れ値を予測できる。

分類モデルの作成は__IsolationForest()__で行い、これに__fit()__メソッドを使って学習を行わせ、predict()で予測を行う。この戻り値はLOFと同様「-1」が外れ値となるので、 __data[predictions == -1]__のようにして取得する。

・コード
![スクリーンショット 2020-10-29 15.23.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698700/6d7910db-4a90-5774-5cb8-62384e4fd081.png)

・結果![スクリーンショット 2020-10-29 15.22.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698700/24e06303-4aff-5fa1-df0a-dc90bc387055.png)

#不均衡データについて
##不均衡データによって起こる問題
・データ値のうち__特定の値が極端に多かったり少なかったりするデータ__を__「不均衡データ」__という。
・例えば、二値分類のように「0」か「1」の値を持つ場合に、1000件のデータのうち999件が「1」で1件が「0」のデータであったとする。このデータで予測する場合、__「1」を予測するなら99.9%的中するが、「0」はほぼ予測できない__。
・よって、不均衡データについても、__データの前処理段階で適切に調整する必要がある__。

・調整の方法には以下の3つがある。
　・__オーバーサンプリング__:頻度が少ないデータを__増やして__均衡を取る方法。
　・__アンダーサンプリング__:頻度が多いデータを__減らして__均衡を取る方法。
　・__SMOTE-ENN__:少ない方を増やし(SMOTE)、多い方を減らし(ENN)て均衡を取る。

・不均衡データを調整するにあたり、はじめに行うのは「不均衡データがあるか」の確認、すなわち、__それぞれの値の個数（頻度）を可視化する__ことである。
・個数の可視化は以下のように行う。
__データ['列'].value_counts()__

・コード（ある人が自動車を購入したかどうかのデータ）![スクリーンショット 2020-10-29 15.25.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698700/b12aa6ff-a80c-949c-cdb3-85cc22a6289a.png)

##オーバーサンプリング
・__オーバーサンプリング__は、頻度の少ないデータを増やして多いデータとの均衡を取る方法である。
・データの増やし方は何種類かあるが、簡単なのは__「既存のデータをランダムに水増しする」__手法である。
・水増しには、不均衡データを調整する際に使われるimbalanced-learnの__RandomOverSampler(ratio='minority')__が使われる。
・RandomOverSamplerを定義したら、実際に　__.fit_sample(X,y)__で実行する。
・これには目的変数Xと説明変数yをわたし、水増ししたものを格納する変数としても、2つ用意する。

・コード![スクリーンショット 2020-10-29 16.15.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698700/af63bf2f-e5e1-fd9e-ec9f-077bdc5c2fa2.png)


##アンダーサンプリング
・__アンダーサンプリング__は、頻度が多いデータを減らして均衡を取る方法である。
・データの減らし方は、__RandomUnderSampler()__で行える。この時引数で指定するratioについて、今回は頻度の多い方を扱うので、__'majority'__とする。

・コード![スクリーンショット 2020-10-29 16.13.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698700/79204ef5-3ef9-8163-8aac-390d1890a9fa.png)


##SMOTE-ENN
・SMOTEはオーバーサンプリング（データの水増し）に使われ、ENNはアンダーサンプリング（データの削除）に使われる。
・また、SMOTEでは__最近傍法（kNN）__を使って、増やすべきデータを推測して水増しを行い、ENNでも、kNNと同じように減らすべきデータを推測して削除を行う。
・SMOTEとENNの使い方は__SMOTEENN(smote=,enn=)__で行う。基本的にはROSやRUSと同様であるが、引数ではkNNで使う近傍点の個数(k_neighbors,n_neighbors)を指定する必要がある。

・コード![スクリーンショット 2020-10-29 16.15.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698700/34354d74-0869-5e88-d0cc-520309db02e7.png)


#まとめ
・__欠損値__は「NaN」で表される中身のないデータのことである。
・__外れ値__は他のデータと著しく離れたデータのことである。
・__不均衡データ__は特定の値が極端に多かったり少なかったりするデータのことである。
・これらのデータは機械学習の妨げになるものなので、前処理をしなければならない。

・欠損値は__「代入法」__でNaNに数値を代入することで補完する。
・外れ値は__「LOF」「isolationForest」__で取得し、除外する。
・不均衡データは__「オーバーサンプリング」「アンダーサンプリング」__あるいはその両方を行うことでデータの均衡を取る。

今回は以上です。最後まで読んでいただき、ありがとうございました。