Skip to Content

A/B ํ…Œ์ŠคํŠธ

์ค‘๊ธ‰๊ณ ๊ธ‰

ํ•™์Šต ๋ชฉํ‘œ

  • A/B ํ…Œ์ŠคํŠธ ์„ค๊ณ„
  • ํ‘œ๋ณธ ํฌ๊ธฐ ์‚ฐ์ •
  • ์ „ํ™˜์œจ/ํ‰๊ท  ๋น„๊ต ๊ฒ€์ •
  • ๊ฒฐ๊ณผ ํ•ด์„

0. ์‚ฌ์ „ ์ค€๋น„ (Setup)

๋ฐ์ดํ„ฐ ์‹ค์Šต์„ ์œ„ํ•ด CSV ํŒŒ์ผ์„ ๋กœ๋“œํ•ฉ๋‹ˆ๋‹ค.

import pandas as pd import numpy as np import seaborn as sns import matplotlib.pyplot as plt from scipy import stats # Load Data orders = pd.read_csv('src_orders.csv', parse_dates=['created_at']) items = pd.read_csv('src_order_items.csv') products = pd.read_csv('src_products.csv') users = pd.read_csv('src_users.csv') # Merge for Analysis df = orders.merge(items, on='order_id').merge(products, on='product_id').merge(users, on='user_id') # Simulate AB Test Data for Examples np.random.seed(42) df['experiment_group'] = np.random.choice(['control', 'treatment'], size=len(df)) # Add slight effect to treatment df.loc[df['experiment_group'] == 'treatment', 'sale_price'] *= 1.05 df['order_amount'] = df['sale_price'] # Alias for example

1. A/B ํ…Œ์ŠคํŠธ๋ž€?

์ •์˜

A/B ํ…Œ์ŠคํŠธ๋Š” ๋‘ ๊ฐ€์ง€ ๋ฒ„์ „(A: ๋Œ€์กฐ๊ตฐ, B: ์‹คํ—˜๊ตฐ)์„ ๋น„๊ตํ•˜์—ฌ ์–ด๋А ๊ฒƒ์ด ๋” ํšจ๊ณผ์ ์ธ์ง€ ๊ฒ€์ฆํ•˜๋Š” ์‹คํ—˜์ž…๋‹ˆ๋‹ค.

ํ™œ์šฉ ์˜ˆ์‹œ

  • ์›น์‚ฌ์ดํŠธ ๋ฒ„ํŠผ ์ƒ‰์ƒ
  • ์ด๋ฉ”์ผ ์ œ๋ชฉ
  • ๊ฐ€๊ฒฉ ์ •์ฑ…
  • ์ถ”์ฒœ ์•Œ๊ณ ๋ฆฌ์ฆ˜

2. ํ‘œ๋ณธ ํฌ๊ธฐ ์‚ฐ์ •

๊ฒ€์ •๋ ฅ ๋ถ„์„

์‹คํ—˜ ์ „์— ํ•„์š”ํ•œ ํ‘œ๋ณธ ํฌ๊ธฐ๋ฅผ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค.

from statsmodels.stats.power import TTestIndPower # ํŒŒ๋ผ๋ฏธํ„ฐ effect_size = 0.2 # ํšจ๊ณผ ํฌ๊ธฐ (์ž‘์Œ: 0.2, ์ค‘๊ฐ„: 0.5, ํผ: 0.8) alpha = 0.05 # ์œ ์˜์ˆ˜์ค€ power = 0.8 # ๊ฒ€์ •๋ ฅ # ํ‘œ๋ณธ ํฌ๊ธฐ ๊ณ„์‚ฐ analysis = TTestIndPower() sample_size = analysis.solve_power( effect_size=effect_size, power=power, alpha=alpha, alternative='two-sided' ) print(f"ํ•„์š” ํ‘œ๋ณธ ํฌ๊ธฐ (๊ทธ๋ฃน๋‹น): {int(sample_size)}") print(f"์ด ํ•„์š” ํ‘œ๋ณธ: {int(sample_size * 2)}")
์‹คํ–‰ ๊ฒฐ๊ณผ
ํ•„์š” ํ‘œ๋ณธ ํฌ๊ธฐ (๊ทธ๋ฃน๋‹น): 393
์ด ํ•„์š” ํ‘œ๋ณธ: 786

3. ์ „ํ™˜์œจ ๋น„๊ต (๋น„์œจ ๊ฒ€์ •)

Z-๊ฒ€์ •

from statsmodels.stats.proportion import proportions_ztest # ๋ฐ์ดํ„ฐ # A๊ทธ๋ฃน: 1000๋ช… ์ค‘ 50๋ช… ์ „ํ™˜ # B๊ทธ๋ฃน: 1000๋ช… ์ค‘ 65๋ช… ์ „ํ™˜ conversions = [50, 65] n_observations = [1000, 1000] # Z-๊ฒ€์ • z_stat, p_value = proportions_ztest(conversions, n_observations, alternative='two-sided') # ์ „ํ™˜์œจ rate_a = conversions[0] / n_observations[0] rate_b = conversions[1] / n_observations[1] lift = (rate_b - rate_a) / rate_a * 100 print(f"A๊ทธ๋ฃน ์ „ํ™˜์œจ: {rate_a:.2%}") print(f"B๊ทธ๋ฃน ์ „ํ™˜์œจ: {rate_b:.2%}") print(f"์ƒ๋Œ€์  ์ฆ๊ฐ€: {lift:.1f}%") print(f"p-value: {p_value:.4f}") if p_value < 0.05: print("โ†’ B๊ทธ๋ฃน์ด ์œ ์˜๋ฏธํ•˜๊ฒŒ ๋” ์ข‹์Œ!") else: print("โ†’ ์œ ์˜๋ฏธํ•œ ์ฐจ์ด ์—†์Œ")
์‹คํ–‰ ๊ฒฐ๊ณผ
A๊ทธ๋ฃน ์ „ํ™˜์œจ: 5.00%
B๊ทธ๋ฃน ์ „ํ™˜์œจ: 6.50%
์ƒ๋Œ€์  ์ฆ๊ฐ€: 30.0%
p-value: 0.1496
โ†’ ์œ ์˜๋ฏธํ•œ ์ฐจ์ด ์—†์Œ

4. ํ‰๊ท  ๋น„๊ต (t-๊ฒ€์ •)

์ฃผ๋ฌธ ๊ธˆ์•ก ๋น„๊ต

from scipy import stats # ๊ทธ๋ฃน๋ณ„ ๋ฐ์ดํ„ฐ group_a = df[df['experiment_group'] == 'control']['order_amount'] group_b = df[df['experiment_group'] == 'treatment']['order_amount'] # t-๊ฒ€์ • t_stat, p_value = stats.ttest_ind(group_a, group_b) print(f"A๊ทธ๋ฃน ํ‰๊ท : ${group_a.mean():.2f}") print(f"B๊ทธ๋ฃน ํ‰๊ท : ${group_b.mean():.2f}") print(f"์ฐจ์ด: ${group_b.mean() - group_a.mean():.2f}") print(f"p-value: {p_value:.4f}") # ํšจ๊ณผ ํฌ๊ธฐ (Cohen's d) pooled_std = np.sqrt((group_a.std()**2 + group_b.std()**2) / 2) cohens_d = (group_b.mean() - group_a.mean()) / pooled_std print(f"Cohen's d: {cohens_d:.3f}")
์‹คํ–‰ ๊ฒฐ๊ณผ
A๊ทธ๋ฃน ํ‰๊ท : $59.49
B๊ทธ๋ฃน ํ‰๊ท : $62.97
์ฐจ์ด: $3.48
p-value: 0.0000
Cohen's d: 0.050

5. ๊ฒฐ๊ณผ ํ•ด์„

์˜์‚ฌ๊ฒฐ์ • ํ”„๋ ˆ์ž„์›Œํฌ

1. p < 0.05 ์ธ๊ฐ€? - No โ†’ ์œ ์˜๋ฏธํ•œ ์ฐจ์ด ์—†์Œ, ์ถ”๊ฐ€ ์‹คํ—˜ ํ•„์š” - Yes โ†’ ๋‹ค์Œ ๋‹จ๊ณ„๋กœ 2. ํšจ๊ณผ ํฌ๊ธฐ๊ฐ€ ์‹ค๋ฌด์ ์œผ๋กœ ์˜๋ฏธ์žˆ๋Š”๊ฐ€? - ์ „ํ™˜์œจ 0.1% ์ฆ๊ฐ€ vs 10% ์ฆ๊ฐ€ - ๋น„์ฆˆ๋‹ˆ์Šค ์ž„ํŒฉํŠธ ๊ณ„์‚ฐ 3. ๋น„์šฉ ๋Œ€๋น„ ํšจ๊ณผ๊ฐ€ ์žˆ๋Š”๊ฐ€? - ๊ตฌํ˜„ ๋น„์šฉ - ์˜ˆ์ƒ ์ˆ˜์ต ์ฆ๊ฐ€

์ฃผ์˜์‚ฌํ•ญ

โš ๏ธ
A/B ํ…Œ์ŠคํŠธ ์ฃผ์˜์‚ฌํ•ญ
  • ํ”ผํ‚น(Peeking): ์‹คํ—˜ ๋„์ค‘ ๊ฒฐ๊ณผ ํ™•์ธ ํ›„ ์กฐ๊ธฐ ์ข…๋ฃŒ ๊ธˆ์ง€
  • ๋‹ค์ค‘ ๋น„๊ต: ์—ฌ๋Ÿฌ ์ง€ํ‘œ ๋™์‹œ ๊ฒ€์ • ์‹œ ๋ณด์ • ํ•„์š” (Bonferroni)
  • ๋…ธ์ถœ ํŽธํ–ฅ: ๊ทธ๋ฃน ๊ฐ„ ํŠน์„ฑ ๋ถˆ๊ท ํ˜• ํ™•์ธ
  • ์™ธ๋ถ€ ์š”์ธ: ์‹œ์ฆŒ, ํ”„๋กœ๋ชจ์…˜ ๋“ฑ ์˜ํ–ฅ ๊ณ ๋ ค

ํ€ด์ฆˆ

๋ฌธ์ œ

A/B ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ๊ฐ€ ๋‹ค์Œ๊ณผ ๊ฐ™์„ ๋•Œ, ์ƒˆ ๋””์ž์ธ(B)์„ ์ ์šฉํ•ด์•ผ ํ• ๊นŒ์š”?

  • A๊ทธ๋ฃน: 5000๋ช…, ์ „ํ™˜ 150๋ช…
  • B๊ทธ๋ฃน: 5000๋ช…, ์ „ํ™˜ 175๋ช…

์ •๋‹ต ๋ณด๊ธฐ

from statsmodels.stats.proportion import proportions_ztest conversions = [150, 175] n = [5000, 5000] z_stat, p_value = proportions_ztest(conversions, n) rate_a = 150/5000 rate_b = 175/5000 lift = (rate_b - rate_a) / rate_a * 100 print(f"A ์ „ํ™˜์œจ: {rate_a:.2%}") print(f"B ์ „ํ™˜์œจ: {rate_b:.2%}") print(f"์ƒ๋Œ€์  ์ฆ๊ฐ€: {lift:.1f}%") print(f"p-value: {p_value:.4f}") if p_value < 0.05: print("\n๊ฒฐ๋ก : B๋ฅผ ์ ์šฉํ•˜์„ธ์š”!") print(f"- ์ „ํ™˜์œจ {lift:.1f}% ์ฆ๊ฐ€") print(f"- ํ†ต๊ณ„์ ์œผ๋กœ ์œ ์˜๋ฏธํ•จ") else: print("\n๊ฒฐ๋ก : ์ถ”๊ฐ€ ์‹คํ—˜์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค")
์‹คํ–‰ ๊ฒฐ๊ณผ
A ์ „ํ™˜์œจ: 3.00%
B ์ „ํ™˜์œจ: 3.50%
์ƒ๋Œ€์  ์ฆ๊ฐ€: 16.7%
p-value: 0.1586

๊ฒฐ๋ก : ์ถ”๊ฐ€ ์‹คํ—˜์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค

์ •๋ฆฌ

A/B ํ…Œ์ŠคํŠธ ์ฒดํฌ๋ฆฌ์ŠคํŠธ

  1. ๊ฐ€์„ค ๋ช…ํ™•ํžˆ ์„ค์ •
  2. ํ•„์š” ํ‘œ๋ณธ ํฌ๊ธฐ ๊ณ„์‚ฐ
  3. ๋ฌด์ž‘์œ„ ๋ฐฐ์ • ํ™•์ธ
  4. ์ถฉ๋ถ„ํ•œ ๊ธฐ๊ฐ„ ์‹คํ—˜
  5. ์ ์ ˆํ•œ ํ†ต๊ณ„ ๊ฒ€์ • ์„ ํƒ
  6. ํšจ๊ณผ ํฌ๊ธฐ์™€ ๋น„์ฆˆ๋‹ˆ์Šค ์ž„ํŒฉํŠธ ํ•จ๊ป˜ ๊ณ ๋ ค

๋‹ค์Œ ๋‹จ๊ณ„

ํ†ต๊ณ„๋ถ„์„ ์„น์…˜์„ ์™„๋ฃŒํ–ˆ์Šต๋‹ˆ๋‹ค! ML ๊ธฐ์ดˆ ์„น์…˜์—์„œ ๋จธ์‹ ๋Ÿฌ๋‹ ๊ธฐ๋ฒ•์„ ๋ฐฐ์›Œ๋ณด์„ธ์š”.

Last updated on

๐Ÿค–AI Mock InterviewPractice with real questions