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')
局所的だとまた原因を探る必要がありそうですが、全体的に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')
以上、A/Bテスト分析でした!!