Polarsで効果検証入門 第１章のデータ集計と有意差検定をやってみた

# はじめに
今回は[効果検証入門](https://www.amazon.co.jp/dp/B0834JN23Y/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1)の学習とPolarsの学びを兼ねて、Rで書かれたコードをPythonのPolarsを使って再現してみました。
乱数を使用していることから、書籍と異なっている部分がありますがご了承ください。
コードが間違っている場合にはご指摘いただけますと幸いです。
<!-- 発端や概要を記載 -->

# 目次
<!-- タイトルとアンカー名を編集 -->
1. [RCTデータの集計と有意差検定](#Chapter1)
1. [バイアスのあるデータによる検証](#Chapter2)
1. [参考文献](#reference)

<!-- 各チャプター -->
<a id="#Chapter1"></a>
# RCTデータの集計と有意差検定
## データの作成
データセットは[The MineThatData E-Mail Analytics And Data Mining Challenge](http://www.minethatdata.com/Kevin_Hillstrom_MineThatData_E-MailAnalytics_DataMiningChallenge_2008.03.20.csv)のメールマーケティングデータを使用しています。
```python
# データの読込
import pandas as pd
import polars as pl
data = pd.read_csv("http://www.minethatdata.com/Kevin_Hillstrom_MineThatData_E-MailAnalytics_DataMiningChallenge_2008.03.20.csv")
#なぜかpl.read_csvをするとspendを正しく読み込めない
data = pl.from_pandas(reserve_tb.reset_index())
```

1. segmentカラムのWomens E-Mailを除去し、女性向けメールを除いたデータセットを作成（男性向けとメール配信なしのグループに絞る）

2. 介入（メールの配信）の示すフラグを立てる
(treatment：Mens E-Mail = 1、配信なし＝0)

```python
data.with_columns(
    pl.when(pl.col("segment") == "Mens E-Mail")
    .then(1)
    .otherwise(0)
    .alias("treatment")
)

```
## 有意差検定
介入ありとなしのそれぞれのグループで、購入の発生確率(conversion)と購入金額(spend)の平均を計算
```python
male_df = data.filter(
    pl.col('segment') != 'Womens E-Mail'
)

male_d.with_columns(
    pl.when(pl.col("segment") == "Mens E-Mail")
    .then(1)
    .otherwise(0)
    .alias("treatment")
    ).groupby('treatment').agg(
        pl.col('conversion').mean(),
        pl.col('spend').mean()
)

```
出力結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3333303/c47f0fa2-e529-d881-1827-bb1f16b95c60.png)
Polarsならこんな処理も一気に…

次に、介入グループ（男性向けメールが配信）の購買データと、非介入（メール配信なし）グループの購買データで有意差検定（２群間の平均の差でt検定）

```python
# 男性向けメールが配信されたグループ
mens_mail_df = male_df.filter(
        pl.col('treatment') == 1,
        ).select(
        pl.col('spend')
    )
# メール配信なしのグループ
no_mail_df = male_df.filter(
    pl.col('treatment') == 0
    ).select(
    pl.col('spend')
)

# ２群間のt検定
# 平均の差に対して有意差検定を実行
from scipy import stats
stats.ttest_ind(mens_mail_df, no_mail_df, equal_var=True)
```
出力：Ttest_indResult(statistic=array([5.30009029]), pvalue=array([1.16320087e-07]))

p値は1.16e-07と非常に小さいため統計的に有意
<a id="#Chapter2"></a>
# バイアスのあるデータによる効果の検証
## バイアスをつけたデータの準備
1. 介入データ側から購入意欲の小さい人をランダムで除く
2. 非介入データからは購入意欲の高い人をランダムで除く

という処理を行い、無理やりバイアスを擬似的に作成
具体的には

「去年の購入額であるhistoryが300より大きい場合」,
「最後の購入であるrecencyが6より小さい場合」,
「接触チャネル channelが複数あることを表すMultichannelである場合」のOR条件を満たす人を意欲的と判断する。

```python
# データを複製
bias_df = male_df.__copy__()

# メールが配信されていないグループ
# ランダムで50%を抽出
no_mail_del_df = bias_df.select(
    bias_df.filter(pl.col('treatment') == 0)
    .filter((pl.col('history') > 300) | (pl.col('recency') < 6) | (pl.col('channel') == 'Multichannel'))
).sample(fraction=0.5, seed=1)

# 抽出したユーザーを削除
no_mail_bias_df = bias_df.select(
    bias_df.filter(
        ~pl.col('index').is_in(no_mail_del_df['index'])
    )    
)

# メールが配信されているグループ
male_mail_del_df = bias_df.select(
    bias_df.filter(pl.col('treatment') == 1)
    .filter((pl.col('history') <= 300) & (pl.col('recency') >= 6) & (pl.col('channel') != 'Multichannel'))
).sample(fraction=0.5, seed=1)

# 抽出したユーザーをさらに削除
male_mail_bias_df = no_mail_bias_df.select(
    no_mail_bias_df.filter(
        ~pl.col('index').is_in(male_mail_del_df['index'])
    )
)
```
## 有意差検定
バイアスをつけた群間の検定を先ほどと同様に行います。
まずは、グループ毎にconversionとspendの平均を算出
```python
# バイアスありのデータの
male_mail_bias_df.groupby('treatment').agg(
        pl.col('conversion').mean(),
        pl.col('spend').mean()
)
```
出力結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3333303/9514cca5-93db-2588-03dc-e162473e865d.png)
バイアスなしの結果と比較すると、conversionの差が大体0.007→0.008くらい若干広がっています。spendも同様に差が広がっていることが確認できます。
ここで、平均の差についてt検定を行うと

```python
# 男性向けメールが配信されたグループ
mens_mail_df =male_mail_bias_df.filter(
        pl.col('treatment') == 1,
    ).select(
        pl.col('spend')
    )
# メール配信なしのグループ
no_mail_df = male_mail_bias_df.filter(
    pl.col('treatment') == 0
).select(
    pl.col('spend')
)

# ２群間のt検定
# 平均の差に対して有意差検定を実行
from scipy import stats
stats.ttest_ind(mens_mail_df, no_mail_df, equal_var=True)
```
出力：Ttest_indResult(statistic=array([4.86607937]), pvalue=array([1.14376213e-06]))

p値は非常に小さく、統計的に有意となります。

p値がバイアスかける前よりなぜか大きくなっている…。
どこかでミスをしているか、乱数の影響…。

<a id="#reference"></a>
# 参考文献
- [Polars documentation](https://pola-rs.github.io/polars/py-polars/html/reference/)
- [効果検証入門〜正しい比較のための因果推論/計量経済学の基礎](https://www.amazon.co.jp/%E5%8A%B9%E6%9E%9C%E6%A4%9C%E8%A8%BC%E5%85%A5%E9%96%80%E3%80%9C%E6%AD%A3%E3%81%97%E3%81%84%E6%AF%94%E8%BC%83%E3%81%AE%E3%81%9F%E3%82%81%E3%81%AE%E5%9B%A0%E6%9E%9C%E6%8E%A8%E8%AB%96-%E8%A8%88%E9%87%8F%E7%B5%8C%E6%B8%88%E5%AD%A6%E3%81%AE%E5%9F%BA%E7%A4%8E-%E5%AE%89%E4%BA%95-%E7%BF%94%E5%A4%AA/dp/4297111179/ref=sr_1_1?adgrpid=75791098064&hvadid=618630202492&hvdev=c&hvlocphy=1009280&hvnetw=g&hvqmt=e&hvrand=12518901175332425417&hvtargid=kwd-903018875247&hydadcr=846_1015064043&jp-ad-ap=0&keywords=%E5%8A%B9%E6%9E%9C%E6%A4%9C%E8%A8%BC%E5%85%A5%E9%96%80&qid=1681735849&sr=8-1)