koji/メガネ男の日誌

日々の学び、活動状況を記録します。仕事のことは少なめ。

効果検証入門 1章をpythonでやってみた

f:id:kj_man666:20200920201108j:plain

効果検証入門〜正しい比較のための因果推論/計量経済学の基礎の学びを兼ねて、Rで書かれたコードをpythonに置き換えてみました。

コードはこちら

書籍と数値が異なっている点もございます点ご了承ください。

間違いのご指摘等いただけると幸いです。

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

データセットThe MineThatData E-Mail Analytics And Data Mining Challengeのメールマーケティングデータを使用

blog.minethatdata.com

RCT(ランダム化比較実験)データの作成

# データの読込
import pandas as pd
data = pd.read_csv("http://www.minethatdata.com/Kevin_Hillstrom_MineThatData_E-MailAnalytics_DataMiningChallenge_2008.03.20.csv")

segmentカラムのWomens E-Mailを除いて Mens E-Mail と No E-Mail のみにして、女性向けメールを除いたデータセットを作成

介入(メールの配信)の有無(Mens E-Mail なら 1、そうでなければ 0)を表す treatment 変数を追加

male_data = data[data["segment"] != "Womens E-Mail"]

male_data["treatment"] = 0
male_data["treatment"].mask(male_data["segment"] == "Mens E-Mail", 1, inplace=True)

メールが配信されたグループとされなかったグループでの購入の発生確率(conversion)と購入額(spend)の平均を計算

male_data.groupby("treatment").mean()[["conversion", "spend"]]

f:id:kj_man666:20200920203115p:plain

male_data["treatment"].value_counts(sort=False)

0: 21306
1: 21307
Name: treatment, dtype: int64

男性向けメールが配信されたグループの購買データと、メールが配信されなかったグループの購買データを有意差検定

# 男性向けメールが配信されたグループの購買データ
mens_mail = male_data[male_data["treatment"] == 1]["spend"]

# メールが配信されなかったグループの購買データ
no_mail = male_data[male_data["treatment"] == 0]["spend"]

# 2群間でのt検定
from scipy import stats
stats.ttest_ind(mens_mail, no_mail, equal_var=True)

Ttest_indResult(statistic=5.300090294465472, pvalue=1.163200872605869e-07)

p値は1.16e-07と非常に小さいので有意

バイアスのあるデータの作成

  1. 「去年の購入額であるhistoryが300より小さい場合」
  2. 「最後の購入であるrecencyが3より小さい場合」
  3. 接触チャネル channelが複数あることを表すMultichannelである場合」

つまり、1. 過去に多額の購入があり、2. 直近も購入しており、3. 流入元が多ければ、購入に意欲的なユーザー

メールが配信されていないグループでは、3つの条件のどれかに該当するデータをランダムに半分選んで削除
→ メールが配信されていないグループは購入に意欲的なユーザーが半分削除される
→ メールが配信されていないグループは購入に意欲的でないバイアスがかかる

メールが配信されているグループでは、3つの条件に該当しないデータをランダムに半分選んで削除
→ メールが配信されているグループは購入に意欲的でないユーザーが半分削除される
→ メールが配信されているグループは購入に意欲的なバイアスがかかる

メールが配信されていないグループのデータを作成

# データをコピー
biased_data = male_data.copy()

# メールが配信されていないグループ
fulfill_cond_data = male_data[((male_data["treatment"] == 0) & ((male_data["history"] > 300) | (male_data["recency"] < 6) | (biased_data["channel"] == "Multichannel")))]

# 条件を満たすデータから半分抽出する
fulfill_cond_data.sample(frac=0.5, random_state=42)

# 条件を満たすデータの半分を削除する
biased_data.drop(fulfill_cond_data.sample(frac=0.5, random_state=42).index, inplace=True)

メールが配信されているグループのデータを作成

# メールが配信されているグループ
non_fulfill_cond_data = male_data[((biased_data["treatment"] == 1) & (male_data["history"] <= 300) & (male_data["recency"] >= 6) & (male_data["channel"] != "Multichannel"))]

# 条件を満さないデータから半分抽出する
non_fulfill_cond_data.sample(frac=0.5, random_state=42)

# 条件を満たさないデータの半分を削除する
biased_data.drop(non_fulfill_cond_data.sample(frac=0.5, random_state=42).index, inplace=True)

バイアスありのデータのconversion、spendの平均値とカウント数、バイアスなしのデータのconversion、spendの平均値とカウント数を比較

# バイアスありのデータ
display(biased_data.groupby("treatment").mean()[["conversion", "spend"]])

print(biased_data["treatment"].value_counts(sort=False))

# バイアスなしのデータ
print("\n比較 バイアスなしデータ")
display(male_data.groupby("treatment").mean()[["conversion", "spend"]])
print(male_data["treatment"].value_counts(sort=False))

f:id:kj_man666:20200920205208p:plain

バイアスのあるデータで、男性向けメールが配信されたグループの購買データと、メールが配信されなかったグループの購買データを有意差検定

# 男性向けメールが配信されたグループの購買データ
bias_mens_mail = biased_data[biased_data["treatment"] == 1]["spend"]

# メールが配信されなかったグループの購買データ
bias_no_mail = biased_data[biased_data["treatment"] == 0]["spend"]

display(stats.ttest_ind(bias_mens_mail, bias_no_mail, equal_var=True))

Ttest_indResult(statistic=5.283787889513689, pvalue=1.273689559022462e-07)

バイアスありの場合の p値は 1.27e-07、バイアスなしの場合の p値は 1.16e-07

バイアスがあっても p値は非常に小さく、有意という結果となってしまう

# 2章のためにcsvの出力
male_data.to_csv('male_data.csv', index=False)

biased_data.to_csv('biased_data.csv', index=False)

以上になります、最後までお読みいただきありがとうございました。