🤖 AI/ML

A/B 테스트

A/B Testing

두 버전을 비교하는 실험 설계. 통계적 유의성 검증.

📖 상세 설명

A/B 테스트는 두 개의 버전(A: Control, B: Variant)을 동시에 실험하여 어떤 것이 더 나은 성과를 내는지 통계적으로 비교하는 방법론입니다. 사용자를 무작위로 두 그룹으로 나누어 각각 다른 버전을 보여주고, 전환율, 클릭률, 매출 등 핵심 지표의 차이를 측정합니다.

A/B 테스트의 기원은 1920년대 R.A. Fisher의 농업 통계 실험으로 거슬러 올라갑니다. 디지털 분야에서는 2000년 Google이 검색 결과 페이지에서 10개 vs 30개 결과 표시를 테스트하면서 본격화되었습니다. 현재 Amazon은 매일 수백 개의 A/B 테스트를 동시 운영하고, Booking.com은 연간 25,000개 이상의 실험을 수행합니다.

A/B 테스트의 핵심은 귀무가설(H0: 두 버전 간 차이 없음)을 검정하는 것입니다. t-test 또는 카이제곱 검정으로 p-value를 계산하고, 일반적으로 p < 0.05일 때 "통계적으로 유의미하다"고 판단합니다. 검정력(Power)은 보통 80% 이상을 목표로 하며, 이를 달성하기 위한 최소 샘플 사이즈를 사전에 계산해야 합니다.

실무에서 A/B 테스트는 직관과 의견 대신 데이터로 의사결정하는 문화의 기반입니다. "HiPPO(Highest Paid Person's Opinion)" 대신 실험 결과로 결정하면 실패 비용을 줄이고, 작은 개선이라도 누적되면 큰 비즈니스 임팩트를 만들 수 있습니다. Microsoft의 한 실험에서 검색 결과 지연을 100ms 늘렸더니 수익이 1% 감소한 사례는 A/B 테스트의 민감도를 보여줍니다.

💻 코드 예제

import numpy as np
from scipy import stats
import math

def calculate_sample_size(baseline_rate, mde, alpha=0.05, power=0.8):
    """
    A/B 테스트에 필요한 최소 샘플 사이즈 계산

    Args:
        baseline_rate: 기존 전환율 (예: 0.10 = 10%)
        mde: 최소 탐지 효과 크기 (예: 0.02 = 2%p 상승)
        alpha: 유의수준 (기본 0.05)
        power: 검정력 (기본 0.80)
    """
    p1 = baseline_rate
    p2 = baseline_rate + mde
    pooled_p = (p1 + p2) / 2

    z_alpha = stats.norm.ppf(1 - alpha / 2)  # 양측검정
    z_beta = stats.norm.ppf(power)

    numerator = (z_alpha * math.sqrt(2 * pooled_p * (1 - pooled_p)) +
                 z_beta * math.sqrt(p1 * (1 - p1) + p2 * (1 - p2))) ** 2
    denominator = (p2 - p1) ** 2

    n_per_group = math.ceil(numerator / denominator)
    return n_per_group

def ab_test_analysis(control_conversions, control_total,
                     variant_conversions, variant_total):
    """A/B 테스트 결과 분석"""
    # 전환율 계산
    control_rate = control_conversions / control_total
    variant_rate = variant_conversions / variant_total

    # 상대적 상승률
    lift = (variant_rate - control_rate) / control_rate * 100

    # Z-검정 (2-proportion z-test)
    pooled_rate = (control_conversions + variant_conversions) / (control_total + variant_total)
    se = math.sqrt(pooled_rate * (1 - pooled_rate) * (1/control_total + 1/variant_total))
    z_score = (variant_rate - control_rate) / se
    p_value = 2 * (1 - stats.norm.cdf(abs(z_score)))  # 양측검정

    # 신뢰구간 (95%)
    se_diff = math.sqrt(control_rate*(1-control_rate)/control_total +
                        variant_rate*(1-variant_rate)/variant_total)
    ci_lower = (variant_rate - control_rate) - 1.96 * se_diff
    ci_upper = (variant_rate - control_rate) + 1.96 * se_diff

    return {
        "control_rate": f"{control_rate:.2%}",
        "variant_rate": f"{variant_rate:.2%}",
        "lift": f"{lift:+.1f}%",
        "p_value": p_value,
        "significant": p_value < 0.05,
        "confidence_interval": f"[{ci_lower:+.2%}, {ci_upper:+.2%}]"
    }

# 사용 예시: 체크아웃 버튼 색상 테스트
sample_size = calculate_sample_size(baseline_rate=0.10, mde=0.01)
print(f"필요 샘플 사이즈 (그룹당): {sample_size:,}명")

# 실제 실험 결과 분석
results = ab_test_analysis(
    control_conversions=520, control_total=5000,  # 10.4%
    variant_conversions=580, variant_total=5000   # 11.6%
)

print(f"\nControl 전환율: {results['control_rate']}")
print(f"Variant 전환율: {results['variant_rate']}")
print(f"상승률: {results['lift']}")
print(f"p-value: {results['p_value']:.4f}")
print(f"통계적 유의미: {'예' if results['significant'] else '아니오'}")
print(f"95% 신뢰구간: {results['confidence_interval']}")

🗣️ 실무 대화 예시

마케팅 팀 주간 회의에서

"랜딩페이지 A/B 테스트 결과가 나왔습니다. 새로운 헤드라인으로 전환율이 8.2%에서 9.7%로 올랐고, p-value가 0.023이라 통계적으로 유의미합니다. 다음 주부터 승자 버전으로 100% 트래픽을 돌려도 될까요?"

그로스 해커 면접에서

"A/B 테스트에서 Type I Error는 실제로 차이가 없는데 있다고 판단하는 거짓 양성이고, Type II Error는 차이가 있는데 없다고 판단하는 거짓 음성입니다. 보통 Type I Error를 5%로 제한하고, Type II Error를 20% 이하로 유지해서 검정력 80%를 확보합니다."

제품 팀 리뷰에서

"지금 결과를 보면 Variant가 2% 더 좋아 보이지만, 아직 샘플 사이즈가 부족해서 신뢰구간이 0을 포함하고 있습니다. 최소 일주일 더 실험을 유지해서 MDE 1%를 탐지할 수 있는 충분한 데이터를 모아야 합니다."

⚠️ 주의사항

⚠️
Multiple Testing Problem (다중 검정 문제)

여러 지표를 동시에 테스트하면 거짓 양성 확률이 증가합니다. 5개 지표를 테스트하면 거짓 양성 확률이 23%까지 올라갑니다. 주요 지표를 하나 정하거나 Bonferroni 보정을 적용하세요.

⚠️
Sample Ratio Mismatch (SRM)

50:50으로 분배했는데 실제 비율이 48:52처럼 틀어지면 실험 인프라에 버그가 있다는 신호입니다. 결과를 분석하기 전에 반드시 SRM 체크를 해야 하며, 문제가 있으면 실험을 폐기해야 합니다.

⚠️
외부 요인 통제 실패

휴일, 마케팅 캠페인, 시스템 장애 등 외부 요인이 결과를 왜곡할 수 있습니다. 실험 기간 중 주요 이벤트를 기록하고, 필요시 해당 기간 데이터를 제외하거나 세그먼트 분석을 수행하세요.

🔗 관련 용어

📚 더 배우기