주제
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 트래픽 나누는 방법
- 미리 모든 사용자를 A/B로 나누기
- 로그인한 사용자를 대상으로 하는 경우 가능
- 다양한 각도에서 bias 제거 가능
- 비로그인 사용자를 대상으로 하는 테스트라면 사용 불가능한 방법
- 테스트 중에 신규 등록된 사용자에게도 적용 불가능
- 사용자를 동적으로 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가 된다

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같은 방법들은 처음 접했는데 물론 실제로 사용할 땐 파이썬 라이브러리가 있지만 분석을 하려면 로직을 이해해야 하니까 조금 더 개인적으로 공부할 필요성을 느꼈다.
'Data Science > TIL (Today I Learned)' 카테고리의 다른 글
| 프로그래머스 데이터분석 데브코스 1기 - 69일차 (0) | 2024.02.29 |
|---|---|
| 프로그래머스 데이터분석 데브코스 1기 - 68일차 (1) | 2024.02.28 |
| 프로그래머스 데이터분석 데브코스 1기 - 66일차 (0) | 2024.02.26 |
| 프로그래머스 데이터분석 데브코스 1기 - 65일차 (0) | 2024.02.23 |
| 프로그래머스 데이터분석 데브코스 1기 - 64일차 (0) | 2024.02.22 |