マーケティング施策の効果検証における回帰不連続デザインの応用

# はじめに
クーポン施策を例に、マーケティング施策の効果検証に回帰不連続デザイン(RDDあるいはRD)を応用する手法について、Pythonによるシミュレーションを交えてまとめました。内容に誤り等ございましたら、ご指摘いただけますと幸いです。

# マーケティング施策の効果検証
CRM施策の一環として会員に割引クーポンを配布し、「クーポンの効果」すなわち「クーポンがどれほど会員の購入率に影響を与えたか？」を検証する例を考えます。

このとき、クーポンの効果を「クーポンが配布されてから2週間以内にクーポンを利用した人の購入率が、クーポンがなかった場合どれほど減少したか」とします。

## 理想的な効果検証
クーポン施策の効果検証を行う際に理想的な方法は「クーポンの配布（処置）を**ランダムに**割り付ける」という方法です。

ここで、記号を以下のように設定します。
- $Y$: 商品の購入（購入した場合1, 購入しなかった場合0）
- $Z$: クーポンの配布（配布された場合1, 配布されなかった場合0）
- $D$: クーポンの利用（利用した場合1, 利用しなかった場合0）
- $P(\cdot)$: ()内の状態となる確率（e.g: $P(Y=1) \ $は購入率）

このとき、クーポンの効果は
```math
\frac{P(Y=1|\ Z=1)- P(Y=1|\ Z=0)}{P(D=1| \ Z=1)} \tag{1}
```
と表されるということが知られています。詳細はこちらの記事をご参照ください。

https://qiita.com/s1ok69oo/items/d9f3a3860388f3b9911d 

:::note warn
本記事では、$Y$（商品の購入）や$\ D$（クーポンの利用）は、クーポンを配布されてから2週間以内の結果とします。
:::


## 現実的な効果検証
しかし、CRM的な観点で言うと、クーポンをランダムに割り付けるのは望ましくありません。このようなケースでは「何らかの基準をもって、優良顧客と分類される会員にクーポンを配布し、そうでない会員にはクーポンを配布しない」というようなアプローチを取ることが多いです。

そのため、効果検証をする際には「直近6ヶ月間の累計ポイントが100ポイント以上の会員を"優良顧客"とする」というような定義づけを行い、この定義に基づいて"優良顧客"にクーポンを配布するというアプローチをとります。以下、この設定で話を進めます。

:::note warn
本記事では、ポイントの詳細（付与条件や利用方法など）については言及しませんが、サービスの利用頻度が多ければ多いほど累計ポイントも貯まるものとします（おそらく、みなさんが普段利用しているサービスのポイント制度と同様のものだと思われます）
:::

### 回帰不連続デザイン(RDD)
まず「クーポンを配布された会員が商品を購入するとき、必ずクーポンを利用する」というケースを考えます。

こういったケースでクーポンの効果を推定するとき、やってしまいがちな誤った（**ナイーブな**）推定方法として「クーポンが配布された会員全体の購入率」と「クーポンが配布されていない会員全体の購入率」の差を効果と推定しまう方法が挙げられます。

なぜこれがナイーブな推定となってしまうかと言うと、
- クーポンを配布される会員は、サービスの利用頻度が高く、元々（クーポンを配布されなくても）商品を購入する確率が高い
- クーポンを配布されない会員は、クーポンを配布される会員に比べてサービスの利用頻度が低く、商品を購入する確率が低い

傾向にあると考えられるからです。つまり、「サービスの利用頻度」が「クーポンの配布」と「商品の購入率」の両方に影響を与える**交絡因子**となっているのです。（DAGを描くと以下の通り）

![無題のプレゼンテーション.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/550377/3db58ad7-263c-c94c-95b4-d6b2eb1e4992.png)

ここで、「サービスの利用頻度」をどうやって表現するか考える必要があり、今回は「直近6ヶ月間の累計ポイント」を**代理変数**としてを利用することにします。

![無題のプレゼンテーション.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/550377/fef39b82-6c47-8c16-6636-48eeb562fba3.png)

:::note warn
「直近6ヶ月間の累計ポイントが100ポイント以上の会員を"優良顧客"とする」というような定義づけを行い、この定義に基づいて"優良顧客"にクーポンを配布するというアプローチをとるのは、
- 「サービスの利用頻度」の代理変数として「直近6ヶ月間の累計ポイント」を利用できると考えられる
- これから紹介する「回帰不連続デザイン(RDD: Regression Discountinuity Design)」という手法によって効果を（できるだけ）バイアスなく推定できる

といった理由からです。効果検証を行う際は、行き当たりばったりでアプローチを考えるのではなく、効果をバイアスなく推定できるように**クーポン施策を実行する前段階できちんと設計しておく必要**があります。
:::

回帰不連続デザイン(RDD)について説明するために、記号を以下のように整理します。
- $R_i$: 直近6ヶ月間の累計ポイント
- $Z_i$: クーポンの配布（$\ R_i \geq 100 \ $であれば$\ Z_i = 1$（配布）, $R_i < 100 \ $であれば$\ Z_i=0$（未配布））
- $Y_i$: 商品の購入（$\ Y_i=1 \ $であれば購入、$Y_i=0 \ $であれば非購入）

このとき、例えば
- $R = 200 \ $の会員のグループ（実際にはクーポンが配布される）
- $R = 0 \ $の会員のグループ（クーポンが配布されない）

を比較すると、**クーポンを配っていなかったとしても**、「$R=200 \ $の会員のグループの商品購入率」の方が「グループ$R=0 \ $の会員のグループの商品購入率」よりも大きい、すなわち、
$$P(Y=1 | \ R=200, \ Z=0) > P(Y=1| \ R=0, \ Z=0)$$と考えるのが自然です。次に

- $R = 100 \ $の会員のグループ（実際にはクーポンが配布される）
- $R = 99 \ $の会員のグループ（クーポンが配布されない）

を比較します。このとき、2グループの**クーポンを配布していなかった場合**の商品購入率に差があると考えられるでしょうか？おそらく、「累計ポイントが100のグループ」も「累計ポイントが99のグループ」もサービスの利用頻度に差はなく、商品の購入率は（ほぼ）等しい、すなわち、
$$P(Y=1 | \ R=100, \ Z=0) = P(Y=1| \ R=99, \ Z=0) \tag{2}$$と考えるのが自然だと思います。

:::note warn
例外的なケースとして
- **連続性の仮定** が成り立たないケース
- **non-manipulation** が成り立たないケース

などが挙げられます。詳細は下記の記事をご参照ください。
:::

https://zenn.dev/s1ok69oo/articles/f40ca93fe31a8d

ここで、「累計ポイントが100のグループ」に注目すると、クーポンの効果は「クーポンを配布された場合の商品購入率」と「クーポンを配布されなかった場合の商品購入率」の差、すなわち、
$$ P(Y=1 | \ R=100, \ Z=1) - P(Y=1 | \ R=100, \ Z=0) \tag{3}$$と表すことができます。

ただし、実際には累計ポイントが100のグループにはクーポンが配布されてしまうため、「累計ポイントが100のグループにクーポンを配布しなかった場合の商品購入率」、すなわち、
$$P(Y=1 | \ R=100, \ Z=0)$$を観測することはできません。

そこで、観測可能な「累計ポイントが99のグループにクーポンを配布しなかった場合の商品購入率」、すなわち、
$$P(Y=1 | \ R=99, \ Z=0)$$を利用し、(3)式に(2)式を代入すると、クーポンの効果は
```math
P(Y=1 | \ R=100, \ Z=1) - P(Y=1 | \ R=99, \ Z=0) \tag{4}
```

と推定することができます。これが「**回帰不連続デザイン(RDD)**」のアプローチです。

:::note warn
現実的には、「累計ポイントがちょうど100の会員とちょうど99の会員のデータ数が少なく、うまく推定できない」という問題が生じることが多々あります。このような場合は、クーポン配布の閾値となっている累計ポイントに幅を持たせて
$$P(Y=1 | \ 100 \leq R < 105, \ Z=1) - P(Y=1 | \ 94 < R \leq 99, \ Z=0)$$のようにクーポンの効果を推定します。
:::

### ファジーな回帰不連続デザイン(Fuzzy RDD)
次に「クーポンを配布された人が商品を購入するとき、必ずしもクーポンを利用するとは限らない」というケースを考えます。

このような状況を「**ノンコンプライアンス**」（あるいは「**不遵守**」）と表現し、例えば、
- 特に理由もなく、会員が商品購入時にクーポンを利用しなかった
- 電子クーポンをメールにて配布したものの、会員がメールに気づかなかった
- 物理的なクーポンを配布したものの、会員が商品購入時にクーポンを持参し忘れてしまった

というようなさまざまなシーンで起こります。

このとき、クーポンの効果を
$$P(Y=1 | \ R=100, \ Z=1) - P(Y=1 | \ R=99, \ Z=0)$$と推定しようとすると、累計ポイント100の会員は商品購入時に必ずしもクーポンを利用しているとは限らず、ナイーブな推定になってしまいます。このようなケースに対応した回帰不連続デザインを「**ファジーな回帰不連続デザイン(Fuzzy RDD)**」と言います。

ここで、先述した「累計ポイントが100のグループも99のグループもサービスの利用頻度に差はなく、商品の購入率は等しい」ということに注目すると、累計ポイントが100or99の会員たちは「サービスの利用頻度が等しく、クーポンの配布はあたかもランダムに割り付けられている」と考えることができます。

そのため、クーポンの効果は累計ポイント100or99のグループに(1)式を用いて
```math
\frac{P(Y=1| \ Z=1, 99 \leq R \leq 100)- P(Y=1| \ Z=0, 99 \leq R \leq 100)}{P(D=1| \ Z=1, 99 \leq R \leq 100)} = \frac{P(Y=1| \ R=100) - P(Y=1| \ R=99)}{P(D=1| \ R=100)} \tag{5}
```

と推定することができます。

:::note warn
このとき、クーポンの配布の有無（≒累計ポイント）は
- クーポンの利用に影響を与える（クーポンが配布されない会員はクーポンを利用できない）
- 商品の購入率に影響を与えない（累計ポイント100or99のグループでは会員同士のサービスの利用頻度に差はない）

と考えられるため、**操作変数**(**IV: Instrumental variable**)となっていることが分かります。すなわち、ファジーな回帰不連続デザインによる効果の推定は**操作変数法の応用形**として捉えることができるというわけです。

操作変数の詳細は下記の記事をご参照ください。
:::

https://zenn.dev/s1ok69oo/articles/3b75bbfd12b6a2

# Pythonによるシミュレーション
Pythonでデータを自作し、回帰不連続デザインを用いてクーポンの効果を推定してみます。
今回はクーポンを配布された会員は必ず商品購入時にクーポンを利用するとし、クーポンの効果を0.3と設定しています。

```py
# 必要なライブラリのインポート
import numpy as np
import pandas as pd

# データの設定
np.random.seed(0)
size = 1000

# 累計ポイント
R = np.random.randint(0, 200, size=size)
# クーポンの配布
Z = np.where(R>=100, 1, 0)
# 購入の有無
y_prob = R/400 + 0.3*Z # クーポンの効果は0.3
Y = np.array([])
for i in range(size):
  Y_i = np.random.choice(2, size=1, p=[1-y_prob[i], y_prob[i]])[0]
  Y = np.append(Y, Y_i)

# データフレームに格納
df = pd.DataFrame({'累計ポイント': R, 'クーポンの配布': Z, '購入の有無': Y}).astype('int')
df.head()
```
（出力結果）
![スクリーンショット 2023-02-13 1.28.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/550377/d2b878bb-2716-c5c1-e984-f8cced2ef518.png)

### ナイーブな推定
まずはクーポンが配布された会員（$Z=1$）とクーポンが配布されていない会員（$Z=0$）の購入率の差を計算します。
```py
# ナイーブな推定
df_z1 = df[df['クーポンの配布']==1]
df_z0 = df[df['クーポンの配布']==0]

(df_z1['購入の有無'].sum() / len(df_z1)) - (df_z0['購入の有無'].sum() / len(df_z0))
```
(出力結果)
```
0.5534307130501633
```

実際のクーポンの効果は0.3であるため、かなり過大評価しているようです。

### 回帰不連続デザイン(RDD)による効果の推定
次に累計ポイント100（クーポン配布あり）の会員と累計ポイント99（クーポン配布なし）の会員の購入率の差
$$P(Y=1 | \ R=100, \ Z=1) - P(Y=1 | \ R=99, \ Z=0) \tag{4}$$を計算します。

```py
# (4)式
df_r100 = df[df['累計ポイント']==100]
df_r99 = df[df['累計ポイント']==99]

(df_r100['購入の有無'].sum() / len(df_r100)) - (df_r99['購入の有無'].sum() / len(df_r99))
```
（出力結果）
```
0.2857142857142857
```

実際のクーポンの効果0.3であるため、ナイーブな推定と比較して推定値がかなり改善したようです。

:::note warn
(4)式は、累計ポイント100($R=100$)の会員は全員クーポンが配布され($Z=1$)、累計ポイント99($R=99$)の会員は全員クーポンが配布されない($Z=0$)ので、観測データから
$$P(Y=1| \ R=100) - P(Y=1| \ R=99) \tag{4'}$$を計算したものと同じ値になります。
:::

# おわりに
最後まで読んでいただきありがとうございました。

zennにて「Python×データ分析」をメインテーマに記事を執筆しているので、ご一読いただけますと幸いです。

https://zenn.dev/s1ok69oo 

また、過去にLTや勉強会で発表した資料が下記リンクにてまとめてありますので、こちらもぜひご一読くださいませ。

https://speakerdeck.com/s1ok69oo


# 参考文献
- 西山他(2019)「計量経済学」有斐閣
- 星野(2009)「調査観察データの統計科学」岩波出版
- 安井(2020)「効果検証入門」技術評論社


### 参考リンク
- [A/Bテスト（RCT）におけるノンコンプライアンスと操作変数法の応用](https://qiita.com/s1ok69oo/items/d9f3a3860388f3b9911d)
- [Pythonで因果推論(7)~操作変数(IV)を用いた効果検証~](https://zenn.dev/s1ok69oo/articles/3b75bbfd12b6a2)
- [Pythonで因果推論(8)~回帰不連続デザイン(RDD)を用いた効果検証~](https://zenn.dev/s1ok69oo/articles/f40ca93fe31a8d)
- [マーケティング施策のおける効果検証入門](https://speakerdeck.com/s1ok69oo/maketeingushi-ce-nookeruxiao-guo-jian-zheng-ru-men)
- [マーケティング指標におけるデータサイエンス入門](https://speakerdeck.com/s1ok69oo/maketeinguzhi-biao-niokerudetasaiensuru-men)