SPSS Modelerのバランス・ノードをPythonで書き換える。アンバランスデータの修正

SPSS Modelerで目的変数のレコード数に不均衡がある場合にそのバランスをそろえるのがバランス・ノードです。
このバランス・ノードを解説するとともに、Pythonのpandasで書き換えてみます。

#0．元データ

ここではローン審査での、顧客の属性と判定したリスクの入ったデータを使います。Riskが１の場合はリスクの高い顧客、0はリスクの低い顧客を示しています。

CustID:顧客番号
Age:年齢
Car_loans：車のローンランク
Credit_cards:クレジットカード利用ランク
Education:教育ランク
Income:収入ランク
Risk:リスクありなし

500件あります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/bc7c63ee-3924-93e7-dd50-025f44bdd1dd.png)

Riskの1と0のレコード件数を比較するとRisk=1のレコードは全体の25%、Risk=0のレコードは75%で、アンバランスになっていることがわかります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/fd3a4b09-5f81-d433-1b42-987e1a7a6857.png)

通常こういうデータでRisk予測モデルを作る場合は、Riskがある人を予測したいのですが、仮にすべてをRiskなしと予測しても75%の精度で予測ができ、Riskなしと予想しがちなモデルができてしまいます。

これを避けるために、Riskありとなしのレコードのバランスをとるのがバランス・ノードです。

#1m．①アンダーサンプリング　Modeler版
バランス・ノードは単体で使うこともできますが、棒グラフノードから作るのが一般的です。

まずRiskで棒グラフを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/f0dae664-6084-5eaa-c25f-6a9c2237644e.png)

グラフができたら、生成メニューから「バランスノード（減少）」を選びます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/ffeef355-4e59-8df6-e300-3bfae8d354fc.png)

そうするとバランスノードがキャンバスに追加されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/c113d965-5c95-f134-072f-e347a874d38d.png)

中身を見るとRisk＝１の時には1倍なのでそのまま、Risk＝０の時には約0.3倍のレコード数にするというバランス比率が定義されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/40230808-d6c1-43c5-2fd4-1ed6b622d950.png)

このサンプリングノードを接続したうえで、改めて棒グラフノードで結果を確認すると、Risk=0のデータが約1/3になって、ほぼ同数のレコード数になりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/0fac8810-2489-492e-53d5-2e7fc9ac5d69.png)

注意点はランダムシードが設定できないので、毎回結果が変わってしまうことです。ですので、キャッシュしてキャッシュデータを保存しておくか、エクスポートノードでファイルやDBのテーブルに出力しておくことをお勧めします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/f895ba2d-6c3c-ddb8-da91-69a1936c5509.png)



#1p．①アンダーサンプリング pandas版

pandasでアンバランスデータの修正を行う場合はimblearnを使います。pipやcondaを使って導入しておきます。

```sh
#pip
pip install imbalanced-learn
#conda
conda install -c conda-forge imbalanced-learn
```

imblearnを使うためには、以下のように説明変数のデータフレームと目的変数のデータフレームに分割しておく必要があります。

```python
df_x=df.drop(['Risk'],axis=1)
df_y=df[['Risk']]
```
件数とその棒グラフを表示してみます。

```python
df_risk=df_y.groupby('Risk').size()
print(df_risk)
df_risk.plot.bar()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/260538a8-fbed-5309-5898-7d4a462519f4.png)

では、このデータをアンダーサンプリングしてみます。

RandomUnderSamplerをImportし、random_stateでランダムシードを指定してインスタンス化します。
その上で説明変数のデータフレームと目的変数のデータフレームを引数にfit_resampleを実行するとアンダーサンプリングされた説明変数のデータフレームと目的変数のデータフレームが戻ります。

```python
from imblearn.under_sampling import RandomUnderSampler
sampler = RandomUnderSampler(random_state=1)
x_resampled, y_resampled = sampler.fit_resample(df_x,df_y)
```

件数とその棒グラフを表示してみます。どちらも600件と同数になっているのがわかります。

```python
df_risk=df_y.groupby('Risk').size()
print(df_risk)
df_risk.plot.bar()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/f136ea05-60b3-a3fc-69c0-c9c562b60479.png)

#2m．②オーバーサンプリング　Modeler版
先ほどはRiskありの600件に合わせてRiskなしのレコードを減らすアンダーサンプリングをしましたが、Riskありのレコード件数を増やすオーバーサンプリングも可能です。

棒グラフの生成メニューから「バランスノード（増加）」を選びます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/3a0fcc0f-3cd9-7cd3-ff1c-a92e926e759d.png)

中身を見るとRisk＝0の時には1倍なのでそのまま、Risk＝1の時には約3倍のレコード数にするというバランス比率が定義されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/40230808-d6c1-43c5-2fd4-1ed6b622d950.png)

棒グラフで結果を見ると以下のようにRisk＝１のレコードが3倍に増えて、同数程度になっていることがわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/fdc71582-ea68-a01c-1b01-6db61e406e3b.png)

データをみるとRisk＝１のレコードが約3倍に重複していることがわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/42758114-ab92-7eee-37cf-80b161c10892.png)

#2p．②オーバーサンプリング　pandas版
pandasでオーバーサンプリングする場合はRandomOverSamplerを使います。使い方はRandomUnderSamplerと同じです。

```python
from imblearn.over_sampling  import RandomOverSampler
sampler = RandomOverSampler(random_state=1)
x_resampled, y_resampled = sampler.fit_resample(df_x,df_y)
```
Risk＝１が増えて同数になったことがわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/70542b8e-37b6-588d-f3f7-79bbcc66afbe.png)



#3. 補足

オーバーサンプリングにはSMOTEという方法もあります。SMOTEは単にデータをコピーするのではなく、似たデータを生成します。

【リレー連載】わたしの推しノード –機械学習時代の申し子「SMOTEノード」が不均衡データの壁を突破する | IBM ソリューション ブログ 

https://www.ibm.com/blogs/solutions/jp-ja/spssmodeler-push-node-10/

ModelerにはSMOTEノードがあります。imbalanced-learnでもSMOTEが可能です。

SMOTE — Version 0.8.0 

https://imbalanced-learn.org/stable/references/generated/imblearn.over_sampling.SMOTE.html


#4. サンプル
サンプルは以下に置きました。

ストリーム
https://github.com/hkwd/200611Modeler2Python/raw/master/balance/balance3.str
notebook
https://github.com/hkwd/200611Modeler2Python/blob/master/balance/balance.ipynb
データ
https://raw.githubusercontent.com/hkwd/200611Modeler2Python/master/data/credit_risk.csv

■テスト環境
Modeler 18.3
Windows 10 64bit
Python 3.8.5
pandas 1.0.5
imblearn 0.7.0

#5. 参考情報


Python: 機械学習における不均衡データの問題点と対処法について - CUBE SUGAR CONTAINER 

https://blog.amedama.jp/entry/imbalanced-data

バランス・ノード - IBM Documentation

https://www.ibm.com/docs/ja/spss-modeler/18.3.0?topic=nodes-balance-node

imbalanced-learn API reference — Version 0.8.0 

https://imbalanced-learn.org/stable/references/index.html#api