PyCaretで始めるマーケティングデータ分析

# はじめに
最近、機械学習ネタを書いていなかったので、久々に、機械学習で記事を書こうとPyCaretを触っていたら色々新機能が増えており、中でも意外と便利な **「ゲインチャート」** が加わっていたので、使い方をご紹介してみたいと思います。

https://github.com/pycaret/pycaret

# やること
ツールの使い方だけでは面白みが無いので、**「ダイレクトマーケティングの効率化」** をお題（想定）としてゲインチャートがどう使えるか？を見て行きます。

## 設定
販促でお客様にカタログを送付するシナリオを考えます。
特に何も考えなければ、**すべてのお客様**にカタログを郵送すると思うのですが、やはり
**コストがかかります。**


<img width=550 src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183826/604363a7-2a21-065a-208f-6e6ef123440f.png>

本当は、
* <font color='#C00000'><b>買う！、買うかも。</b></font>なお客様にはカタログを郵送。
* <font color='#0070C0'><b>買わない。</b></font>なお客様にはカタログは郵送しない。

がうまくコントロール出来ると、コストも下げられますし、エコですし、これを実現したい。
（ユーザ視点でも、不要なカタログは送付不要ですので、CS的にも良いかもです！）

<img width=550 src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183826/942b5038-8661-2e35-164c-250491ca82d6.png>

## 使うデータセット
とは言え、適切なデータセットがあるわけではないので、Kaggleの下記データセットを一部いじって、**「ダイレクトマーケティング」** っぽいデータを使った機械学習を進めていきたいと思います。

https://www.kaggle.com/datasets/yoghurtpatil/direct-marketing

デモグラフィックデータで、「顧客特性」と「購入金額」を含み、このお題で使えそうです。
データ項目を簡単に確認しておきます。

* 元々は、#1～#9 の説明変数を用いて、目的変数の#10 支出額を説明、導出する為のデータセットですが
* 今回のテーマに合わせ分類問題としたいので、、
一定額以上の高額な支出が **見込めるか？否か？** の２値分類としたいと思います。

|# | 列名 | 説明 | データ |
|:---: | :---: | :---: | :---: |
|1 | Age | 顧客の年齢 | 年配／中年／若年|
|2 | Gender | 性別 | 男性／女性|
|3 | OwnHome | 住居 | 持ち家 or 賃貸|
|4 | Married | 結婚 | 独身 or 既婚|
|5 | Location | 家～店舗の距離 | 遠い／近い|
|6 | Salary | 所得 | 連続値（ドル）|
|7 | Children | 子供の人数 | 連続値（人数）|
|8 | History | 過去の購入量 | 低／中／高|
|9 | Catalogs | カタログ送付数 | 連続値（送付数）|
|10 | **AmountSpent** | 支出額 **（目的変数）** | 連続値（ドル）|

# では、始めます
Kaggleのデータということで、KaggleのNotebook（Kernel）で進めたいと思います。
Google Colabとかを使う方は、データをダウンロードして頂ければと。

https://www.kaggle.com/datasets/yoghurtpatil/direct-marketing

## 準備
KaggleのNotebookに、PyCaretをpipします。
相性の問題か、3.0RCや2.3の最近のVerだとうまくいかなかったので、2.3.6を使います。

```python
pip install pycaret==2.3.6
```

そして、必要なものを一式、importしておきます。

```python
import numpy as np
import pandas as pd 
import seaborn as sns
import pycaret
print(pycaret.__version__) # 2.3.6ならOK
```

## データ確認と前処理
###
まずは、pandasでロードし、データを確認します。
質的データは文字列、量的データは数値で表現されているので、そのままで大丈夫そうです。

```python
df = pd.read_csv("/kaggle/input/direct-marketing/DirectMarketing.csv")
df
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183826/5c6642d9-c2b7-31fb-41ff-06b39a842064.png)

とりあえず、describeしておきます。

```python
df.describe()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183826/f05c412b-4b30-806e-4f5b-a59c2f66498f.png)

が、目的変数のAmountSpentを観るのに、四分位だと荒いので、、
５％刻みでのパーセンタイルを確認してみようと思います。

```python
df["AmountSpent"].quantile(
    np.arange(
        0.75, #  75%タイルから
        1,    # 100%タイルまで
        0.05  #   5%刻みで表示
    )
)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183826/062769f7-da27-f1d4-c6af-3a45f411e646.png)

```python
sns.histplot(
    df["AmountSpent"],
    bins=20
)
```
なんとなく、購買金額の**上位５％程度**の顧客を<font color='#C00000'><b>買う！、買うかも。</b></font>と見なすとリアリティがありそうです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183826/81682711-4191-f7bf-b89c-625aacbcef24.png)

なので、**AmountSpent > 3052** のユーザについて、**購買フラグ(target)を立てます。**

```python
df["Target"] = df["AmountSpent"] > 3052
df["Target"].value_counts()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183826/c51ea246-4085-9de2-556d-36d767881773.png)

最後に、今回は目的変数が**AmountSpent**ではなく、**Target**ですので、**AmountSpent**を消しておきます。
合わせて、こういったダイレクトマーケティング向けのデモグラフィーで、**顧客の年収（Salary）** が入手できているのも**正直、違和感**はあるのと、リークしそうなので合わせて削除しておきます。


```python
df_train = df.drop(
    columns=
    [
       "AmountSpent",
       "Salary"
    ],
    axis=1
)
df_train
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183826/282aa3cc-b9c8-dfde-33b0-9407bfd5dd0d.png)


**これで前処理完了です！**

## PyCaretでモデル作成
では、ここからPyCaretを動かして行きます。まずは、setupなのですが、、
量的変数を正しく認識できるように、**numeric_features**にて、カラム名を指定しておきます。

```python
from pycaret.classification import *
s = setup(
    data=df_train,
    target="Target",
    numeric_features=
    [
        "Children",
        "Catalogs"
    ],
)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183826/94069269-ad23-9f6e-37e8-8beed4e65b77.png)

Data Typeが正しいことを確認したので、Enterを押下し、ステップを進めます。
モデルをfittingしていくのですが、評価指標としては、 F-measure（**F1**）を指定します。

```python
best = compare_models(
    sort="F1"
)
```

* 要件として、Precision／Recallのバランスを取りたいので、その調和平均であるF-measureを使います[^1]
* 今回の学習データは、正例：負例＝１：１９といった不均衡データで注意が必要ですが、マイノリティを正例としているのでF-measureでも正しく評価できます。
* なお、そういった事を気にしたく無い場合 **MCC（マシューズ相関係数／Matthews Correlation Coefficient）** を指定するという方法もあります。
 
詳細は、下記をご確認ください。

https://qiita.com/shnchr/items/7991f8ca7ae7acf1dad9

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183826/d2c9ecc0-8523-4f9c-ab04-356758a9795e.png)

そして、best estimatorである勾配ブースティング分類器を可視化していきます。

```python
evaluate_model(
    estimator=best
)
```

これでやっと、今回使いたい、**累積ゲインチャート**を描く事ができました！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183826/096487c7-9286-5701-8f6d-06cb32874466.png)


# 累積ゲインチャートについて
上記のゲインチャートの縦軸、横軸ですが、下記の通りです。
* 横軸　：　分類器が予測した「購入しそうな順[^3]」に並んだ顧客
* 縦軸　：　その「顧客」までの累積の反応率（真陽性率・Recall）

[^3]: 「購入しそうな順」は、正しくは、モデルのpredict_probaの順を意図しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183826/d4d0878f-7771-3b38-9ce6-bfb3cf8c98b1.png)

## 反応率（真陽性率・Recall）とは

**実際にP（買う）の顧客のうち、P（買う）と正しく予測できた割合**です。

```math

Recall = \frac{TP}{TP + FN} 
= \frac{\color{red}{①正しくP(買う)と予測できた数}}{\color{blue}{②実際にP(買う)の顧客の全量}}

```

混同行列で見ていくと、、

<img width=500 src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183826/a476f881-daf8-dbe1-e78f-31054f723749.png>

こんな感じです。ここで、TPとFNの意味について考えてみたいと思います。

* $ TP $：実際に「買う」お客さんで、予測として「買う」と予測できた。
* $ FN $：実際に「買う」お客さんだが、**予測としては「買わない」と予測してしまった。**

となります。では、Recallが100%とはどういった状況でしょうか。

```math
Recallが100％ = \frac{TP}{TP + FN}  = \frac{100}{100 + 0}　 \Leftrightarrow 　 FN = 0
```

式でみれば自明ですが、$ FN $**が0人のとき、Recallが100%となります。**
つまり、Recall100%で$FN=0$とは、

* A）実際に、「買う」お客さんの全員を、「買う」と予測した集合（$TP + FP$）に含む。
* B）実際には「買わない」のお客さんだが、「買う」と予測してしまう誤り（$FP$）は特に意識しない

## ダイレクトマーケティングに応用する
Recallが高い予測モデルを利用すれば、**実際に「買う」お客さんを逃すことなく、** DM、カタログ、クーポン等の販促施策を実行できます。
仮に、Recallが高い予測モデルにて、$ P $（買う）と予測された顧客全員に販促施策を実行します。
そのとき、この販促施策はどういった特性を持つかを、先述のA,Bに対応づけて考えると、
* A）実際に、「買う」お客さんの**全員に、DM、カタログ等の販促施策が実行可能**
* B）実際には「買わない」お客さんにも、DM、カタログ等を送ってしまうが、それはしょうがない。

つまり、Aの特性を優先し、想定見込み客の全量に対して販促施策を打つことを優先する。
そういった活用が可能です。

# 読み解きかた
読み解くのに必要な知識は説明できたので、実際にこのチャートを読み解いていきます。

## ①上位10%に販促を打つと、ターゲットの60%をカバー可能

まずは、下記を読み解いていきます。
横軸は、分類器が予測した「購入しそうな順」に並んだ顧客ですので、左側の「0.0～0.1」の範囲には、「購入しそうな顧客」のTop10%（優良そうな顧客）が含まれています。
この顧客のトップ10%に、販促を打ったら、「実際に買う」とラベル付された顧客の何％をカバー出来るでしょうか？

そうです、0.1のところの縦軸の値を読み取れば良いのです。
縦軸の値は0.6。つまり、Top10%の顧客での累積のRecallは0.6ですので、販促でターゲットとしたい、実際に購入につながる顧客の60%をカバー出来ることを意味します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183826/1e99bb1c-0855-ff96-5d12-a8f1642eb898.png)


## ②上位20%に販促を打つと、ターゲットのすべてをカバー可能[^2]

Top20%に販促を打つと、「実際に買う」とラベル付された顧客の何％をカバー可能でしょうか？
0.2のときの累積Recallは1.0ですので、このモデルにおいてはすべてのターゲットをカバー可能です。[^2]

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183826/fe1d05f3-6406-b47f-3e28-c83739280bf6.png)

## ③理想の完璧な分類器だと

今回の設定では、P「買う」のラベルのデータは、全体の5%だったので、、
完璧な分類器では、上位5%に販促を打った時点で、Recall100%となります。
つまり、下記の赤線の様な累積ゲインチャートが描かれるのですが、、、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183826/5487fbc4-5e21-e03c-22e4-70982feaaeca.png)

そんな完璧な分類器は現実的に作成困難で、リークしている可能性が高いので、
今回の結果をどう読み解けばよいかというと、

* Recall100%を上位5%で言い当てる完璧モデルと比べると、**上位20%でRecall100%になる今回のモデルは見劣りするかもしれませんが、、**
* 何も考えずに、すべての顧客に販促を打つ状況から比較すると、**上位20%への販促でもRecall100%で全数販促と効果は一緒ですので、カタログ費用や郵送費用は80%削減（出来るかもしれない）**

と考えることが出来るのでは無いでしょうか？

<img width=550 src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183826/942b5038-8661-2e35-164c-250491ca82d6.png>


# まとめ
* PyCaretのVerUpに伴い追加された、累積ゲインチャートについて説明しました。
* グラフの見方だけを説明してもつまらないので、ダイレクトマーケティングのトイデータを用いて、販促業務の改善といった例題で、累積ゲインチャートの使い方を見ていきました。
* トイデータの特性か、かなり性能の良いモデルができてしまい、上位20%でそれまでの累積Recall100%という状況でしたが、考え方はお伝え出来たと思います。
* ぜひ、皆さんの身近なテーマで、PyCaretを活用いただければと思います。
* なお、反響があれば、類するテーマで別の記事も書いてみたいと思います。

# 補足
今回は簡単のために、問題を単純化して扱いましたが、実際には、
* モデルが重視する特徴量が、現場のマーケターの感覚と一致するか（納得性）
* 過去の累積データは、今回対象とする商材のマーケティングを説明可能か、適切か？
* 累積顧客データは基本的に過去からの蓄積されたデータであり、集計期間をどう取扱か？
* 施策を繰り返す場合に、前回の販促の介入効果を割り引いた検討が必要（差の差等）
* LTVを考慮した場合の販促検討

等々、色々検討すべき事項はあると思います。

## 実際の累積ゲイン曲線
利用したトイデータの特性か、今回作ったモデルは、よく出来すぎていました。
実際には、こんな感じとなります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183826/8fecea6e-259e-78fe-02ac-ee74b6dfa59d.png)

つまり、

* ML予測結果に基づき上位20%に販促を打てば、ターゲットの80%がカバー可能
* カバー率100%にするためには、残りの80%の顧客（ほぼ全数）に販促を打つ必要があり
    * 残20%のターゲットへの販促は諦める。
    * もしくは、残20%のターゲット向けの販促は別手段（Email等）で実施する。

等々の議論があるかと思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183826/a1a18592-c67b-e36a-d03c-8522fe7e1467.png)

# リンク
以前に書いたPyCaretの記事をリンクしておきます。

https://qiita.com/shnchr/items/bdd4810b3477aa977413

https://qiita.com/shnchr/items/be21f10748a14a2b3b40

https://qiita.com/shnchr/items/7991f8ca7ae7acf1dad9

[^1]:ダイレクトマーケティングでの活用であれば、Recallが稼げていると、真に購買につながる顧客をカバー出来ていると解釈することができます。一方で、Recallのみを追求すると、Precisionが劣化しクーポンや販促の無駄撃ちが増えるため、モデル構築時は、その両者の調和平均であるF-measureを指定しておきます。

[^2]:データの特性か、モデルとして良く出来すぎていて、ちょっとリアリティが無いので、気になる方は末尾の補足をご確認ください。