介入群と非介入群のサンプル数に偏りがあるときの効果検証

サムネイル

はじめに

はじめまして。マーケティング本部 データ戦略部 事業アナリシスグループ所属の竹島です。2023年にデータアナリストとしてDMMに中途入社しました。弊社のデータアナリストは、データ分析によって得られた示唆をもとに、担当事業のグロース支援を行います。具体的な業務内容は、キャンペーンなどの施策提案、ABテストの実施、効果検証などが挙げられます。
本記事では「Average Treatment Effect(ATE)」や「傾向スコア」などの効果検証に関する用語を知っている方を対象に、介入群と非介入群のサンプル数が著しく異なるような状況の効果検証について紹介します。

モチベーション

効果検証をする際、介入群と非介入群のサンプル数が偏ることがあるかと思います。

例えば、キャンペーン商品紹介用ランディングページへ来訪したユーザー(介入群)は、来訪しなかったユーザー(非介入群)の数%程度というような状況を考えます。このような状況の効果検証は、傾向スコアを用いたバランスが非常に難しくなります。そこで、サンプル数が偏っている状況において、効果検証をどう行うべきか試行錯誤した記録として、本記事を書きました。この記事が、現場で効果検証を行っている方の参考になれば幸いです。

例:DMMのランディングページ

DMMのトップページ上部のバナーをクリックして遷移するページがランディングページです。このページから商品のピックアップやその詳細ページに飛ぶことができます。

データセット

ユーザーの購買意欲をモデル化し、それに従って購買金額が決まるような簡易的なデータセットを作りました。ユーザーの購買意欲は「購買するか否か」、「購買金額をいくらにするか」の2つのポテンシャルによって生成されるものとします。それぞれのポテンシャルはベータ分布に従うものとしました。ベータ分布のパラメータは、介入群の方が非介入群より平均が大きくなるように設定し、セレクションバイアスが入るようにしておきます。

vは購買するか否か、xは購買金額を示します。Tは介入の有無を意味しており、T=0のとき非介入群、T=1のとき介入群であるとします。

これらによって、ユーザーの日々の購買金額は以下のようになります。介入期間には、介入効果τが購買金額に作用するように設定しました。

シミュレーション条件は、介入群のユーザー数:10,000、非介入群のユーザー数:10,000、観測期間:200日、介入期間:最後20日、α=0.8、β=1,000、τ=50、s=200、a_0=0.5、a_1=3、b_0=3、b_1=9としました。今回の場合、介入効果はατv_{i,t}=10です。

以下に実際に作成したデータの一部を記載しておきます。介入群の購買意欲が高い傾向にあることがわかります。

効果検証に用いる手法

X-Learner

今回の効果検証にはX-Learnerを用います。X-Learnerは、介入群と非介入群それぞれの側面から見た反実仮想を推定し、傾向スコアによって重み付けをすることで介入効果を算出する手法です。元論文

まず、非介入群のOutcomeを推定する回帰モデルと介入群のOutcomeを推定する回帰モデルそれぞれを準備します。非介入群の回帰モデルに介入群の共変量を代入すれば、介入群に介入が起きないであろう反実仮想のOutcomeを推定できます。また逆も然りです。

次にこのモデルをもとに、介入群の観測データと介入群に介入が起きないであろう反実仮想のOutcomeを比較することで、介入効果を推定できます。

最後に、介入効果を各種共変量で条件付けたものを傾向スコアで重み付け平均し、介入効果を推定します。

メリットは、モデリングの手軽さとロバスト性です。デメリットは信頼区間を算出する際の計算コストや精度があまり良くないことです(詳細は割愛します)。

データの前処理

X-Learnerはサンプルごとの情報を共変量にする必要があります。ただし、200日分のデータを全て入力するのはコスト上あまり良くないため、月ごとの平均購買金額(Outcome)の形に加工しました。また、ダミー変数として、購買金額にノイズを加えて一定の値で割り、擬似的に購買点数なども準備しました。

前処理後のデータイメージ(※ダミー変数は一部のみ表示)

効果検証

サンプル数に偏りがない場合

まずは、介入群と非介入群のサンプル数に偏りがない場合について、効果推定を行います。今回は、EconMLに実装されているX-Learnerを用いました。

X-Learnerを用いた効果推定はかなり手軽に実装できます。

train, test = train_test_split(raw, train_size=0.7, stratify=raw[treatment])

x_model =  XLearner(models=XGBRegressor(tree_method='gpu_hist'))
x_model.fit(Y=train[target],T=train[treatment],X=train[features])

xtau = x_model.effect(test[features], T0=0, T1=1)
xtau.mean()

推定介入効果は10.83 と算出されました。介入効果は10.0だったので、なかなかの精度だと思います。

サンプル数に偏りがある場合

前置きが長くなりましたが、ここからが今回の主題です。介入群を10,000人の中から500人無作為に抽出し、非介入群は10,000人全てを使います。サンプル数を偏らせた設定で、先ほどと同様に効果推定を行います。

raw_treatment_sample = raw_treatment.sample(500, random_state=99)
raw_sample = pd.concat([raw_ctrl, raw_treatment_sample]).reset_index(drop=True)

train, test = train_test_split(raw_sample, train_size=0.7,
              stratify=raw_sample[treatment], random_state=99)

x_model =  XLearner(models=XGBRegressor(tree_method='gpu_hist'))
x_model.fit(Y=train[target],T=train[treatment],X=train[features])

xtau = x_model.effect(test[features], T0=0, T1=1)
xtau.mean()

推定介入効果は8.13 であり、先ほどよりも実際の値よりも低く見積もられました。

精度低下の要因

X-Learnerで用いた傾向スコアをプロットしてみると、サンプル数が同数(左)と偏らせた(右)方で、バランスはできているようです。しかし、偏らせた方の傾向スコアは、極端に小さい値に寄ってしまっていることがわかります。これは機械学習で不均衡データを扱うときに見られる現象で、モデルはとりあえず0を出力しておけば高い精度を得られるので、このようなことになってしまいます。つまり、見かけ上バランスはできていますが、似たような傾向スコア間でのバランシングがうまくいっていない可能性があります。

時系列モデルによる効果推定

次に、時系列で介入効果を推定する手法を使ってみます。不均衡データへの直接的な対策ではないことに注意していただきたいです。

今回はCausal Impactを使いました。Causal Impactは、Bayesian structure time series model(状態空間モデル)を用いた因果推論の手法です。このモデルでは、時系列データをlocal linear trend(傾向)とseasonality(周期性)でモデリングします。

  • local linear trend

μtは時刻tのtrend、δtはt→t+1への変移量、ηはノイズです。この組み合わせで時系列の傾向を表現します。

  • seasonality

Sは周期性、γは観測データです。これにより、時刻t-sの観測データが時刻t+1に与える影響を表現できます。

介入前の観測データから時系列の傾向や周期性を学習し、そのモデルで介入以降の時系列予測をすれば、介入が起きなかった場合の推移を予測できます(反実仮想)。その予測結果と観測データを比較することで、介入効果を推定します。

Causal Impactによる予測イメージ(引用)

より詳細なCausal Impactの説明はこちらを参考にしてください。

Causal Impactは時系列フォーマットでデータを入力する必要があるため、前処理をやや変更します。

dt_ctrl = y_ctrl.mean(axis=0)
dt_treatment = y_treatment[sampled_member].mean(axis=0)

dt_raw = pd.concat([pd.DataFrame(dt_treatment), pd.DataFrame(dt_ctrl)], axis=1)
dt_raw.columns = ["target", "NOT_CHANGED"]

前処理後のデータイメージ

先ほどのデータセットに対して、Causal Impactで介入効果を推定します。

pre_period, post_period= [0, 179], [180, 199]
ci_imb = CausalImpact(dt_raw, pre_period, post_period)
ci_pred = ci_imb.inferences

effect = ci_pred.dropna()["point_effects"].mean()
effect

推定介入効果は 11.31 でした。X-Learnerの時と比べて少しだけ精度が良くなったように見えます。

---

次に、推定結果のロバスト性を確認します。介入群のサンプル抽出を複数回行い、サンプルの選び方によって推定結果がどう変化するか実験しました。介入群を抽出するサンプル数は100~5,000まで変更し、推定精度を比較しました。

推定結果を比較した図が以下です。エラーバーはbootstrap信頼区間を示しています。

Causal Impactの方が、平均、信頼区間の幅の面から、ロバストになっているかと思います。

まとめ

介入群と非介入群のサンプル数が著しく異なるような状況の効果検証について紹介しました。サンプル数に大きな偏りがある場合、傾向スコアによるバランシングが機能しないため、精度が低下しました。今回はそれらに対して時系列的なアプローチをとることで、推定精度向上を目指してみました。効果検証にはさまざまな手法があるかと思いますが、手法だけにこだわらず、元データの性質に向き合うことが大事だと痛感した事例でした。このような偏りがあるデータに対して、効果検証する際の参考になれば幸いです。
一見Causal Impactの方が優れた手法に見えますが、Causal ImpactはATT(介入群への平均処置効果)になっていること、個別の介入効果が推定できないなど、Causal Impact自体にも制約はあります。

さいごに

現在、事業アナリシスグループではデータアナリストを募集しています。
実データを用いた統計分析や機械学習に興味がある方、データを活用した事業貢献に携わりたい方にエントリーいただけるとうれしいです!ご興味ある方は下記リンクよりご応募ください。心よりお待ちしております。

dmm-corp.com

DMMではカジュアル面談も行っています。気軽に話したい方はぜひお申し込みください。
カジュアル面談はこちら