jiku log

データサイエンスの核心を掴む : 学びと発見の記録

「効果検証入門」を読む ~第1章 セレクションバイアスとRCT ②メールマーケティングの効果検証~

はじめに

実務における効果検証の精度と信頼性を高めるための方法論を学ぶために,安井翔太 著「効果検証入門~正しい比較のための因果推論/計量経済学の基礎」を読むことにした。


本記事は,「第1章 セレクションバイアスとRCT」における,メールマーケティングの効果検証の分析例に関する読書メモである。

  • 本書の紹介ページ

効果検証入門 ~正しい比較のための因果推論/計量経済学の基礎:書籍案内|技術評論社

  • 関連コード

GitHub - ghmagazine/cibook

本記事を読むことで得られること

本記事を読むことで得られることは,主に以下の内容である。

  • RのサンプルコードをPythonで置き換えて実行した例

第1章 セレクションバイアスとRCTとは

1.4 Rによるメールマーケティングの効果の検証

本節では,RCTを行なったデータとバイアスのあるデータを集計することによるグループ間の比較について説明している。


本記事ではサンプルコードをGeminiでPythonコードへ変換して動作確認しながら,その挙動を確認した。
github.com

動作確認は,Google Colab上で行なった。

RCTを行なったデータの準備

本節ではメールマーケティングの効果を,The MineThatData E-Mail Analytics And Data Mining Challenge というデータで確認する。このデータは,ECサイトのユーザに対してRCTを適用したメールマーケティングを行なった際のデータである。

介入は,

  1. 男性向けメールを送る
  2. 女性向けメールを送る
  3. メールを送らない

の3つがランダムに割り振られている。

データの前処理

今回は簡略化のため,女性向けのメールが配信されているデータは削除する。

  • サンプルコード
import pandas as pd
from scipy import stats

# (3) データの読み込み
email_data = pd.read_csv("http://www.minethatdata.com/Kevin_Hillstrom_MineThatData_E-MailAnalytics_DataMiningChallenge_2008.03.20.csv")

# (4) データの準備
male_df = email_data[email_data["segment"] != "Womens E-Mail"].copy()
male_df["treatment"] = (male_df["segment"] == "Mens E-Mail").astype(int)


全データ(email_data)と,削除後のデータ(male_df)の件数を比較した結果は以下の通りである。

  • サンプルコード
print("email_data : ")
print(email_data.shape)
print("male_df:")
print(male_df.shape)
  • 出力
email_data : 
(64000, 12)
male_df:
(42613, 13)

女性向けのメールが配信されているデータを削除すると,サンプル数が64,000から42,613に減っていることが確認できた。

RCTデータの集計と有意差検定

RCTデータに対して,メールの介入による効果を確認する。データ中に出てくる変数conversionは,売上が発生すると1,発生しないと0になる。またspendは売上の金額を表す。


男性向けメールを送るという介入(treatment)を行なったグループと,メールを送らなかったグループをそれぞれ集計し,集計した値で比較をする。

  • サンプルコード
# (5) 集計による比較
summary_by_segment = male_df.groupby("treatment").agg(
    conversion_rate=("conversion", "mean"),
    spend_mean=("spend", "mean"),
    count=("treatment", "count")
)
print("(5) 集計による比較\n", summary_by_segment)
  • 出力
(5) 集計による比較
            conversion_rate  spend_mean  count
treatment                                    
0                 0.005726    0.652789  21306
1                 0.012531    1.422617  21307

この結果を確認すると,メールを送らなかったグループ(treatmentが0)ではconversionが発生する確率が0.573%であるのに対して,メールを送ったグループ(treatmentが1)ではconversionが発生する確率が1.25%であったため,メールが配信されると打ち上げが発生する確率が高くなっており,その差は0.00677であった。またメールを送ったグループの方が,売上も大きくなっている。


次に有意差検定を行なう。有意差検定は,「メールを送ったグループとメールを送らなかったグループを比較すると,売上に差がない」ということを帰無仮説とする検定である。ただし2つのグループの分散は同じであると仮定する。

  • サンプルコード
# (6) t検定を行う
mens_mail = male_df[male_df["treatment"] == 1]["spend"]
no_mail = male_df[male_df["treatment"] == 0]["spend"]

rct_ttest = stats.ttest_ind(mens_mail, no_mail, equal_var=True)
print("(6) t検定\n", rct_ttest)
  • 出力
(6) t検定
 TtestResult(statistic=np.float64(5.300090294465472), pvalue=np.float64(1.163200872605869e-07), df=np.float64(42611.0))

結果を確認すると,t値(statistic)は5.30,p値(pvalue)は1.163e-07であるため,「メールを送ったグループとメールを送らなかったグループを比較すると,売上に差がない」という帰無仮説が棄却されることが分かる。

そのためRCTを行なっているデータでは,この差はメール配信の有無のみで起こるものであると考えられ,かつその差は統計的に有意であるということが確認できた。

バイアスのあるデータによる効果の検証

次に,バイアスのあるデータを作成する。バイアスのあるデータは,メールが配信されていないグループに対して,

  • 昨年の購入額(history)が300より高い
  • 最後の購入である(recency)が3より小さい
  • 接触チャネル(channel)が複数ある

という条件のサンプルをランダムに半分選んで削除することで,最近購入したユーザ,すなわたい潜在的に購入意欲が高いユーザに対してメールが多く配信された状況を作り出す。

  • サンプルコード
# (7) セレクションバイアスのあるデータの作成
import numpy as np
np.random.seed(42)

obs_rate_c = 0.5
obs_rate_t = 0.5

biased_data = male_df.copy()
biased_data["obs_rate_c"] = np.where(
    (biased_data["history"] > 300) | (biased_data["recency"] < 6) | (biased_data["channel"] == "Multichannel"),
    obs_rate_c, 1
)
biased_data["obs_rate_t"] = np.where(
    (biased_data["history"] > 300) | (biased_data["recency"] < 6) | (biased_data["channel"] == "Multichannel"),
    1, obs_rate_t
)
biased_data["random_number"] = np.random.uniform(0, 1, len(biased_data))
biased_data = biased_data[
    ((biased_data["treatment"] == 0) & (biased_data["random_number"] < biased_data["obs_rate_c"])) |
    ((biased_data["treatment"] == 1) & (biased_data["random_number"] < biased_data["obs_rate_t"]))
]


先ほどと同様に,集計による比較を行なった。

  • サンプルコード
# (8) セレクションバイアスのあるデータで平均を比較
summary_by_segment_biased = biased_data.groupby("treatment").agg(
    conversion_rate=("conversion", "mean"),
    spend_mean=("spend", "mean"),
    count=("treatment", "count")
)
print("(8) セレクションバイアスのあるデータで平均を比較\n", summary_by_segment_biased)
  • 出力
(8) セレクションバイアスのあるデータで平均を比較
            conversion_rate  spend_mean  count
treatment                                    
0                 0.004410    0.520996  14740
1                 0.013747    1.524296  17168

メールを送ったグループと送らなかったグループのconversion_rateの差は0.00934となっており,RCTデータにおける差である0.00677よりも大きくなっている。そのため,バイアスによって平均の差が大きくなっていると言える。


同様に有意差検定を行なった結果は以下の通りである。

  • サンプルコード
# (9) t検定を行う(Biased)
mens_mail_biased = biased_data[biased_data["treatment"] == 1]["spend"]
no_mail_biased = biased_data[biased_data["treatment"] == 0]["spend"]

rct_ttest_biased = stats.ttest_ind(mens_mail_biased, no_mail_biased, equal_var=True)
print("(9) t検定(Biased)\n", rct_ttest_biased)
  • 出力
(9) t検定(Biased)
 TtestResult(statistic=np.float64(5.868741094015184), pvalue=np.float64(4.4345049409599506e-09), df=np.float64(31906.0))

p値が4.435e-09となっており,2つのグループの差がさらに大きくなっていると言える。

まとめと感想

今回は,「第1章 セレクションバイアスとRCT」における,メールマーケティングの効果検証の分析例についてまとめた。

RCTのデータもバイアスのあるデータも,メールの配信の有無に応じた差がないとは言えないということが,集計やt検定によって確認することができた。ただしRCTのデータもバイアスのデータも,いずれも「メールの配信の有無による差がないとはいえない」というところまでしかわからず,バイアスによる効果への影響まではわからない,ということも確認できた。
本書の後段では,このようなバイアスの影響を低減して効果を推定する方法が説明されているので,しっかり理解していきたい。

また,RのコードもGeminiを使えばPythonでかなり置き換えられることも理解できた。Rは大昔に触ったことがあるくらいでほとんど忘れてしまったが,Pythonに変換したコードがそれっぽい結果を出力しているのでかなり便利だった。ただし,生成AIの出力するコードが必ずしも正しいとは言えないので,チェックができるよう数学的な背景などは理解していきたい。


本記事を最後まで読んでくださり,どうもありがとうございました。