๋ฐ์ดํฐ ์ถ์ฒ : ์ผ๊ธ
https://www.kaggle.com/code/sergylog/ab-test-data-analysis
๋ผ์ด๋ธ๋ฌ๋ฆฌ & ๋ฐ์ดํฐ ๋ถ๋ฌ์ค๊ธฐ
import numpy as np
import pandas as pd
from scipy.stats import mannwhitneyu
from scipy.stats import ttest_ind
from scipy.stats import norm
from scipy.stats import pearsonr
from scipy.stats import shapiro
import matplotlib.pyplot as plt
import seaborn as sns
from tqdm.auto import tqdm
df = pd.read_csv('AB_Test_Results.csv')
๋ฐ์ดํฐ ํ์
- ํต์ ์ง๋จ๊ณผ ๋ณํ(test)๋ฅผ ์ ์ฉํ ์คํ์ง๋จ ๋ณ์ ํ์ธ : VARIANT_NAME
df.head()
df.nunique()
df.describe()
- A/B test ๊ทธ๋ฃน ํ์ธ
double_variant_count = df.groupby('USER_ID')['VARIANT_NAME'].nunique().value_counts()
double_variant_count
double_variant_count / double_variant_count.sum()
--
1 0.756325
2 0.243675
๐ฏ ํ์ธํ ์ฌํญ : ํ ๋ช ์ ์ ์ ๊ฐ ํต์ & ์คํ์ง๋จ์ ๋์์ ์ํ๋ฉด ๊ฒฐ๊ณผ ํด์์ด ์ด๋ ค์์ง โถ๏ธ ์ค๋ณต ์ ์ ๋ ์ ์ธํ๊ธฐ๋ก ํ๋ค.
# 1๊ฐ์ ํ
์คํธ ๊ทธ๋ฃน์ ์ํ ์ ์ ๋ง ๋ณด๊ธฐ
single_variant_users = df.groupby('USER_ID')['VARIANT_NAME'].nunique() == 1
single_variant_users
# 1๊ฐ ๊ทธ๋ฃน์๋ง ์ํ ์ ์ ํํฐ๋ง
df = df[df['USER_ID'].isin(single_variant_users.index)]
# ํ ๊ฐ์ ํ์ธ
df.groupby('USER_ID')['VARIANT_NAME'].nunique().value_counts().iloc[0] == double_variant_count.iloc[0]
--
True
๋ฐ์ดํฐ ๋ถํฌ ํ์ธ
sns.boxplot(x='VARIANT_NAME', y='REVENUE', data=df)
- outlier๊ฐ ๋ง์ด ๋ณด์ด๋ฏ๋ก, ์ถํ ๋งค์ถ์ก์ ๊ธฐ์ค์ผ๋ก ๋ด๋ฆผ์ฐจ์ ํ์ฌ ํ์ธํด ๋ณด๊ธฐ๋ก ํ๋ค.
- ์ด์์น๋ฅผ ํ์ธํด๋ณด๋, 3342 ์์ด๋์ ์ ์ ์ ๋งค์ถ์ด ์ ๋ ๋๋ค.
# ์ด์์น ์ฐพ๊ธฐ
df.sort_values(by='REVENUE', ascending=False).iloc[:10]
- 3342 ์ ์ ๋ฅผ ํ์ธํด๋ณด๋, ํต์ ์ง๋จ์ ์ํด์๋ค.
- ๋จ์ผ ๊ฑด์ผ๋ก ํ์ธ๋๋ฉฐ, ์ด ๊ฑด์ ์ญ์ ํ์ฌ ๋๋จธ์ง ์ ์ ์ ๋งค์ถ ๋ถํฌ๋ฅผ ํ์ธํด ๋ณด๊ธฐ๋ก ํ๋ค.
df[df['USER_ID']==3342]
# ํด๋น ์ด์์น ์ ์ธ
df = df[df['USER_ID']!=3342]
- ๋ํ, ๋งค์ถ์ด ๋ฐ์ํ์ง ์์ ๊ฑด์ ์ ์ธํ ๊ฒฝ์ฐ์ ์ ์ฒด ๊ฒฝ์ฐ๋ฅผ ๋น๊ตํ์ฌ ๋ฐ์ค ํ๋กฏ์ ๊ทธ๋ ค๋ณธ๋ค.
- ๋งค์ถ์ด ๋ฐ์ํ์ง ์์ ๊ฑด์ ์ ์ธํ์ ๋ ๋ถํฌ๊ฐ ๋ ์ ๋ณด์ธ๋ค.
f, axes = plt.subplots(2, sharex=True, figsize = (5, 12))
sns.boxplot(ax=axes[0], x='VARIANT_NAME', y='REVENUE', data=df)
sns.boxplot(ax=axes[1], x='VARIANT_NAME', y='REVENUE', data=df[df['REVENUE']>0])
plt.xticks(np.arange(2), ('control', 'variant'))
plt.show()
- ์ค์ ๋ก ๋งค์ถ์ด 0์ธ ๊ณ ๊ฐ์ด ๋ง์์ง ํ์ธํด๋ณธ๋ค.
- ๋งค์ถ์ด 0์ธ๋ฐ, 0 ์ด์์ธ ๊ฒ์ผ๋ก๋ ๋ํ๋๋ ๊ณ ๊ฐ์ด 156๋ช ์ด๋ค. ์ฆ, ๊ณ ๊ฐ ๋ฐ์ดํฐ๊ฐ ์ค๋ณต๋์ด ์๋ค.
# ๊ตฌ๋งค๊ฐ ์๋/์๋ ๊ณ ๊ฐ ๋น๊ต
(df.loc[(df['REVENUE']==0)&(df['USER_ID'].isin(df.loc[df['REVENUE']>0, 'USER_ID'].values)), 'USER_ID']).count()
---
156
๊ทธ๋ฃน๋ณ๋ก ํ์ธ
all_stat = df.groupby(by='VARIANT_NAME').agg({'USER_ID':'nunique',
'REVENUE':['sum', 'mean', 'median', 'count']})
- ๊ฐ ์ฌ์ฉ์๋ณ๋ก ์ฃผ๋ฌธ์ด ์ผ๋ง๋ ์์ฃผ ๋ฐ์ํ๋์ง ํ๊ฐํ๊ธฐ ์ํ ์งํ๋ฅผ ๊ณ์ฐํด ๋ณด์
๐ ์ฌ์ฉ์ 1๋ช ๋น ํ๊ท ๊ตฌ๋งค ํ์ - ๊ฒฐ๊ณผ๋ฅผ ๋ณด๋ฉด ๋ ์ง๋จ์ ํฐ ์ฐจ์ด๊ฐ ์๋ ๊ฒ์ผ๋ก ๋ณด์ธ๋ค
orders_per_user = all_stat.loc[:, ('REVENUE', 'count')] / all_stat.loc[:, ('USER_ID', 'nunique')]
orders_per_user
- ์ฌ์ฉ์ 1๋ช ๋น ํ๊ท ๋งค์ถ๋ ๊ณ์ฐํด๋ณธ๋ค.
revenue_per_user = all_stat.loc[:, ('REVENUE', 'sum')] / all_stat.loc[:, ('USER_ID', 'nunique')]
revenue_per_user
- ์์์ ๊ตฌํ ๋ ๊ฐ๋ค์ all_stat์ ์ถ๊ฐํ๋ค.
all_stat.loc[:, ('per_user', 'orders')] = orders_per_user
all_stat.loc[:, ('per_user', 'revenue')] = revenue_per_user
all_stat
๐ ํ๊ท ๊ตฌ๋งค ๊ธ์ก๋ ํต์ ์ง๋จ์ด ๋ ๋์ ๊ฒ์ผ๋ก ํ์ธํ ์ ์๋ค.
๊ตฌ๋งค ์ ์ ๋ค๋ง ๋ถ์
- ์์ ๋์ผํ๊ฒ ์ฝ๋๋ฅผ ์์ฑํ๋, revenue๊ฐ 0์ด ์๋ ๊ฒ๋ง ํํฐ๋งํด ์ค๋ค.
paid_stat = df.loc[df.REVENUE != 0].groupby('VARIANT_NAME').agg({'USER_ID':'nunique',
'REVENUE':['sum', 'mean', 'median', 'count']})
orders_per_user = paid_stat.loc[:, ('REVENUE', 'count')] / paid_stat.loc[:, ('USER_ID', 'nunique')]
revenue_per_user = paid_stat.loc[:, ('REVENUE', 'sum')] / paid_stat.loc[:, ('USER_ID', 'nunique')]
paid_stat.loc[:, ('per_user', 'orders')] = orders_per_user
paid_stat.loc[:, ('per_user', 'revenue')] = revenue_per_user
- ๋์ผํ๊ฒ ์๊ฐํํ๋ค. ๐ ๋ชจ๋ ์ ์ ์ ๊ตฌ๋งค๊น์ง ์ด์ด์ง ์ ์ ์ ์์ต ๋ถํฌ
- ๊ตฌ๋งค๊น์ง ์ด์ด์ง ์ ์ ๊ทธ๋ํ์์ ๋ช ํํ ์ฐจ์ด๊ฐ ๋ ๋ณด์ด๊ณ , ์คํ์ง๋จ์ ๊ตฌ๋งค๊ฐ ๋ง์ด ๋ฐ์ํ ๊ตฌ๊ฐ๋ ํ์ธํ ์ ์๋ค.
f, axes = plt.subplots(2, figsize = (10, 8))
sns.distplot(df.loc[df['VARIANT_NAME']=='control', 'REVENUE'], ax=axes[0], label='control')
sns.distplot(df.loc[df['VARIANT_NAME']=='variant', 'REVENUE'], ax=axes[0], label='variant')
axes[0].set_title('Distribution of revenue of all users')
sns.distplot(df.loc[(df.VARIANT_NAME == 'control') & (df['REVENUE'] > 0), 'REVENUE'], ax=axes[1], label='control')
sns.distplot(df.loc[(df.VARIANT_NAME == 'variant') & (df['REVENUE'] > 0), 'REVENUE'], ax=axes[1], label='variant')
axes[1].set_title('Paying Users revenue Distribution')
plt.legend()
plt.subplots_adjust(hspace=0.3)
ํต๊ณ ๊ฒ์
โถ๏ธ ์ ๊ท๋ถํฌ ์ฌ๋ถ : shapiro-Wilk ๊ฒ์
- ์คํ์ง๋จ์ ์์ต๋ถํฌ๋ ์ ๊ท๋ถํฌ๊ฐ ์๋์ ํ์ธํ ์ ์๋ค.
shapiro(df.loc[df.VARIANT_NAME=='variant', 'REVENUE'])
--
ShapiroResult(statistic=0.027033090591430664, pvalue=0.0)
โถ๏ธ Mann-Whitney ๊ฒ์
- ์์ต์ด 0์ธ ๊ฐ๊ณผ ์ค๋ณต๊ฐ์ด ๋ง์ ๋ฐ์ดํฐ์ด๋ฏ๋ก ์ ์ํด์ผ ํ๋ค.
- ๋ง-์ํธ๋ ๊ฒ์ (Mann-Whitney U ๊ฒ์ )์ ๋ ๊ฐ์ ๋ ๋ฆฝ์ ์ธ ํ๋ณธ ๊ฐ์ ๋น๋ชจ์์ ์ธ ๋น๊ต๋ฅผ ์ํํ๊ธฐ ์ํด ์ฌ์ฉ๋๋ค.
- ๋น๋ชจ์ ๊ฒ์ ์ ๋ฐ์ดํฐ๊ฐ ์ ๊ท ๋ถํฌ๋ฅผ ๋ฐ๋ฅด์ง ์๊ฑฐ๋ ๋ชจ์ง๋จ์ ๋ํ ๋ถํฌ์ ๋ํ ๊ฐ์ ์ ๋ง์กฑํ์ง ์์ ๋ ์ฌ์ฉํ๋ค.
(df['REVENUE']==0).value_counts()
--
True 9848
False 151
- ์คํ์ง๋จ๊ณผ ํต์ ์ง๋จ์ ์์ต์ ๋ํ ๋น๋ชจ์ ๊ฒ์ ๊ฒฐ๊ณผ
- statistics : ๋ ์ง๋จ ๊ฐ ์์ํฉ์ผ๋ก, ์ง๋จ์ด ์ ์ฌํ๋ฉด ๊ฐ์ด ์๊ณ , ์ง๋จ ์ฐจ์ด๊ฐ ํฌ๋ฉด ํฐ ๊ฐ์ด ๋์ด
- pvalue : ํต๊ณ์ ์ผ๋ก ์ ์๋ฏธํ ์ฐจ์ด๊ฐ ์์ (0.05๋ณด๋ค ํผ)
- ์ฆ, ๋ ์ง๋จ ๊ฐ ์ฐจ์ด๊ฐ ํต๊ณ์ ์ผ๋ก ์ ์๋ฏธํ์ง ์์
mannwhitneyu(df.loc[df.VARIANT_NAME=='variant', 'REVENUE'], df.loc[df.VARIANT_NAME=='control', 'REVENUE'])
--
MannwhitneyuResult(statistic=12478180.0, pvalue=0.5291970335120277)
- ์์ต์ด ๋ฐ์ํ ๊ฒฝ์ฐ๋ง ๋ค์ ํ๋ฒ ์ํ
- ๊ฒฐ๊ณผ : ์ญ์ ์ ์๋ฏธํ ์ฐจ์ด ์์
mannwhitneyu(df.loc[(df.VARIANT_NAME=='control')&(df.REVENUE>0), 'REVENUE'],
df.loc[(df.VARIANT_NAME=='variant')&(df.REVENUE>0), 'REVENUE'])
--
MannwhitneyuResult(statistic=3284.0, pvalue=0.10145877111519161)
๊ฒฐ๋ก
๐ ๋จผ์ ์ดํด๋ณด์๋ ๋ฐ์ดํฐ์ ๋ํ ํ์์ ํตํด, ๋ ์ง๋จ ๊ฐ ์ ์๋ฏธํ ๋งค์ถ ์ฐจ์ด๋ฅผ ๋ฐ์ํ์ง๋ ์์ ๊ฒ์ผ๋ก ๋ณด์ธ๋ค.
๐ ํต๊ณ ๊ฒ์ ์ผ๋ก ์ฌํ์ธํด๋ณด์๋ ๊ฒฐ๊ณผ๊ฐ ๊ฐ๋ค.
728x90