因果推論でデジタル広告のCPA・ROASを改善する方法

# 記事の概要
現在、デジタル広告の成長率は著しい。インターネットに接続すると、
多くのページに広告が埋め込まれている。広告には必ず配信している
広告主がおり、多額の広告費用が使われている。

しかし、多くの広告主は費用を下げたいと考えているはずだ。
よって、今回はコンバージョン数を下げずに広告費を減らせる
可能性がある方法を記載する。

# 広告主が取り扱う広告のKPI
広告主は主に以下の指標でデジタル広告を評価する。
　　CVs = 訪問ユーザ数(全部) x コンバージョン率
　　Cost = 訪問ユーザ数（広告経由） x クリック単価
　　Revenue = 訪問ユーザ数(全部) x コンバージョン率 x 単価 x 購入回数
　　CPA = Cost / Cvs
　　ROAS =  Revenue / Cost

訪問ユーザ数（広告経由） x クリック単価が広告費用となる。
これを下げれば、CPA・ROASも改善する仕組みだ。


# 広告の貢献度の評価
広告の分析にはアトリビューションというものがあり、広告の貢献度を図るために定義された指標だ。
購入の直前に触れた広告のみを評価するラストクリックや、購入までの最初に触れた広告のみを評価する
ファーストクリックが一般的だ。
　https://anagrams.jp/blog/basic-of-attribution/

アトリビューション分析によって広告媒体のCPAやROASを計測し、どの時点で貢献しているかが可視化できる。

ただ、これだけでは広告の貢献度を図るのは難しい。なぜなら、広告に触れた
ユーザがコンバージョンしても、広告がきっかけなのかはわからない。
つまり、「広告に触れなくても」コンバージョンしていた可能性がある。

# 因果推論によるターゲット分類
ここで使用するのが因果推論だ。因果推論は簡潔に言うと「原因」と
「結果」を明らかにする手法だ。例えば医療業界で言うと、「この薬を
飲んだから（原因）、病気が治った（結果）」、ということを検証するために
利用される。以下の事例がわかりやすい。
https://healthpolicyhealthecon.com/2014/09/30/study-design-overview/


広告で言うと「広告に触れて（原因）、商品を購入した（結果）」と
置き換えられる。因果推論を使用する場合、「ある広告に触れたユーザ」と
「ある広告に触れなかったユーザ」が、それぞれ「コンバージョンしたか否か」
のデータを収集する。

このデータを因果推論のモデルに学習させ、テストデータで予測させると、
ターゲットを以下のように分類できる。

　① あまのじゃくユーザ：広告を配信すると、コンバージョンしなくなる。
　② 無関心ユーザ：広告を配信有無に関わらず、コンバージョンしない。
　③ 確実購入ユーザ：広告を配信有無に関わらず、コンバージョンする。
　④ 説得可能ユーザ：広告を配信すると、コンバージョンする。

例えば、モデルの予測で④の数が多ければ、広告がユーザを説得して
いることになるので、それは配信するべき広告となる。

しかし、①②③の数が多い場合は広告を配信しなくてもユーザは
コンバージョンするし、コンバージョンを阻害している可能性もある。

# CPA・ROASを改善するために

ここまででわかる通り、因果推論でCPA・ROASを改善する方法は以下だ。
・因果推論モデルを用いてターゲットを分類する。
・説得可能ユーザが多い広告の出稿を増やすか、配信不要ユーザが多い広告の
　出稿を減らす。

この流れをPythonで実装してみる。

# 1. モジュールインポート
```
# Main imports
from econml.metalearners import TLearner, SLearner, XLearner, DomainAdaptationLearner, DoublyRobustLearner
from sklearn.linear_model import LinearRegression
from sklearn.ensemble import RandomForestRegressor, RandomForestClassifier, GradientBoostingRegressor
from sklearn.model_selection import train_test_split

# Helper imports 
import os, sys
import numpy as np
import pandas as pd
from numpy.random import binomial, multivariate_normal, normal, uniform
import matplotlib.pyplot as plt
```

# 2.データセットの作成
データは以下から取得。BigQueryに存在するGoogleAnalyticsの
公開データセットで、サイトへのアクセス情報だけでなく広告経由の流入も
格納されている。既にダウンロードして、results_bqga.csvにまとめている。  
 　　https://bigquery.cloud.google.com/table/bigquery-public-data:google_analytics_sample.ga_sessions_20170801

```
os.listdir("../input")
df_bqga = pd.read_csv("../input/results_bqga.csv")
# 欠損値の確認
df_bqga.isnull().sum()

 total_visits              0
 total_transaction    890508
 campaign                  0
 source                    0
 medium                    0
 fullVisitorId             0
 visitNumber               0

# 欠損値を0に変換
df_bqga["total_transaction"] = df_bqga["total_transaction"].apply(lambda x : 0 if x != x else x)

# ユーザ、参照元、流入経路、キャンペーンでグルーピング
cols = ["fullVisitorId", "source", "medium", "campaign", "visitNumber", "total_visits"]
df_bqga_grp = df_bqga.groupby(by=cols).sum().reset_index()
```

# 3. CATE計算のためのデータ作成
CATEは「ある特徴量で条件付けた際の介入の因果効果の期待値」と言える。
つまりCATEが高いユーザは広告を付与した場合の効果が高いので、広告を配信するべきという結論が得られる。主に３つのデータが必要である。

・Outcome（Y） = 目的変数でコンバージョンを達成したか、売上高など
・Treatment（T） = 広告やクーポンなど、アクションに触れたか否か
・Feature（X）= サイト訪問数や属性など、ユーザの特徴

以下のコードで上記のデータを作成している。

```
# Treatmentの作成（広告に触れたか否か）
df_bqga_grp["Treatment"] = df_bqga_grp.medium.apply(lambda x : 1 if x in ["cpc", "cpm", "affiliate"] else 0)

# Outcomeの作成（購入したか否か）
df_bqga_grp["Outcome"] = df_bqga_grp.total_transaction.apply(lambda x : 0 if x == 0 else 1)

# Featureの作成（自然検索で訪問したか、再訪問したか、何回訪問したか）
df_bqga_grp["is_organic"] = df_bqga_grp.medium.apply(lambda x : 1 if x == "organic" else 0)
df_bqga_grp["is_return"] = df_bqga_grp.visitNumber.apply(lambda x : 0 if x == 1 else 1)
df_bqga_grp = pd.get_dummies(data=df_bqga_grp, columns=["total_visits"])

# 不要な列の作成
df_bqga_grp.drop(columns=["source", "medium", "campaign", "visitNumber" ,"total_transaction"], axis=1, inplace=True)

df_data = df_bqga_grp.groupby(by=["fullVisitorId"]).sum().reset_index()

# 全データを0 or 1に変換
df_data.iloc[:, 1] = df_data.iloc[:, 1].apply(lambda x : 0 if x == 0 else 1)
df_data.iloc[:, 2] = df_data.iloc[:, 2].apply(lambda x : 0 if x == 0 else 1)
df_data.iloc[:, 3] = df_data.iloc[:, 3].apply(lambda x : 0 if x == 0 else 1)
df_data.iloc[:, 4] = df_data.iloc[:, 4].apply(lambda x : 0 if x == 0 else 1)
df_data.iloc[:, 5] = df_data.iloc[:, 5].apply(lambda x : 0 if x == 0 else 1)
df_data.iloc[:, 6] = df_data.iloc[:, 6].apply(lambda x : 0 if x == 0 else 1)
df_data.iloc[:, 7] = df_data.iloc[:, 7].apply(lambda x : 0 if x == 0 else 1)

# 不均衡データの修正
df_case1 = df_data[(df_data["Treatment"] == 1) & (df_data["Outcome"] == 1)]
df_case2 = df_data[(df_data["Treatment"] == 1) & (df_data["Outcome"] == 0)].iloc[0:1000, :]
df_case3 = df_data[(df_data["Treatment"] == 0) & (df_data["Outcome"] == 1)].iloc[0:1000, :]
df_case4 = df_data[(df_data["Treatment"] == 0) & (df_data["Outcome"] == 0)].iloc[0:1000, :]
```

# 4. データ分割
　トレーニング＆テストデータに分割

```
train_df, test_df = train_test_split(df_data, test_size=0.2, random_state=0, stratify=df_data['Treatment'])

# ユーザIDを抽出しておき、データセットからはユーザIDを削除
user_ids = test_df.fullVisitorId
train_df.drop(["fullVisitorId"], inplace=True, axis=1)
test_df.drop(["fullVisitorId"], inplace=True, axis=1)

```

# 5. CATEの計算と予測（ここからが本題）
先ほど作成したデータから広告を配信するべきユーザ＝CATE値が高い
ユーザを算出する。今回はeconmlを用いて実行する。
モジュールの詳細を知りたい方は以下を参照してほしい。 
 　　https://github.com/microsoft/EconML

## EconMLとは？
EconMLはMicrosoftが作成したパッケージで、計量経済学と機械学習を
融合させたもの。このツールを用いて、意思決定を自動化することが
最終目的らしい。今回はEconMLを用いてユーザ毎のCATE値を計算してみる。

### データの投入
```
# 学習に必要なデータを投入
T = train_df.Treatment
Y = train_df.Outcome
X = train_df.drop(["Outcome", "Treatment"], axis=1)

# テスト用データにはユーザの特徴のみを投入しておく
X_test = test_df.drop(["Outcome", "Treatment"], axis=1)
```

### モデル学習
EconMLには様々な計算用アルゴリズムが用意されている。その中の一つで
Meta-Learnersパッケージの中からDR-Learnerのアルゴリズムを使用して
学習する。

```
# Instantiate Doubly Robust Learner
outcome_model = GradientBoostingRegressor(n_estimators=100, max_depth=6)
pseudo_treatment_model = GradientBoostingRegressor(n_estimators=100, max_depth=6)
propensity_model = RandomForestClassifier(n_estimators=100, max_depth=6, 
                                                  class_weight='balanced_subsample')

DR_learner = DoublyRobustLearner(outcome_model=outcome_model, pseudo_treatment_model=pseudo_treatment_model,
                                 propensity_model=propensity_model)
# Train DR_learner
DR_learner.fit(Y, T, X)
```

### CATE値の予測
最後にEconMLでCATE値を予測して、ユーザIDと並べてみる。

```
# Estimate treatment effects on test data
DR_te = DR_learner.effect(X_test)
df_cate = pd.DataFrame({"user-id" : user_ids, "cate" : DR_te})
df_cate.head()
	user-id	        cate
	2.121750e+15	-0.188887
	2.766490e+17	-0.086540
	2.252240e+15	-0.137244
	1.563470e+15	-0.188887
	1.677000e+15	0.597261
```

# CATEを計算した後
これでユーザ毎のCATE値が計算できた。あとはこの値に従って広告を配信する
ユーザと停止するユーザを振り分ける。ただ、いきなり広告配信を取りやめる
のは勇気がいるので、徐々に予算を減らしていき総コンバージョン数が
変わらないことを検証しながら運用していくのだ。

機械学習はマーケティングの分野にも確実に応用されていき、クリエイティブ
出ない分野はどんどん効率化されていくだろう。