Causal ML パッケージと学ぶ Meta-Learner

# はじめに
仕事関係で [Uplift Modeling](https://qiita.com/usaito/items/af3fa59d0ee153a70350) について調べていたら、CATE (Conditional Average Treatment Effect) にたどり着きました。
CATE は ATE (Average Treatment Effect) をある特徴量で条件付けたもので、ATE が"平均的な"処置効果を算出しているのに対し、効果は各属性 (特徴量) によって変わるはずであるという考えのもと、非均質性 (heterogeneity) を織り込んだ形での処置効果を算出しています。

$$ATE:=E[Y(1)-Y(0)]$$
$$CATE:=E[Y(1)-Y(0)|X=x]$$

ここで、$Y(1)$、$Y(0)$ は潜在的結果変数、$X=x$ はある特徴量となります。

CATE、すなわち個人やセグメントレベルでの処置効果を推定することができれば、処置効果がプラスの人にのみキャンペーンを打つなどのターゲティング最適化や、商材・チャネルなどのパーソナライゼーションが可能となります。

# Causal ML とは
Uber Technologies のメンバーが開発した、機械学習を用いた因果推論手法を提供する Python パッケージで、実験データ／観察データから CATE を推定することができます。
以下の手法が提供されていますが、本稿では S-Learner、T-Learner、X-Learner について学んでいきます。

- Tree-based algorithms
    - Uplift tree/random forests on KL divergence, Euclidean Distance, and Chi-Square
    - Uplift tree/random forests on Contextual Treatment Selection
- Meta-learner algorithms
    - S-learner
    - T-learner
    - X-learner
    - R-learner

# 事前準備
#### Causal ML インストール

```sh
> pip install causalml
```

#### ライブラリインポート

```python
import pandas as pd
import numpy as np
from matplotlib import pyplot as plt
%matplotlib inline

from sklearn.model_selection import train_test_split
from xgboost import XGBRegressor

from causalml.inference.meta import BaseXRegressor, BaseSRegressor, BaseTRegressor
```

#### データ準備
『[岩波データサイエンス Vol.3](https://www.iwanami.co.jp/book/b243764.html)』中の、CM 接触によるアプリ利用への因果効果推定で用いられたデータを使用します。
　[加藤・星野　サポートページ（岩波データサイエンスVol.3 特集 因果推論）](https://sites.google.com/site/iwanamidatascience/vol-3/vol3-ingasuiron/vol3-katouhoshino)

```python
# データをダウンロード
path = 'https://raw.githubusercontent.com/iwanami-datascience/vol3/master/kato%26hoshino/q_data_x.csv'
df = pd.read_csv(path)

X = df[['area_kanto', 'area_tokai', 'area_keihanshin', 'age', 'sex', 'marry_dummy', 'child_dummy',
        'job_dummy1', 'job_dummy2', 'job_dummy3', 'job_dummy4', 'job_dummy5', 'job_dummy6', 'job_dummy7', 
        'inc', 'pmoney', 'fam_str_dummy1', 'fam_str_dummy2', 'fam_str_dummy3', 'fam_str_dummy4', 'TVwatch_day']]
Y = df['gamesecond'] # アプリ利用秒数
W = df['cm_dummy'] # CM接触有無

# 学習データとテストデータに分割 (Wで層化)
X_train, X_test, Y_train, Y_test, W_train, W_test = train_test_split(X, Y, W, test_size=0.2, shuffle=True, random_state=42, stratify=W)
```

# S-Learner
**Stage 1**
処置の割り当てを特徴量に含めた単一の予測モデルを作成し、平均アウトカム $μ(x)$ を推定

```math
μ(x)=E[Y|X=x, W=w]
```

**Stage 2**
CATE の推定値：

```math
\hat{τ}(x)=\hat{μ}(x, W=1)-\hat{μ}(x, W=0)
```

```python
# S-Learnerインスタンスを作成 (今回はXGBoostベース)
learner_s = BaseSRegressor(learner=XGBRegressor(random_state=42))

# 学習
learner_s.fit(X=X_train, treatment=W_train, y=Y_train)

# テストデータに対して予測
cate_s = learner_s.predict(X=X_test)
```

cate_s には、テストデータの各行に対する CATE の推定値が格納されています。

```
array([[ 1139.97802734],
       [  -22.8037262 ],
       [-1353.3692627 ],
       ...,
       [ -751.50939941],
       [ 1418.30859375],
       [ -743.94995117]])
```

# T-Learner
**Stage 1**
処置を行ったデータ、処置を行わなかったデータからそれぞれ予測モデルを作成し、平均アウトカム $μ_1(x)$、$μ_0(x)$ を推定

```math
μ_1(x)=E[Y(1)|X=x] \\
μ_0(x)=E[Y(0)|X=x]
```

**Stage 2**
CATE の推定値：

```math
\hat{τ}(x)=\hat{μ_1}(x)-\hat{μ_0}(x)
```

```python
# T-Learnerインスタンスを作成 (今回はXGBoostベース)
learner_t = BaseTRegressor(learner=XGBRegressor(random_state=42))

# 学習
learner_t.fit(X=X_train, treatment=W_train, y=Y_train)

# テストデータに対して予測
cate_t = learner_t.predict(X=X_test)
```

# X-Learner
**Stage 1**
処置を行ったデータ、処置を行わなかったデータからそれぞれ予測モデルを作成し、平均アウトカム $μ_1(x)$、$μ_0(x)$ を推定

```math
μ_1(x)=E[Y(1)|X=x] \\
μ_0(x)=E[Y(0)|X=x]
```

**Stage 2**
処置群、統制群における条件付き処置効果 $D_i^{1}(x)$、$D_i^{0}(x)$ を算出


```math
D_i^{1}(x)=Y_i^{1}-\hat{μ_0}(X_i^{1}) \\
D_i^{0}(x)=\hat{μ_1}(X_i^{0})-Y_i^{0}
```

$D^{1}$、$D^{0}$ をターゲットとする予測モデルをそれぞれ作成し、CATT (Conditional Average Treatment Effect on the Treated)、CATU (Conditional Average Treatment Effect on the Untreated) を推定

```math
τ_1(x)=E[D^{1}|X=x] \\
τ_0(x)=E[D^{0}|X=x]
```

**Stage 3**
CATE の推定値：

```math
\hat{τ}(x)=g(x)\hat{τ_0}(x)+(1-g(x))\hat{τ_1}(x)
```

$g(x)$ は 0 から 1 の範囲の値をとる重み関数であり、傾向スコア $P[W=1|X=x]$ の推定値を用いることが多い


```python
# X-Learnerインスタンスを作成 (今回はXGBoostベース)
learner_x = BaseXRegressor(learner=XGBRegressor(random_state=42))

# 学習
# 今回のように傾向スコアを指定しない場合は、ElasticNetにより自動で計算される
learner_x.fit(X=X_train, treatment=W_train, y=Y_train)

# テストデータに対して予測
cate_x = learner_x.predict(X=X_test)
```

# 比較
各手法における CATE の推定値をバイオリンプロットで確認します。

```python
plt.figure(figsize=(12,8))
plt.violinplot([cate_s.flatten(), cate_t.flatten(), cate_x.flatten()], showmeans=True)
plt.xticks([1,2,3], ['S-learner', 'T-learner', 'X-learner'])
plt.ylabel('Distribution of CATE')
```

![violinplt.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/488765/54cdd076-2d94-db20-b959-9494194d126d.png)

S-Learner < X-Learner < T-Learner の順にばらつきが大きくなっています。が、これ以上は何も言えませんね。。各手法の特徴やどのようなケースに適しているのかは今後学んでいきます。
ちなみに、S-Learner は処置の割り当て $W$ で木の分割を行わないケースがままあり、推定値がゼロに寄りやすいとのこと。

# 注意点
- 処置の割り当ては観測されている特徴量にのみ依存していること
    - There are no hidden confounders
- 処置群、統制群における特徴量のパターンに大きな偏りがないこと
    - 偏りがあると外的妥当性が限定される


# 参考
- [Welcome to Causal ML's documentation](https://causalml.readthedocs.io/en/latest/)
- [causalml - Meta-Learner Example Notebook](https://github.com/uber/causalml/blob/master/examples/meta_learners_with_synthetic_data.ipynb)
- [Meta-learners for Estimating Heterogeneous Treatment Effects using Machine Learning](https://arxiv.org/pdf/1706.03461.pdf)
- [AIで原因と結果を把握する ~機械学習と因果推論の融合 Meta-Learner~](https://note.com/tak1/n/nc28b93a07452)
- [岩波データサイエンスvol.3のCM接触の因果効果を他の方法で推定してみた(3)](https://yuranhiko.hatenablog.com/entry/causal_inference_iwanami3-3)