데이터로그😎
[데이터리안_플젝2] A/B test 본문
🚨 *데이터리안의 무료 강의를 참고했습니다. Mode에서 제공하는 소스코드는 제가 사용하는 MySQL에는 맞지않아, MySQL에 맞도록 코드를 직접 짰습니다. 따라서 문제점이 발견될 수 있습니다. 수정 사항을 발견하시면 언제든 댓글 주세요. 분석에 사용한 자료들의 출처와 상세 코드는 맨아래출처 부분에서 확인할 수 있습니다.
A/B test란 무엇일까?
- 정의
- 웹 사이트 방문자를 임의로 두 집단으로 나누고, 한 집단에는 기존 사이트를 보여주고 다른 집단에게는 새로운 사이트를 보여준 다음, 두 집단 중 어떤 집단이 더 높은 *성과를 보이는지를 측정하여 새 사이트가 기존 사이트에 비해 좋은지를 정량적으로 평가하는 방식을 의미한다.
- *성과: 회원 가입율, 재방문율, 구매전환율 등이 될 수 있다. (목표에 따라 다르게 설정된다.)
- 목표
- 상관관계로부터 인과관계를 찾아내기 위함이다. 즉, 원인과 결과를 찾아내기 위함인데, 이를 찾아내야만 원인에 해당하는 요소에 개입하여 결과에 해당하는 요소가 우리가 원하는 방향으로 변화할 수 있기 때문이다.
- 혹은 이미 결과에 변화가 생겼을 때, 이 변화의 원인이 우리가 했던 개입 때문이 맞는지를 판단할 수 있기 때문이다.
- 예시
- 어떤 쇼핑몰 웹 사이트가 있다. 3개월에 걸쳐 디자인 개편 프로젝트를 진행했고 지난주에 성공적으로 새 디자인을 적용했다. 그랬더니 새 디자인을 적용하기 전에 비해 일 매출이 10% 증가했다. 매출 증가가 새 디자인 도입 덕분이라고 볼 수 있을까?
- 새 디자인 적용과 같은 내부 요인이 직접적인 원인일 수 있다. 그러나 외부 요인이 작용했을 수도 있다. 하필 새로운 디자인이 적용된 날에 경쟁 쇼핑몰이 문을 닫았다거나, 하필 그 날 쇼핑몰에 새로운 경쟁력있는 상품이 있고 되었다거나 하는 요인과 같은 외부 요인이 있을 수 있다. 만약 새로운 경쟁력있는 상품이 입고 되어 매출이 10% 증가했다면, 매출 증가는 디자인 팀 덕분이 아니라 영업팀 덕이 된다.
- A/B 테스팅을 하게 되면 방문자를 임의로 A,B 두 집단으로 나누고 A 집단에게는 기존 디자인을, B집단에게는 새로운 디자인을 보여주며 테스트를 진행한다. 새로운 디자인을 적용하기 전후의 매출을 비교하는 것이 아니라, 동시에 A, B 두집단에게 테스팅이 진행하여 두 집단의 매출을 비교하면 시간의 흐름에 따라 발생하는 변화를 (경쟁 쇼핑몰 부도, 신상품 입고 등)을 통제할 수 있기 때문에 순수한 디자인 변화로 인한 매출 차이를 가려낼 수 있다. 만약 A 집단에 비해 B 집단에서 매출 증가가 있었다면 "디자인 개편과 매출 증가 사이에는 인과관계가 성립할 가능성이 크다" 라고 말할 수 있다.
- 출처: https://boxnwhis.kr/2015/01/29/a_b_testing.html
개요
- 상황
- Yammer의 publisher 기능 개선을 위해 A/B test를 진행했다.
- 테스트 기간: 2014.06월 한 달간
- A/B 그룹
- A 그룹: shown the old version of publisher (control group, 대조군)
- B 그룹: shown the new version of publisher (test group, 실험군)
- 데이터 정보 (테이블)
테이블 명 설명 컬럼 정보 users user에 관한
모든 정보- user_id : 유저 고유의 아이디
- created_at : 유저 계정 생성일
- company_id : 유저가 속한 회사 ID
- language : 유저 사용 언어
- activated_at : 유저 계정의 활성일, state가 active로 변경된 일자
- state : 계정 상태 (active / pending)events user가 발생시킨
모든 event에 관한
정보 (log)- user_id: 유저 고유의 아이디
- occurred_at : event 발생일자
- event_type : engagement / signup_flow (가입)
- event_name: login / home_page / like_message / send_message 등
- location : 사용 위치
- device : 접속 기기
- user_type : 유저 타입experiments 실험 관련 정보
(A/B test 뿐만 아니라 기타 실험들에 대한 정보 포함)- user_id : 유저 고유의 아이디
- occurred_at : 실험 일자
- experiment : 실험 종류
- experiment_group : control group / test group
- location : 위치
- device : 기기
- user_type : 유저 타입
[분석1] 평균 메시지 발송건수 비교
A/B test 성과지표
- A/B test 결과의 성과지표: '유저 당 평균 메시지 발송건수' 로 지정.
- '유저 당 평균 메시지 발송건수' 를 구하기 위해 도출해야하는 데이터
- ① 각 그룹에 포함된 유저 수
- ② 각 그룹의 메시지 발송건수
- ③ 각 그룹의 유저 당 평균 메시지 발송건수 (③=②/① 의 식으로 구할 수 있다)
쿼리
- JOIN 절을 통해 experiments, users, events 테이블을 모두 합쳤다.
- ON 절에서 아래의 데이터만 추출했다.
- 실험 종류는 publilsher update이다.
- event는 실험 기간 동안에 발생한 것이어야 한다. (2014-06월 동안)
- event_type은 engagement여야 한다. (event_type은 sign up과 관련된 것이 아니어야 한다)
- experiment_group( control_group, test_group) 에 따라 GROUP BY 진행하여 집계를 한다.
- ① 각 그룹에 포함된 유저 수 (user_cnt) = COUNT(DISTINCT ex.user_id)
- ② 각 그룹의 메시지 발송건수 (metric) = event_name이 'send_message'인 user_id 수를 count한다.
- ③ 각 그룹의 유저 당 평균 메시지 발송건수 (average) = ②/①
query = " SELECT experiment_group, COUNT(DISTINCT ex.user_id) user_cnt, \ COUNT(CASE WHEN event_name = 'send_message' THEN ex.user_id ELSE NULL END) as metric,\ COUNT(CASE WHEN event_name = 'send_message' THEN ex.user_id ELSE NULL END) / COUNT(DISTINCT ex.user_id) as average \ FROM experiments ex \ JOIN users u ON ex.user_id = u.user_id \ JOIN events e on ex.user_id = e.user_id \ AND ex.experiment = 'publisher_update' \ AND e.occurred_at LIKE '%2014-06%' \ AND e.event_type = 'engagement' \ GROUP BY experiment_group" message_cnt_df = pd.read_sql(query, connection) message_cnt_df
최종 데이터 및 그래프
- 최종 데이터
- 그래프
import seaborn as sns import matplotlib.pyplot as plt plt.figure(figsize=(10,6)) sns.set_theme(palette='deep') ax = sns.barplot(data=message_cnt_df, x='experiment_group', y='average') plt.xlabel('Experiment Group',fontsize=14) plt.ylabel('Average Message Sent', fontsize=14) plt.xticks(fontsize=14) plt.yticks(fontsize=14) for p in ax.patches: ax.annotate(p.get_height(), (p.get_x()+p.get_width()/2, p.get_height()), fontsize=12,color='red', ha='center',va='center', textcoords='offset points') plt.show()
분석1 결론
- test group은 테스트 기간 동안 평균 4회의 메시지를 발송했고, control group은 평균 2.6회의 메시지를 발송했다.
- test group의 평균 메시지 발송 수가 control group 보다 50% 이상 많음
- test group과 control group 사이에 지표 차이가 나는 것은 좋지만 왜 이렇게까지 많이 차이가 날까? 1.5배 차이라니
- 아래의 원인들 때문에 평균 메시지 발송건수 지표가 이렇게나 많이 차이나는 것은 아닐까?
- 잘못된 성과 지표 선정: 분석1에서 사용했던 '유저당 평균 메시지 발송건수' 라는 지표가 publisher 개선에 따른 결과를 잘 반영하지 못할 수 있다.즉, 메시지 발송건수가 publisher 개선 프로젝트의 성공을 측정하는 지표가 아닐 수도 있다. 다른 지표도 함께 살펴보아야 한다.
- 샘플링의 오류: Control, Test group이 제대로 나뉘지 않았을 수도 있다. Randomly하게 실험군, 대조군을 나누어야 하는데 랜덤하지 못했을 수 있다. 예를 들면 [실험군:여자, 대조군:남자]로 나뉘거나 [실험군: 10,20대, 대조군:30대 이상] 으로 나뉘거나 했을 수 있다. 이때 X(원인)는 publisher version이고 Y(결과)는 Average Message Sent 수 여야하는데, Y에 영향을 미치는 것이 X이외에 다른 것이 있다면 실험이 잘못된 것이다.
- 앞으로의 분석은 아래와 같이 진행할 것이다.
- 새로운 성과 지표 선정
- 그룹의 샘플링이 제대로 되어있는지 확인
[분석2-1] 로그인 횟수 분석
새로운 A/B test 성과 지표
- '유저당 평균 메시지 발송건수' 는 Test group > Control group 의 결과가 나왔다. 목표하던 바대로 결과가 나왔지만 문제는 Test, Control group 간에 너무 큰 차이를 보인다는 것이다. 그래서 이 결과가 과연 믿을 수 있을만한 것인가? 라는 의심이 간다..!
- 그래서 '유저의 평균 로그인 횟수'와 '유저의 평균 로그인 일수' 라는 새로운 지표를 세우고 다시 분석을 하려 한다.
- '유저의 평균 로그인 횟수' 를 구하기 위해 도출해야하는 데이터
- ① 각 그룹에 포함된 유저 수
- ② 각 그룹의 메시지 로그인 횟수
- ③ 각 그룹의 유저 당 평균 로그인 횟수 (③=②/① 의 식으로 구할 수 있다)
- '유저의 평균 로그인 일수' 를 구하기 위해 도출해야하는 데이터
- ① 각 그룹에 포함된 유저 수
- ② 각 그룹의 메시지 로그인 일수
- ③ 각 그룹의 유저 당 평균 로그인 일수 (③=②/① 의 식으로 구할 수 있다)
- '유저의 평균 로그인 횟수' 를 구하기 위해 도출해야하는 데이터
쿼리
- JOIN 절을 통해 experiments, users, events 테이블을 모두 합쳤다.
- ON 절에서 아래의 데이터만 추출했다.
- 실험 종류는 publilsher update이다.
- event는 실험 기간 동안에 발생한 것이어야 한다. (2014-06월 동안)
- event_type은 engagement여야 한다. (event_type은 sign up과 관련된 것이 아니어야 한다)
- experiment_group( control_group, test_group) 에 따라 GROUP BY 진행하여 집계를 한다.
- ① 각 그룹에 포함된 유저 수 (user_cnt) = COUNT(DISTINCT ex.user_id)
- ② 각 그룹의 로그인 횟수(login_cnt) = event_name이 'login'인 user_id 수를 count한다.
- ③ 각 그룹의 유저 당 평균 로그인 횟수 (average) = ②/①
query = " SELECT experiment_group, COUNT(DISTINCT ex.user_id) user_cnt, COUNT(CASE WHEN event_name ='login' THEN ex.user_id ELSE NULL END) as login_cnt, \ COUNT(CASE WHEN event_name ='login' THEN ex.user_id ELSE NULL END)/ COUNT(DISTINCT ex.user_id) average \ FROM experiments ex \ JOIN users u ON ex.user_id = u.user_id \ JOIN events e on ex.user_id = e.user_id \ AND ex.experiment = 'publisher_update' \ AND e.occurred_at LIKE '%2014-06%' \ AND e.event_type = 'engagement' \ GROUP BY experiment_group" login_cnt_df = pd.read_sql(query, connection) login_cnt_df
최종 데이터 및 그래프
- 최종 데이터
- 그래프
ax = sns.barplot(data= login_cnt_df, x='experiment_group', y='average')
for p in ax.patches:
ax.annotate(p.get_height(), (p.get_x()+p.get_width()/2, p.get_height()),
fontsize=12,color='red', ha='center',va='center', textcoords='offset points')
분석2-1 결론
- 평균 로그인 수는 대조군이 평균 3.3번, 실험군이 평균 4.1번으로, 실험기간 중 평균 로그인 수도 실험군이 더 높다.
- 즉, test group의 유저들은 한 달 동안 평균 4.1회, control group의 유저들은 한 달 동안 평균 3.3회 로그인 했다.
- 그러나, 로그인 횟수가 4회라는 것은 4일동안 하루에 1회 로그인한 것일 수도 있고, 하루 동안 4회 로그인한 것일 수도 있다. 만약 후자라면 하루에 로그인-로그아웃을 계속 반복하고 있다는 소리이기 때문에 좋은 시그널일 수 없다.
- 따라서 로그인 일수도 확인해봐야 한다.
[분석2-2] 로그인 일수 분석
쿼리
- 서브쿼리
- JOIN 절을 통해 experiments, users, events 테이블을 모두 합쳤다.
- ON 절에서 아래의 데이터만 추출했다.
- 실험 종류는 publilsher update이다.
- event는 실험 기간 동안에 발생한 것이어야 한다. (2014-06월 동안)
- event_type은 engagement여야 한다. (event_type은 sign up과 관련된 것이 아니어야 한다)
- experiment_group( control_group, test_group) 에 따라 GROUP BY 진행하여 집계를 한다.
- 실험 그룹, user_id 별로 활동일 수를 계산한다.
- 외부쿼리
- 다시 한 번 실험그룹에 따라 집계를 한다. 이 때 day의 평균값을 구한다.
query = " SELECT experiment_group, avg(day) as login_day_avg \
FROM \
(SELECT experiment_group, e.user_id, COUNT(DISTINCT DATE_FORMAT(e.occurred_at, '%Y-%m-%d')) as day \
FROM experiments ex \
JOIN users u ON ex.user_id = u.user_id \
JOIN events e on ex.user_id = e.user_id \
AND ex.experiment = 'publisher_update' \
AND e.occurred_at LIKE '%2014-06%' \
AND e.event_type = 'engagement' \
GROUP BY experiment_group, e.user_id) a \
GROUP BY 1"
login_day_df = pd.read_sql(query, connection)
login_day_df
최종 데이터 및 그래프
- 최종 데이터
- 그래프
ax2= sns.barplot(data = login_day_df, x='experiment_group', y='login_day_avg') for p in ax2.patches: ax2.annotate(p.get_height(), (p.get_x()+p.get_width()/2, p.get_height()), fontsize=12,color='red', ha='center',va='center', textcoords='offset points')
분석2 결론
- test group의 유저들은 한 달 동안 평균 3일, control group의 유저들은 한 달 동안 평균 3.6일 로그인 했다.
- 실험 기간 동안에 control group과 test group의 평균 로그인 횟수, 평균 로그인 일수를 계산해봤더니 모두 control < test group
- 실험 기간동안 실험군이 대조군 대비 유저 당 평균 0.8회 더 많이 로그인 했다.
- 실험 기간동안 실험군이 대조군 대비 유저 당 평균 0.6일 더 많이 로그인 했다.
[분석3] 샘플링 공정성 확인
Test, Control group을 나눌 때 과연 샘플링이 공정하게 되었는가?
- 쿼리
query = " SELECT DATE_FORMAT(u.activated_at, '%Y-%m') as month_activated, \
COUNT(CASE WHEN e.experiment_group = 'control_group' THEN u.user_id ELSE NULL END) as control_users, \
COUNT(CASE WHEN e.experiment_group = 'test_group' THEN u.user_id ELSE NULL END) as test_users \
FROM experiments e \
JOIN users u \
ON e.user_id = u.user_id \
GROUP BY 1 \
ORDER BY 1"
sampling_df = pd.read_sql(query, connection)
sampling_df
- 최종 데이터
- 그래프
plt.figure(figsize=(10,6))
sns.lineplot(data=sampling_df, x='month_activated', y='control_users')
sns.lineplot(data=sampling_df, x='month_activated', y='test_users')
plt.xticks(rotation=45)
plt.show()
분석3 결과
- 2014.06월에 가입한 user들은 모두 control group에 포함되어 있다.
- A/B test를 실행한 6월에 가입한 user들은 이전에 가입한 user들에 비해 로그인, 메시지 발송 등 활동할 기회가 적다. (활동기간이 촉박하기 때문). 따라서 6월 가입자들의 로그인 지표, 활동 지표는 낮은게 당연
- 그런데 이러한 6월 가입자들이 모두 control group에 포함되어 있다. 이러한 이유로 control user group의 성과 지표가 좋지 않게 나온 것일 수도 있다.
- 6월 가입자들을 모두 제외하고 다시 메시지 발송건수를 분석해보자!
[분석4] 6월 가입자 제외 후 분석
- 쿼리
query = " SELECT experiment_group, COUNT(DISTINCT ex.user_id) user_cnt, \ COUNT(CASE WHEN event_name = 'send_message' THEN ex.user_id ELSE NULL END) as metric,\ COUNT(CASE WHEN event_name = 'send_message' THEN ex.user_id ELSE NULL END) / COUNT(DISTINCT ex.user_id) as average \ FROM experiments ex \ JOIN users u ON ex.user_id = u.user_id \ JOIN events e on ex.user_id = e.user_id \ AND ex.experiment = 'publisher_update' \ AND e.occurred_at >= ex.occurred_at \ AND e.occurred_at < '2014-07' \ AND e.event_type = 'engagement' \ AND u.activated_at NOT LIKE '%2014-06%' \ GROUP BY experiment_group" mod2_df = pd.read_sql(query, connection) mod2_df
- 최종 데이터
- 그래프
ax3 = sns.barplot(data=mod2_df, x='experiment_group',y='average') for p in ax3.patches: ax3.annotate(p.get_height(), (p.get_x()+p.get_width()/2, p.get_height()), fontsize=12,color='red', ha='center',va='center', textcoords='offset points')
분석4 결과
- control group에서 6월에 가입한 user들을 제외했더니 2.6 -> 2.9로 Average Message Sent 수가 증가했고, 실험군과 대조군의 차이는 적어짐.
- 차이가 적어졌지만 여전히 test group의 평균 메시지 전송수가 더 많다.
분석 결과 요약
- 실험 기간 동안 실험군이 대조군 대비 유저 당 평균 0.8회 더 많이, 0.6일 더 자주 로그인 함 (분석2)
- 실험 기간 동안 신규로 가입한 유저들이 모든 대조군으로 들어가는 오류가 있었다. (분석3)
- 실험 기간 동안 신규로 가입한 유저(2014-06 가입)들을 제외하고, 기존 유저들의 데이터만 분석했을 때에도 실험 기간 동안 실험군의 메시지 평균 전송횟수는 4.07회, 대조군 2.91회로 그 차이는 줄어들었으나 여전히 실험군에서 메시지 전송량이 많음을 확인 (분석4)
출처
💟 분석에 사용할 데이터와 분석할 문제에 대한 설명은 아래 링크에서 확인할 수 있다.
https://mode.com/sql-tutorial/validating-ab-test-results
💟데이터리안 강의
'SQL > 프로젝트' 카테고리의 다른 글
[데이터리안_플젝1] 유저 인게이지먼트 하락 원인 분석 ② (1) | 2024.01.02 |
---|---|
[데이터리안_플젝1] 유저 인게이지먼트 하락 원인 분석 ① (0) | 2024.01.01 |