주제

1. A/B 테스트 시스템 구성
2. 트래픽을 A/B로 나누는 방법
3. A/B 테스트 결과 분석
4. A/B 테스트 관련 통계

 

1. A/B 테스트 시스템 구성

  • 런타임 시스템 + 분석 시스템
  • 구현 방법 :
    • 직접 구현
    • SaaS 사용
      • 서비스 : Optimizely, VWo, etc...
      • 대체적으로 Front End 관련 테스트를 하는데 유용함

 

2. 트래픽을 A/B로 나누는 방법

 

2.1 userid vs. deviceid

  • A/B 테스트의 성격에 따라 userid를 사용할지 deviceid를 사용할지 결정
    • 로그인한 사용자에게만 하는 테스트인가? 신규 vs. 기존 vs. 모두
    • 모든 방문자에게만 하는 테스트인가?
  • userid :
    • 보통 서비스에 사용자 등록이 되는 순간 부여되는 유일한 ID
  • deviceid :
    • 로그인과 관련없이 서비스 방문자에게 부여되는 ID로, 보통 브라우저 쿠키를 이용해서 만들어짐(리셋되는 순간 다시 만들어짐)
    • 사용자가 아닌 브라우저를 유일하게 지칭함
    • 한 userid가 여러 개의 deviceid를 가질 수 있고, 한 deviceid에 다수의 userid가 나타날 수 있음
    • 단순 크롤링 / 스크래핑을 하는 봇의 경우 쿠키 지원을 안하기 때문에 이정보가 없음

 

2.2 트래픽 나누는 방법

  1. 미리 모든 사용자를 A/B로 나누기
    • 로그인한 사용자를 대상으로 하는 경우 가능
    • 다양한 각도에서 bias 제거 가능
    • 비로그인 사용자를 대상으로 하는 테스트라면 사용 불가능한 방법
    • 테스트 중에 신규 등록된 사용자에게도 적용 불가능
  2. 사용자를 동적으로 A/B 테스트 진행 중에 나누기
    • 일반적으로 사용되는 방법
    • 로그인한 사용자이건 아니건 적용이 가능
    • 하지만 bias가 생길 가능성이 있음(특히 상호작용)
  • 파이썬으로 트래픽 나눠보기
    • UserID / DeviceID를 랜덤한 값으로 변경
    • MD5로 바뀐 값을 숫자로 변경
    • 위에서 나온 값에 Variant의 수로 나머지 연산을 수행
    • 나머지가 0이면 A, 1이면 B
import hashlib

def split_userid(id, num_of_variants=2):
     """Given an id and the number of variants, returns a bucket number"""
     h = hashlib.md5(str(id).encode())
     return int(h.hexdigest(), 16) % num_of_variants

 

  • sql로 트래픽 나눠보기
    • MD5 → LEFT → STRTOL → MOD
    • LEFT는 오버플로우 방지를 위해 사용
    • STRTOL는 16진수 문자열을 숫자로 변경하기 위해 사용
    • MOD는 나머지 계산으로 값을 0과 1로 바꾸기 위해 사용
sql = """
SELECT 100 user_id, MOD(STRTOL(LEFT(MD5(100),15), 16), 2) variant_id
UNION
SELECT 101 user_id,MOD(STRTOL(LEFT(MD5(101),15), 16), 2) variant_id
"""
df = sqlio.read_sql_query(sql, conn)

 

3. A/B 테스트 결과 분석

  • A/B 테스트 결과 분석은 경험이 중요!
  • 가설을 잘 세워야 배우는 것이 있다
  • 테스트는 기본적으로 최소 일주일은 진행해야 함
    • 많은 테스트들이 처음에는 통계적 유의미한 차이를 보이다 없어짐 → Data peeking problem
    • 최소 샘플의 크기와도 연관됨
    • 사용자들이 익숙한 UI/UX의 변경은 최소 2-4주를 실행해야함
  • 결과 분석은 객관적이고 공개되어야 함
    • 테스트 실험을 제안한 사람이 분석하는 것은 좋지 않음
    • 다양한 사람들이 모인 자리에서 결과를 분석하고 여러 의견을 듣는 것이 좋음
    • 이를 통해 경험 많은 사람들의 분석 방법을 배울 수 있음!

3.1 Outlier가 A/B 테스트에 미치는 영향

  • 어느 서비스나 큰 손들이 존재함
    • 이런 고객들은 어느 버킷에 들어가느냐가 분석에 큰 영향을 끼침
    • 이런 사용자들을 제외시키는 것도 하나의 방법
    • 큰 손 고객들의 retention / churn rate를 살펴볼 필요가 있음(A/B 테스트가 아니더라도)
  • 봇 유저(scraper bot)가 한쪽으로 몰리는 경우
    • session / impression 등에 큰 영향을 줄 수 있음

3.2 잘못된 가설

  • Survivorship Bias
    • 가끔 가설이 덜 중요한 문제를 해결하려는 경우가 있음. 가설에 대해 많은 질문을 하는 것이 중요함
  • 정말 중요한 지표를 보고 있는가?
    • 성공/실패의 기준으로 더 중요한 지표가 있는지 항상 고민이 필요함(매출액이나 구매율이 대표적)
  • 안 좋은 가설의 예 :
    • 굉장히 임팩트가 적은 기능 구현에 사용하는 경우(가설이 중요한 문제를 다루지 않음)
    • B2C 환경에서 다른 가격대를 테스트하는 경우(컨트롤이 쉽지 않음)
    • 검색엔진 UI와 검색엔진 알고리즘을 동시에 테스트하는 경우

 

4. A/B 테스트 관련 통계

  • 귀무가설(Null Hypothesis) : A와 B의 구매율은 동일하다
  • 중심극한정리(CLT)를 사용하면 데이터 분포를 정규분포로 변환 가능
  • B-A를 계산하여 동일한지 아닌지 여부를 판단
    • t-test를 사용하여 P value 혹은 z score를 계산하여 판단
  • 귀무가설 기반의 A/B 테스트 분석은 비교 대사이 되는 데이터가 정규분포를 따른다고 가정한다

4.1 정규분포 (Normal Distribution, Bell Curve)

  • 평균과 표준편차로 분포의 생김새를 정한다

4.2 표준정규분포(Standard Normal Distribution)

  • 평균이 1, 표준편차가 0인 분포, Z 분포라고도 함
  • 신뢰구간과 z-score
    • 90% → 1.645
    • 95% → 1.96
    • 99% → 2.575
  • 양측검정 vs. 단측검정
    • Two-sided vs. One-sided

표준정규분포

 

4.3 중심극한정리(Central Limit Theorm)

  • 모든 데이터가 정규분포를 따르지 않음 → 중심극한 정리를 사용
  • 샘플의 크기가 클수록, 그리고 샘플을 뽑는 수가 클수록 더 정규분포에 가까워짐
  • 모집단에서 샘플을 반복해서 수집 (샘플 크기는 30 이상)
  • 각 샘플의 평균을 계산
  • 평균들의 평균이 모집단 평균과 유사해짐
  • 샘플 평균의 히스토그램은 정규분포를 따르게 됨
  • 수집된 샘플 평균들간의 차이나 합도 정규분포를 따름

4.3.1 실습

  • 왜도된 분포를 가진 모집단에서 샘플링해보기
  • 모집단 분포 확인

왜도된 분포를 가진 모집단

  • 샘플 사이즈가 10일 때 :

정규분포에 가까워진 모습

  • 샘플 사이즈가 1000일 때 :

샘플 사이즈가 커지니 정규분포에 더욱 가까워짐

 

4.4 트래픽 크기 비교 가설

  • 트래픽이 원하는 형태로 잘 나누어졌는지 점검 : 테스트의 성공 여부 지표를 비교하기 전 제일 먼저 해야하는 일
  • A/B 테스트 사용자 크기를 통계적으로 비교
  • 50:50으로 나눈 테스트라면 P(A) = P(B) 혹은 P(B) = 0.5가 귀무가설이 됨
  • 비교 방법 : 
    • proportion z-test (or one-sample t-test)로 유의수준을 계산

4.5 비율 비교 : Proportion z-test

  • 하나의 모집단에서 N개의 샘플을 통해서 나온 특정 이벤트의 확률의 평균이 P인 경우
  • 결과는 z-score가 된다

proportion z-test의 공식

4.5.1 Bucket 크기 비교에 z-test 적용해보기

  • 예를 들어 P가 테스트 사용자의 비율이고 N이 테스트에 속한 전체 사용자의 수라고 할 때,
  • z-score = (P - 0.5) / √p(1 - p) / N
  • (95% 신뢰도일 때) z-score가 1.96보다 크거나 -1.96보다 작으면 P는 50%가 아니라고 할 수 있다
    • 귀무가설 reject
  • 파이썬을 이용한 Proportion z-test 적용
    • z-score가 -0.218로 귀무가설을 채택.
    • 즉 B 사용자 비율이 50%라고 할 수 있다
from statsmodels.stats.proportion import proportions_ztest

n_test = 2056
n_ctrl = 2070
stats, pvalue = proportions_ztest(n_test, n_ctrl+n_test, value=0.5, alternative='two-sided')
print(stats, pvalue)

결과

 

4.6 t-test

  • 두 집단 간의 평균의 차이가 유의미한지 검증할 수 있는 방법
  • 버킷 크기 비교는 One-sample t-test
  • Impression / click / purchase / amount는 two-sample t-test

 

4.7 A/A 테스트를 통한 검증 방식

  • 기본적으로 A/B 테스트 분석과 동일함
  • 차이점 : 
    • 기존 서비스 방문 트래픽을 랜덤하게 추출(보통 날짜 기간 기반)
    • 앞서 구현한 Bucketing 로직을 적용해서 트래픽을 A와 A'로 분리
    • 기타 비교 지표들을 계산하고 그 값들이 동일함을 컨펌
  • A와 A'의 차이가 통계적으로 무의미해야함
  • 파이썬으로 A와 B에 속한 사용자 수와 세션 수 카운트하기
import hashlib

def split_userid(id, num_of_variants=2):
   h = hashlib.md5(str(id).encode())
   return int(h.hexdigest(), 16) % num_of_variants

 

a_user_count = 0
b_user_count = 0

for index, row in df.iterrows():
    if split_userid(row["user_id"]) == 0:
        a_user_count += 1
    else:
        b_user_count += 1

print(a_user_count, b_user_count)

결과

 

 


느낀점

A/B 테스트를 배우면서 통계적으로 검증을 해야하는 부분이 조금 어려웠다. Proportion z-test나 t-test같은 방법들은 처음 접했는데 물론 실제로 사용할 땐 파이썬 라이브러리가 있지만 분석을 하려면 로직을 이해해야 하니까 조금 더 개인적으로 공부할 필요성을 느꼈다. 

+ Recent posts