Skip to Content

02. A/B ํ…Œ์ŠคํŒ… (Experiments)

๊ณ ๊ธ‰2์‹œ๊ฐ„

1. ๊ฐœ์š” ๋ฐ ์‹œ๋‚˜๋ฆฌ์˜ค

์ƒํ™ฉ: ๋งˆ์ผ€ํŒ… ํŒ€์ด ์ƒˆ๋กœ์šด ๋žœ๋”ฉ ํŽ˜์ด์ง€(B์•ˆ)๋ฅผ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค. โ€œ๊ธฐ์กด ํŽ˜์ด์ง€(A์•ˆ)๋ณด๋‹ค ์ „ํ™˜์œจ์ด 2% ์˜ฌ๋ž์–ด์š”!โ€๋ผ๊ณ  ๊ธฐ๋ปํ•ฉ๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ ๋‹น์‹ ์€ ์นจ์ฐฉํ•˜๊ฒŒ ๋ฌป์Šต๋‹ˆ๋‹ค.

โ€œํ‘œ๋ณธ ์ˆ˜๋Š” ๋ช‡ ๋ช…์ด์—ˆ๋‚˜์š”? ๊ทธ 2% ์ƒ์Šน์ด ์šฐ์—ฐ์ผ ํ™•๋ฅ (P-value)์€ ์–ผ๋งˆ์ธ๊ฐ€์š”?โ€

A/B ํ…Œ์ŠคํŠธ๋Š” ๋น„์ฆˆ๋‹ˆ์Šค ์˜์‚ฌ๊ฒฐ์ •์˜ ๊ฝƒ์ž…๋‹ˆ๋‹ค. ์ด๋ฒˆ ์ฑ•ํ„ฐ์—์„œ๋Š” **๋น„์œจ ๊ฒ€์ •(Proportions Z-test)**๊ณผ **ํ‘œ๋ณธ ํฌ๊ธฐ ๊ณ„์‚ฐ(Power Analysis)**์„ ๋ฐฐ์›๋‹ˆ๋‹ค.


2. ๋ฐ์ดํ„ฐ ์ค€๋น„

A/B ํ…Œ์ŠคํŠธ ๋กœ๊ทธ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์œผ๋ฏ€๋กœ, ๊ธฐ์กด ๋ฐ์ดํ„ฐ์—์„œ ์„ฑ๋ณ„์„ A/B ๊ทธ๋ฃน์ด๋ผ๊ณ  ๊ฐ€์ •ํ•˜๊ณ  ์‹œ๋ฎฌ๋ ˆ์ด์…˜ํ•ด๋ด…๋‹ˆ๋‹ค.

  • Group A: ๋‚จ์„ฑ (Male)
  • Group B: ์—ฌ์„ฑ (Female)
  • Conversion: ๊ตฌ๋งค ์—ฌ๋ถ€ (์ฃผ๋ฌธ ์ด๋ ฅ์ด ์žˆ์œผ๋ฉด 1, ์—†์œผ๋ฉด 0)
from statsmodels.stats.proportion import proportions_ztest import numpy as np # ... BigQuery client setup

3. ๋น„์œจ ๊ฒ€์ • (Proportions Z-test)

์ „ํ™˜์œจ(Conversion Rate)๊ณผ ๊ฐ™์€ ๋น„์œจ์„ ๋น„๊ตํ•  ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

โ“ ๋ฌธ์ œ 1: ๊ทธ๋ฃน ๊ฐ„ ์ „ํ™˜์œจ ๋น„๊ต

Q. ๋‚จ์„ฑ๊ณผ ์—ฌ์„ฑ์˜ ๊ตฌ๋งค ์ „ํ™˜์œจ(์ „์ฒด ๊ฐ€์ž…์ž ์ค‘ ๊ตฌ๋งค์ž ๋น„์œจ)์„ ๊ตฌํ•˜๊ณ , ๋‘ ๋น„์œจ์˜ ์ฐจ์ด๊ฐ€ ์œ ์˜๋ฏธํ•œ์ง€ ๊ฒ€์ •ํ•˜์„ธ์š”.

๐Ÿ’ก

Hint: COUNT(DISTINCT user_id)๋กœ ์ „์ฒด ๋ชจ์ˆ˜๋ฅผ ๊ตฌํ•˜๊ณ , LEFT JOIN orders๋กœ ๊ตฌ๋งค์ž๋ฅผ ์…‰๋‹ˆ๋‹ค.

์ •๋‹ต ์ฝ”๋“œ ๋ณด๊ธฐ

# 1. ๋ฐ์ดํ„ฐ ์ง‘๊ณ„ query = """ SELECT u.gender, COUNT(DISTINCT u.user_id) as total_users, COUNT(DISTINCT o.user_id) as purchasers FROM `your-project-id.retail_analytics_us.src_users` u LEFT JOIN `your-project-id.retail_analytics_us.src_orders` o ON u.user_id = o.user_id GROUP BY u.gender """ df = client.query(query).to_dataframe().set_index('gender') # 2. ํ†ต๊ณ„๋Ÿ‰ ์ถ”์ถœ (์„ฑ๊ณต ํšŸ์ˆ˜, ์‹œํ–‰ ํšŸ์ˆ˜) count = df['purchasers'].values # [๋‚จ์„ฑ๊ตฌ๋งค์ž์ˆ˜, ์—ฌ์„ฑ๊ตฌ๋งค์ž์ˆ˜] nobs = df['total_users'].values # [๋‚จ์„ฑ์ „์ฒด, ์—ฌ์„ฑ์ „์ฒด] # 3. Z-test z_stat, p_val = proportions_ztest(count, nobs) print(f"๋‚จ์„ฑ ์ „ํ™˜์œจ: {count[0]/nobs[0]:.4f}") print(f"์—ฌ์„ฑ ์ „ํ™˜์œจ: {count[1]/nobs[1]:.4f}") print(f"P-value: {p_val:.4f}") if p_val < 0.05: print("โœ… ์ „ํ™˜์œจ ์ฐจ์ด๊ฐ€ ์œ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.")
์‹คํ–‰ ๊ฒฐ๊ณผ
Error: name 'client' is not defined

4. ํ‘œ๋ณธ ํฌ๊ธฐ ๊ณ„์‚ฐ (Sample Size & Power)

ํ…Œ์ŠคํŠธ๋ฅผ ํ•˜๊ธฐ ์ „์— ๊ฐ€์žฅ ๋จผ์ € ํ•ด์•ผ ํ•  ์งˆ๋ฌธ์ž…๋‹ˆ๋‹ค.

โ€œ๋ช‡ ๋ช…ํ•œํ…Œ ์‹คํ—˜ํ•ด์•ผ ๋ฏฟ์„ ๋งŒํ•œ ๊ฒฐ๊ณผ๊ฐ€ ๋‚˜์˜ค๋‚˜์š”?โ€

๋„ˆ๋ฌด ์ ์œผ๋ฉด ํšจ๊ณผ๊ฐ€ ์žˆ์–ด๋„ ๋ชป ์ฐพ๊ณ (False Negative), ๋„ˆ๋ฌด ๋งŽ์œผ๋ฉด ๋ˆ ๋‚ญ๋น„์ž…๋‹ˆ๋‹ค.

โ“ ๋ฌธ์ œ 2: ํ•„์š”ํ•œ ํ‘œ๋ณธ ์ˆ˜ ๊ณ„์‚ฐ

Q. ํ˜„์žฌ ์ „ํ™˜์œจ์ด 10%๋ผ๊ณ  ํ•  ๋•Œ, ์ด๋ฅผ 11%๋กœ ๊ฐœ์„ (1%p ์ƒ์Šน)ํ•˜๋Š” ๊ฒƒ์„ ๊ฐ์ง€ํ•˜๋ ค๋ฉด ๊ทธ๋ฃน๋‹น ๋ช‡ ๋ช…์ด ํ•„์š”ํ•œ๊ฐ€์š”? (์œ ์˜์ˆ˜์ค€ ฮฑ=0.05\alpha=0.05, ๊ฒ€์ •๋ ฅ Power=0.8 ๊ธฐ์ค€)

๐Ÿ’ก

Hint: statsmodels.stats.power.NormalIndPower๋ฅผ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ proportion_effectsize๋ฅผ ๊ตฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์ •๋‹ต ์ฝ”๋“œ ๋ณด๊ธฐ

import statsmodels.stats.api as sms from statsmodels.stats.proportion import proportion_effectsize # 1. ํšจ๊ณผ ํฌ๊ธฐ(Effect Size) ๊ณ„์‚ฐ p1 = 0.10 # ๊ธฐ์กด p2 = 0.11 # ๋ชฉํ‘œ effect_size = proportion_effectsize(p1, p2) # 2. ํ‘œ๋ณธ ์ˆ˜ ๊ณ„์‚ฐ required_n = sms.NormalIndPower().solve_power( effect_size=effect_size, power=0.8, alpha=0.05, ratio=1 ) print(f"ํ•„์š”ํ•œ ๊ทธ๋ฃน๋‹น ํ‘œ๋ณธ ํฌ๊ธฐ: {int(np.ceil(required_n))}๋ช…") print(f"์ด ํ•„์š” ํ‘œ๋ณธ ํฌ๊ธฐ: {int(np.ceil(required_n)) * 2}๋ช…")
์‹คํ–‰ ๊ฒฐ๊ณผ
ํ•„์š”ํ•œ ๊ทธ๋ฃน๋‹น ํ‘œ๋ณธ ํฌ๊ธฐ: 14745๋ช…
์ด ํ•„์š” ํ‘œ๋ณธ ํฌ๊ธฐ: 29490๋ช…

๐Ÿ’ก ํŒŒ๋ผ๋ฏธํ„ฐ ์„ค๋ช…

  • Alpha (ฮฑ\alpha): 1์ข… ์˜ค๋ฅ˜ ํ™•๋ฅ  (๋ณดํ†ต 0.05). โ€œํšจ๊ณผ ์—†๋Š”๋ฐ ์žˆ๋‹ค๊ณ  ํ•  ํ™•๋ฅ โ€
  • Power (1โˆ’ฮฒ1-\beta): ๊ฒ€์ •๋ ฅ (๋ณดํ†ต 0.8). โ€œํšจ๊ณผ๊ฐ€ ์žˆ์„ ๋•Œ ์ง„์งœ ์ฐพ์„ ํ™•๋ฅ โ€
  • Effect Size: ๊ฐ์ง€ํ•˜๊ณ ์ž ํ•˜๋Š” ์ฐจ์ด์˜ ํฌ๊ธฐ (ํด์ˆ˜๋ก ์ ์€ ํ‘œ๋ณธ์œผ๋กœ๋„ ์ฐพ์Œ)

5. ์‹คํ—˜ ์„ค๊ณ„ ์‹œ ์ฃผ์˜์‚ฌํ•ญ (Common Pitfalls)

์ฝ”๋”ฉ๋งŒํผ ์ค‘์š”ํ•œ ๊ฒƒ์ด ์„ค๊ณ„์ž…๋‹ˆ๋‹ค.

  1. Peeking Problem (์—ฟ๋ณด๊ธฐ):

    • ์‹คํ—˜ ๋„์ค‘์— โ€œ์–ด? P-value 0.04๋„ค์š”, ๋ฉˆ์ถฅ์‹œ๋‹ค!โ€๋ผ๊ณ  ํ•˜๋ฉด ์•ˆ ๋ฉ๋‹ˆ๋‹ค.
    • ์‚ฌ์ „์— ์ •ํ•œ ํ‘œ๋ณธ ์ˆ˜(N)๋ฅผ ์ฑ„์šธ ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ ค์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  2. SRM (Sample Ratio Mismatch):

    • 50:50์œผ๋กœ ๋‚˜๋ˆด๋Š”๋ฐ, ๊ฒฐ๊ณผ๊ฐ€ 1000๋ช… vs 950๋ช…์ด๋‹ค?
    • ํŠธ๋ž˜ํ”ฝ ํ• ๋‹น ์‹œ์Šคํ…œ์— ๋ฒ„๊ทธ๊ฐ€ ์žˆ๊ฑฐ๋‚˜, ํŠน์ • ๊ทธ๋ฃน์—์„œ ๋ฐ์ดํ„ฐ ๋ˆ„๋ฝ์ด ๋ฐœ์ƒํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค.
    • ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ ๋ฌดํšจ!

๐Ÿ’ก ์š”์•ฝ

  • ๋น„์œจ ๊ฒ€์ •: ํด๋ฆญ๋ฅ , ์ „ํ™˜์œจ ๋“ฑ 0/1 ๋ฐ์ดํ„ฐ ๋น„๊ต
  • Power Analysis: ์‹คํ—˜ ์‹œ์ž‘ ์ „ ํ•„์ˆ˜ ๋‹จ๊ณ„ (โ€œ๋ช‡ ๋ช… ํ•„์š”ํ•ด?โ€)
  • A/B ํ…Œ์ŠคํŠธ๋Š” ๊ณผํ•™์ž…๋‹ˆ๋‹ค: ๊ฐ(Feeling)์ด ์•„๋‹ˆ๋ผ ๋ฐ์ดํ„ฐ๋กœ ์˜์‚ฌ๊ฒฐ์ •ํ•˜์„ธ์š”.

๋‹ค์Œ ์ฑ•ํ„ฐ์—์„œ๋Š” ์ƒ๊ด€๊ด€๊ณ„์™€ ํšŒ๊ท€๋ถ„์„์„ ํ†ตํ•ด ๋ณ€์ˆ˜ ๊ฐ„์˜ ์ˆจ์€ ๊ด€๊ณ„๋ฅผ ์ฐพ์•„๋ด…๋‹ˆ๋‹ค.

Last updated on

๐Ÿค–AI Mock InterviewPractice with real questions