A/Bテストの結果を解析する

今回はA/Bテストの結果を解析していきます。

与えられたデータは2つ。

imp_df = pd.read_csv('section5-ab_test_imp.csv')
goal_df = pd.read_csv('section5-ab_test_goal.csv')
imp_df.head()
log_date app_name test_name test_case user_id transaction_id
0 2013-10-01 game-01 sales_test B 36703 25622
1 2013-10-01 game-01 sales_test A 44339 25623
2 2013-10-01 game-01 sales_test B 32087 25624
3 2013-10-01 game-01 sales_test B 10160 25625
4 2013-10-01 game-01 sales_test B 46113 25626
goal_df.head()
log_date app_name test_name test_case user_id transaction_id
0 2013-10-01 game-01 sales_test B 15021 25638
1 2013-10-01 game-01 sales_test B 351 25704
2 2013-10-01 game-01 sales_test B 8276 25739
3 2013-10-01 game-01 sales_test B 1230 25742
4 2013-10-01 game-01 sales_test B 17471 25743

いつも通り結合していきます。

all_df  = pd.merge(imp_df, goal_df, on='transaction_id', how='left')
all_df = all_df.drop(['app_name_x', 'test_name_x', 'log_date_y', 'app_name_y', 'test_name_y', 'test_case_y'], axis=1)
all_df.columns = ['log_date_x',   'test_case_x', 'user_id_x',   'transaction_id',  'result']
all_df = all_df.fillna(0)
check=[]
for k in range(len(all_df['result'])):
    if all_df.result[k] == 0 :
        check.append(0)
    else:
        check.append(1)
all_df['results'] = check
all_df.head(50)
log_date_x test_case_x user_id_x transaction_id result results
0 2013-10-01 B 36703 25622 0.0 0
1 2013-10-01 A 44339 25623 0.0 0
2 2013-10-01 B 32087 25624 0.0 0
3 2013-10-01 B 10160 25625 0.0 0
4 2013-10-01 B 46113 25626 0.0 0
5 2013-10-01 A 6605 25627 0.0 0
6 2013-10-01 A 346 25628 0.0 0
7 2013-10-01 A 42710 25629 0.0 0
8 2013-10-01 A 37194 25630 0.0 0
9 2013-10-01 A 123 25631 0.0 0
10 2013-10-01 A 41638 25632 0.0 0
11 2013-10-01 B 49024 25633 0.0 0
12 2013-10-01 B 42199 25634 0.0 0
13 2013-10-01 B 33444 25635 0.0 0
14 2013-10-01 B 9347 25636 0.0 0
15 2013-10-01 B 39412 25637 0.0 0
16 2013-10-01 B 15021 25638 15021.0 1
17 2013-10-01 B 118 25639 0.0 0
18 2013-10-01 A 42885 25640 0.0 0
19 2013-10-01 B 9498 25641 0.0 0
20 2013-10-01 B 16920 25642 0.0 0
21 2013-10-01 A 31438 25643 0.0 0
22 2013-10-01 A 37050 25644 0.0 0
23 2013-10-01 B 47484 25645 0.0 0
24 2013-10-01 A 49528 25646 0.0 0
25 2013-10-01 A 45125 25647 0.0 0
26 2013-10-01 A 35315 25648 0.0 0
27 2013-10-01 A 37195 25649 0.0 0
28 2013-10-01 A 32304 25650 0.0 0
29 2013-10-01 A 45970 25651 0.0 0
30 2013-10-01 A 40298 25652 0.0 0
31 2013-10-01 A 49527 25653 0.0 0
32 2013-10-01 A 45340 25654 0.0 0
33 2013-10-01 A 596 25655 596.0 1
34 2013-10-01 A 5525 25656 0.0 0
35 2013-10-01 B 30727 25657 0.0 0
36 2013-10-01 B 40985 25658 0.0 0
37 2013-10-01 B 45778 25659 0.0 0
38 2013-10-01 A 593 25660 0.0 0
39 2013-10-01 B 49529 25661 0.0 0
40 2013-10-01 B 49530 25662 0.0 0
41 2013-10-01 A 38019 25663 0.0 0
42 2013-10-01 B 18254 25664 0.0 0
43 2013-10-01 B 15521 25665 0.0 0
44 2013-10-01 A 41259 25666 0.0 0
45 2013-10-01 B 122 25667 0.0 0
46 2013-10-01 B 3133 25668 0.0 0
47 2013-10-01 A 1532 25669 0.0 0
48 2013-10-01 B 49531 25670 0.0 0
49 2013-10-01 A 49532 25671 0.0 0
all_df = all_df.drop('result', axis=1)
all_df.head()
log_date_x test_case_x user_id_x transaction_id results
0 2013-10-01 B 36703 25622 0
1 2013-10-01 A 44339 25623 0
2 2013-10-01 B 32087 25624 0
3 2013-10-01 B 10160 25625 0
4 2013-10-01 B 46113 25626 0

さて、綺麗になりました。

ちなみに、resultsとして、実際のクリックを1、クリックしていない場合を0で表しています。

まずはクリック率を見てみます。

child = all_df.pivot_table("results", aggfunc='sum', columns='test_case_x')
parent = all_df.pivot_table("results", aggfunc='count', columns='test_case_x')
rato = child/parent
rato
test_case_x A B
results 0.080256 0.11546

なるほど。Bの方が高いのですね。

ちなみに、他の種類のアプリのクリック率は12%程(本に書いてあった)ということなので、Bには期待できそうです。

しかし、この結果は単なるクリック率ですので、まだ安心できません。

カイ二乗検定を行なって、Bの結果が偶然よかったという帰無仮説を棄却していきたいです。

もう一度、全体を確認。

all_df.head()
log_date_x test_case_x user_id_x transaction_id results
0 2013-10-01 B 36703 25622 0
1 2013-10-01 A 44339 25623 0
2 2013-10-01 B 32087 25624 0
3 2013-10-01 B 10160 25625 0
4 2013-10-01 B 46113 25626 0

resultsとtest_case_xでクロス集計します。

cross = pd.crosstab(all_df.test_case_x, all_df.results)
cross
results 0 1
test_case_x
A 40592 3542
B 38734 5056

scipyのstatsは一発でカイ二乗検定を行うことができるので、それを利用。

stats.chi2_contingency(cross)
(308.37505289322877,
 4.9341396337856319e-69,
 1,
 array([[ 39818.18029207,   4315.81970793],
        [ 39507.81970793,   4282.18029207]]))

2つめの4.9341396337856319e-69がp値となります。

余裕で0.05は下回っていますね。

つまり、Bの結果が偶然良いものになった可能性は棄却できそうです。

一応、データの詳細も見ておきます。

日毎のクリック率を可視化したいと思います。

まずはgroupbyしてcount値をとります。

grouped = all_df.groupby(['test_case_x', 'log_date_x', 'results']).count()
grouped = grouped.drop('transaction_id', axis=1)

カラム名を変更。

grouped.columns = ['count']

クリック率/非クリック率(こっちはついで)を追加します。

list=[]
for k in range(len(grouped)):
    if k % 2 ==  0:
        a = grouped["count"][k] / (grouped["count"][k] + grouped["count"][k+1]) 
        list.append(a)
    else:
        b = grouped["count"][k] / (grouped["count"][k] + grouped["count"][k-1]) 
        list.append(b)
grouped['d_rato'] = list
    
grouped_df = grouped.reset_index()
click_df = grouped_df[grouped_df['results'] == 1]

クリック率だけ抜き出します。 そして可視化。

sns.set()
sns.set_context("notebook")
plt.figure(figsize=(24, 12))
sns.pointplot(x='log_date_x', y='d_rato', data=click_df, hue='test_case_x')

f:id:icchy333:20180215193752p:plain

局所的だとまた原因を探る必要がありそうですが、全体的にBの方が高そうなのでよかったです。

せっかくなので平均線も入れておきます。 赤色は全体のクリック率です。

avg = click_df.d_rato.mean()
sns.set()
sns.set_context("notebook")
plt.figure(figsize=(24, 12))
sns.pointplot(x='log_date_x', y='d_rato', data=click_df, hue='test_case_x')
plt.hlines(avg, -5, 30, "red", linestyles='dashed')
plt.hlines(click_df[click_df['test_case_x']=='A'].d_rato.mean(), -5, 30, "blue", linestyles='dashed')
plt.hlines(click_df[click_df['test_case_x']=='B'].d_rato.mean(), -5, 30, "green", linestyles='dashed')

f:id:icchy333:20180215194550p:plain

以上、A/Bテスト分析でした!!