SPSS Modelerのデータ分割ノードをPythonで書き換える。ビン化、ランク付け処理

データを分位数で分けたり、ビン数でわけたりすることをビン化（binning)といいます。SPSS Modeler
SPSS Modelerでこのビン化を行うのがデータ分割ノードです。
優良顧客を判別したり、購入価格帯のボリュームゾーンを探したりすることができます。
このデータ分割ノードを解説するとともに、Pythonのpandasで書き換えてみます。


#0．元データ

ここではID付POSデータを対象に行います。
誰（CUSTID）がいつ（SDATE）何（PRODUCTID、L_CLASS商品大分類、M_CLASS商品中分類）をいくら（SUBTOTAL）購入したかが記録されたID付POSデータを使います。
これを誰（CUSTID）が全部でいくら（SUBTOTAL_sum）購入したかというデータに集計してから使います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/f97a425c-8a4a-8ba1-ed85-385f47cacceb.png)

以下のようなデータになります。2744件あります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/5ec0d710-963b-a6ae-a88d-d94f1708f818.png)

#1m．①分位数、デシル分析　Modeler版
売上金額を上位から並べて、同じ人数で分けてみます。ここで10グループ（ビンといいます）に分割する10分位で分割します。2744人いるので1グループ約274人ずつに分けられます。

以下の設定をします。SUBTOTAL_sumを基準に10個のビンに分けるという意味です。
ビンフィールド：SUBTOTAL_sum
データ分割手段：分位（等カウント）
10分位：チェック

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/ce018f93-4cea-7d70-9fa3-ba0b4514ef7e.png)

以下のようなに総購入金額によって１から10までビンが作られます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/9e95a175-75dd-0752-b0cb-3c06110a4702.png)

各ビンの閾値はデータ分割ノードのビンの値のタブで確認できます。ビン10の最優良顧客層は112,508円から828,844円の購入を行っています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/c50f57c2-c87b-a52e-d637-980e123b28a2.png)

10分割したデータで各ビンの特徴を分析をすることをデシル分析といいます。優良顧客とそうでない顧客の違いなどを分析します。
ビンの番号は大きくなるほど優良顧客ということになっていますが、１位のグループのビン番号は１にした方が直感的なので、置換ノードで11からビン番号を引くことで順番を逆順にします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/a7bc9f30-2a5f-53fb-cbdd-665be5e61828.png)

デシル分析では各ビンを集計して構成比や累積構成比をみることをよく行います。

以下のようにビン（SUBTOTAL_sum＿TILE10）をキーフィールドに設定して購入金額（SUBTOTAL_sum）を計算すると各ビンの総購入金額が出せます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/9c5034a4-f3ad-6a5a-d716-6f7f0b265178.png)

以下のようなデータが作れます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/ca6614b0-6ab9-7d03-e78a-be5cea37dea8.png)

上からさらに構成比や累積構成比を計算すると以下のようになります。
上位２つのビンで累積構成比が60%になっています。つまり上位20%の顧客で全売上の60%を占めていることがわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/ffd3eb48-1640-fb4a-d38b-130ef5f41cd4.png)

こういった優良顧客への偏りはよくあることです。優良顧客がわかれば、優良顧客は他の顧客とどう違うか、どうやって優良顧客の離反を防ぐかなどの分析に進んでいくことができます。

なお、マーケティングでよく使われるRFM分析では、総購入金額(Monetary)だけではなく、回数(Frequency)や最終購入日(Recency)もビン化して要約をします。RFMすべてをビン化する場合は以下のRFM分析ノードを使うのが便利です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/3e4575d8-bfc7-2d08-915b-b374a089f753.png)


#1p．①分位数、デシル分析 pandas版
pandasでも分位数でビンを作ります。
その前にまず、CUSTID毎に購入金額（SUBTOTAL）の総額を集計します。

```python
df_cust_total=df[['CUSTID','SUBTOTAL']].groupby(['CUSTID']).sum()
```

pandasで分位数でビニングを行う場合はpd.qcutを使います。
最初の引数はビニングの基準にするSeriesデータです。次の引数は分割数です。10分位なので10を設定しています。
labels=Falseでビンに連番を振ります。0-9で振られます。
retbins=Trueでビンの境界値を取得しています。decil_binsにビンの境界値のデータがndarrayで返ります。
10-df_cust['DECIL']で降順で1-10のビン番号を振りなおしています。

```python
df_cust['DECIL'],decil_bins=pd.qcut(df_cust['SUBTOTAL'],10,labels=False,retbins=True)
df_cust['DECIL']=10-df_cust['DECIL']
print(df_cust)
print(decil_bins)
```

以下のような結果になります。

```
        SUBTOTAL  DECIL
CUSTID                 
100001     44386      4
100004     63615      3
100006      8200      9
100008     24951      6
100012     72866      2
...          ...    ...
105967     64780      3
105970    245616      1
105972     38744      4
105974    142537      1
105978     25447      6

[2744 rows x 2 columns]
[  1009.    4924.8   9175.   14156.1  19777.6  27265.   36808.4  49254.
  70421.6 112361.1 828844. ]
```

構成比、累積構成比は以下のように算出できます。

```python
#デシルの構成比
df_decil=df_cust.groupby('DECIL').sum().sort_index()
df_decil['構成比']=df_decil['SUBTOTAL']/df_cust['SUBTOTAL'].sum()
df_decil['累積構成比']=df_decil['SUBTOTAL'].cumsum()/df_cust['SUBTOTAL'].sum()
df_decil
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/d9d985db-c19f-81b9-8758-9b3e918045c6.png)

#2m．②固定幅：ビン数指定　Modeler版
分位でのビンは件数を同じにしましたが、固定幅のビンは閾値の間隔を等間隔にしたビンを作ります。
度数分布表を作る場合に使います。売上のボリュームゾーンがどこにあるかなどを調べることができます。

以下の設定をします。SUBTOTAL_sumをの最大値と最小値の間を等間隔で10等分したビンを作るという意味です。
ビンフィールド：SUBTOTAL_sum
データ分割手段：固定幅
ビン数：10

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/b21ad85e-b707-b69f-f3dd-eca877cbb9f1.png)

以下のようにビン番号が割り振られます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/13e9254a-1d9a-6b33-813b-43eb4130b3a4.png)

ビンの閾値は「ビンの値」タブで確認ができます。最小値の1,009から最大値の828,844までを等間隔で区切っています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/7d521015-d82c-f2b4-8806-990a2166ee15.png)

棒グラフノードで度数分布をグラフ化してみると以下のようになります。83792.5未満の購入金額が83%を占めていることがわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/75485953-9652-f40e-9a81-d7188d869086.png)

#2p．②固定幅：ビン数指定　pandas版

pandasで固定幅でビニングを行う場合はpd.cutを使います。
最初の引数はビニングの基準にするSeriesデータです。次の引数は分割数です。10個のビンにわけるので10を設定しています。
labels=Falseでビンに連番を振ります。0-9で振られます。（Modelerと合わせる場合は＋１してください）
retbins=Trueでビンの境界値を取得しています。fixed_binsにビンの境界値のデータがndarrayで返ります。

```python
df_cust2['BIN'],fixed_bins=pd.cut(df_cust2['SUBTOTAL'],10,labels=False,retbins=True)
print(df_cust2)
print(fixed_bins)
```

```
        SUBTOTAL  BIN
CUSTID               
100001     44386    0
100004     63615    0
100006      8200    0
100008     24951    0
100012     72866    0
...          ...  ...
105967     64780    0
105970    245616    2
105972     38744    0
105974    142537    1
105978     25447    0

[2744 rows x 2 columns]
[   181.165  83792.5   166576.    249359.5   332143.    414926.5
 497710.    580493.5   663277.    746060.5   828844.   ]
```
なお、ビンの境界値の最小値が181.165になっていました。最小値は1009なので合っていませんでした。181.165以上83792.5未満でも1009は含まれるので実害はありませんが、謎の現象でした。

次に構成比を計算してみます。まずBINでgroupbyしてcountします。count後も列名がSUBTOTALだとややこしいので、COUNTという列名に変更しています。その後df_cust2_bin['COUNT'].sum()で割って算出しています。
やはり83792.5以下で約83%を占めていることがわかります。

```python
df_cust2_bin=df_cust2.groupby(['BIN']).count().rename(columns={'SUBTOTAL':'COUNT'})
df_cust2_bin['構成比']=df_cust2_bin['COUNT']/df_cust2_bin['COUNT'].sum()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/9c23f37f-23ab-eb16-7232-7b560b8dc3c4.png)

#3m．③ランク付け　Modeler版
ランクは@INDEXを使うような方法が一般的ではありますが、データ分割ノードでも簡単に作成できるので解説しておきます。
売上上位100顧客、100商品などを抽出することに使えます。

以下の設定をします。SUBTOTAL_sumでランク付けをするという意味です。
ビンフィールド：SUBTOTAL_sum
データ分割手段：ランク

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/7fcedad1-0a15-ed53-4e53-c7aad39de6d0.png)

ソートも組み合わせることで以下のような売上順のデータを作ることが可能です。_F_RANKや_P_RANKには上位N%に入るかのパーセンテージが入ります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/1172445d-73bd-c71f-7cce-e68fa4f9fedc.png)

なお、データ分割ノードでは全レコードでしかランク付けができませんが、カテゴリごとにランク付けをする場合には以下の方法をとる必要があります。

https://qiita.com/kawada2017/items/41e8847b280f35fa9aa4

Modelerのデータ分割の方法には分位や固定幅、ランク以外にも以下のデータ分割の方法があります。

- 平均と標準偏差
- カテゴリーの「スーパバイザ」フィールドに関連する最適化

https://www.ibm.com/docs/ja/spss-modeler/18.3.0?topic=node-setting-options-binning


平均と標準偏差はzスコア化して整数化するものです。これは以下の記事のようなデータの準備ノードでも
行えます。

https://qiita.com/kawada2017/items/d2b54b54379d7faaf0e2

カテゴリーの「スーパバイザ」フィールドに関連する最適化はスーパーバイザフィールドに指定したカテゴリー変数を目的変数のようにあつかって、特徴がでるように閾値を決める機能です。決定木で数値型のデータの境界値をきめるようなロジックになります。ロジスティック回帰の特徴量生成などに応用できます。
pandasでは同様の機能はなさそうでしたので、今回は詳しくは取り扱いません。


#3p．③ランク付け　pandas版
pythonの場合はrankメソッドを使います。ascending=Falseで売上上位順になります。
上位N%かはlen(df_cust3)でrankを割ることで算出できます。

```python
df_cust3['rank']=df_cust3['SUBTOTAL'].rank(ascending=False)
df_cust3=df_cust3.sort_values(['rank'])
df_cust3['p_rank']=df_cust3['rank']/len(df_cust3)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/8e0e026d-e229-7455-0ad2-2b4c72e74e8c.png)

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

ストリーム
https://github.com/hkwd/200611Modeler2Python/raw/master/binning/210811binning.str
notebook
https://github.com/hkwd/200611Modeler2Python/blob/master/binning/binning.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


#5. 参考情報
データ分割ノード - IBM Documentation

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

pandasのcut, qcut関数でビニング処理（ビン分割）

https://note.nkmk.me/python-pandas-cut-qcut-binning/