[Yammer/Mode] A/B ํ ์คํธ ๊ฒฐ๊ณผ ๊ฒ์ฆ : 1๏ธโฃ SQL ์ฟผ๋ฆฌ ๋ฏ์ด๋ณด๊ธฐ
Yammer ๋ถ์ ํ๋ก์ ํธ"์ผ๋จธ(Yammer)"๋ ๋ง์ดํฌ๋ก์ํํธ ์ฐํ์ ๊ธฐ์ ์ฉ ์์ ๋คํธ์ํฌ ์๋น์ค ํ์ฌ์ ๋๋ค. Mode์์ ๊ฐ์์ ์ผ๋จธ์ฌ์ ๋ฐ์ดํฐ๋ฅผ ์ ๊ณตํฉ๋๋ค. ์ด๋ฅผ ํ์ฉํ์ฌ ํ์ ๊ณผ ์ ์ฌํ ๋ฌธ์ ์ํฉ์
syimmin-data-analysis.tistory.com
์ด์ ๊ธ์ ํตํด Yammer์ฌ์์ ์งํํ ์๋ก์ด ํฌ์คํ ๊ธฐ๋ฅ์ A/B test์ ๋ํด ์์๋ณด์์ต๋๋ค.
SQL์ ๋ฏ์ด๋ณด๋ ๊ณผ์ ์์ ์ด๋ค ์ค๋ฅ์ ํธํฅ์ด ๋ฐ์ํ๋์ง ์ถ์ธกํด๋ณผ ์ ์๋๋ฐ์,
์ค๋์ A/B test์ ์ํ์ ๋ํด์ ์ํ๋ง์์ ์ธ๋ถ ๋ณ์๊ฐ ์ ํต์ ๋์๋์ง, ์ฆ Random Sampling (๋ฌด์์ ์ถ์ถ)์ด ์ ์ด๋ฃจ์ด์ก๋์ง
๊ทธ๋ฆฌ๊ณ ๊ฐ์ค์ด ํต๊ณ์ ์ผ๋ก ์ ์๋ฏธํ๊ธฐ ์ํด ์ํ ์ฌ์ด์ฆ๊ฐ ์ ์ ํ์ง ์์๋ณด๊ฒ ์ต๋๋ค ๐
Random Sampling (๋ฌด์์ ์ถ์ถ)
A/B ํ ์คํธ์์ random sampling์ ํต์ ๋ณ์๊ฐ ์ ๊ด๋ฆฌ๋ ๊ฒ์ ์ ์ ๋ก ๋ชจ๋ ํ๋ณธ์ด ๋์ผํ ํ๋ฅ ์ ๊ฐ์ง ์ํ์์ ๋ฝ๋ ๋ฌด์์ ์ถ์ถ์ ์๋ฏธํฉ๋๋ค.
์ 3์ ๋ณ์๊ฐ ์ข ์ ๋ณ์(๋ชฉํ์งํ)์ ์ํฅ์ ๋ฏธ์น ์ ์๋ค๊ณ ํ๋จ๋๋ค๋ฉด ์ด๋ฅผ ํต์ ํ์ฌ ์ํ๋ง ๊ณผ์ ์ ๋ฐ์ํจ์ผ๋ก์จ ์ํฅ์ ์ฐจ๋จํด์ผ ํฉ๋๋ค.
ํํ ํ๋ ํ์๋ฒํธ๋ฅผ ํ/์ง์ผ๋ก ๋๋๋ ๋ฐฉ๋ฒ์ ๋๋ ์ํ๋ง์ด ์๋๋๋ค. ์ด๋ ํธ์ ์ถ์ถ(convenient sampling)์ผ๋ก ๋๋ค ์ํ๋ง๊ณผ ์ฌ๋๋ค์ด ํํ ํผ๋์ ํฉ๋๋ค.
์ฌ์ฉ ์ธ์ด์ ๋ถํฌ
select distinct
a.language,
count(a.user_id) over (partition by a.language) * 100 / count(a.user_id) over () as language_proportion,
count(b.user_id) over (partition by a.language) * 100 / count(b.user_id) over () as control_language_proportion,
count(c.user_id) over (partition by a.language) * 100 / count(c.user_id) over () as test_language_proportion,
count(a.user_id) over (partition by a.language) as user_count,
count(b.user_id) over (partition by a.language) as control_user_count,
count(c.user_id) over (partition by a.language) as test_user_count,
count(a.user_id) over () as total_users,
count(b.user_id) over () as control_total_users,
count(c.user_id) over () as test_total_users
from tutorial.yammer_users a
left join (select user_id from tutorial.yammer_experiments where experiment = 'publisher_update' and experiment_group = 'control_group') b
on a.user_id = b.user_id
left join (select user_id from tutorial.yammer_experiments where experiment = 'publisher_update' and experiment_group = 'test_group') c
on a.user_id = c.user_id
order by 2 desc, 3 desc, 4 desc

์ธ์ด ์ค์ (Language setting)๊ณผ ๊ฐ์ ์ธ๋ถ ๋ณ์๊ฐ ์คํ ๊ฒฐ๊ณผ์ ์ํฅ์ ์ค ์ ์๊ธฐ ๋๋ฌธ์, ๊ฐ ๊ทธ๋ฃน ๋ด ํด๋น ๋ณ์์ ๋ถํฌ๋ฅผ ํ์ธํ๋ ๊ฒ์ด ์ค์ํฉ๋๋ค.
์ ์ฒด ์ฌ์ฉ์, ๋์กฐ๊ตฐ, ์คํ๊ตฐ์ ์ธ์ด ์ค์ ๋น์จ์ ๋น๊ตํ ๊ฒฐ๊ณผ, ์ธ ๊ทธ๋ฃน ๊ฐ ๋ถํฌ๊ฐ ๊ฑฐ์ ๋์ผํ๊ฒ ๋ํ๋ ์ธ๋ถ ๋ณ์๋ก ์ธํ ์ํฅ์ ์ต์ํํ์์์ ์ ์ ์์ต๋๋ค.
์ ์ ๊ตญ๊ฐ์ ๋ถํฌ
select distinct
a.location,
count(a.user_id) over (partition by a.location) * 100 / count(a.user_id) over () as language_proportion,
count(b.user_id) over (partition by a.location) * 100 / count(b.user_id) over () as control_language_proportion,
count(c.user_id) over (partition by a.location) * 100 / count(c.user_id) over () as test_language_proportion,
count(a.user_id) over (partition by a.location) as user_count,
count(b.user_id) over (partition by a.location) as control_user_count,
count(c.user_id) over (partition by a.location) as test_user_count,
count(a.user_id) over () as total_users,
count(b.user_id) over () as control_total_users,
count(c.user_id) over () as test_total_users
from
(select distinct user_id, location
from tutorial.yammer_events) as a
left join (select user_id from tutorial.yammer_experiments where experiment = 'publisher_update' and experiment_group = 'control_group') b
on a.user_id = b.user_id
left join (select user_id from tutorial.yammer_experiments where experiment = 'publisher_update' and experiment_group = 'test_group') c
on a.user_id = c.user_id
order by 2 desc, 3 desc, 4 desc

์ ์ ๊ตญ๊ฐ ๋ํ ์คํ์ ์ํฅ์ ์ค ์ ์๋ ํ๋์ ์ธ๋ถ ๋ณ์๋ก ์ด๋ฅผ ํต์ ํด์ผ ํฉ๋๋ค.
์ ์ฒด ์ฌ์ฉ์, ๋์กฐ์ง๋จ, ์คํ์ง๋จ์ ์ ์ ๊ตญ๊ฐ์ ๋ถํฌ๋ฅผ ํ์ธํ์ ๋, ๋ชจ๋ ๊ฑฐ์ ํก์ฌํ ๋น์จ๋ก ๋ถํฌ๋์ด ์์์ ํ์ธํ ์ ์์ต๋๋ค.
์ฆ, ์ ์ ๊ตญ๊ฐ๋ ์ ํต์ ๊ฐ ๋์์ต๋๋ค.
๊ฐ์ ๊ฒฝ๊ณผ์ผ ๋ถํฌ
with a as
(SELECT
a.user_id,
a.experiment_group,
'2014-07-01' - b.created_at::date AS days_since_signup
FROM tutorial.yammer_experiments a
JOIN tutorial.yammer_users b
ON a.user_id = b.user_id
order by 3)
select
experiment_group,
days_since_signup,
count(user_id)
from a
group by 1, 2
order by 1, 2

2024-07-01 ๊ธฐ์ค์ผ๋ก ๊ฐ์ ๊ฒฝ๊ณผ์ผ์ ์๊ฐํํด๋ณด๋ฉด
๋์กฐ์ง๋จ์ ์ต๊ทผ ๊ฐ์ ํ ์ ์ ๊ฐ ๋ค์ ํฌํจ๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.
์ต๊ทผ ๊ฐ์ ํ ์ ์ ๋ ๊ธฐ์กด ์ ์ ์ ๋ฌ๋ฆฌ ์๋น์ค์ ๊ธฐ๋ฅ๋ค์ ํ๋ฐํ๊ฒ ํ์ํ๋ฉฐ ํฌ์คํ ๊ธฐ๋ฅ์ ๋ ๋ง์ด ์ฌ์ฉํ์ ๊ฐ๋ฅ์ฑ์ด ์์ต๋๋ค.
๋ฐ๋ผ์, ๊ฐ์ ๋ ์ง๋ผ๋ ์ธ๋ถ ๋ณ์ ๋ํ ํต์ ํ์ง ๋ชปํ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.

์คํ์ง๋จ์ ๊ฐ์ ์ผ์ด 32์ผ ๋ฏธ๋ง์ธ ์ ์ ๋ ์๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค..

๋์กฐ๊ตฐ์์ ์ ๊ท ๊ฐ์ ์์ ๋น์จ์ด ๊ณผ๋ํ๊ฒ ๋์ ๊ฒฐ๊ณผ์ ์ํฅ์ ์ค ์ ์์ผ๋ฏ๋ก,
์ด๋ฅผ ํต์ ํ๊ธฐ ์ํด ๊ฐ์ ์ผ ๊ธฐ์ค 32์ผ ๋ฏธ๋ง์ ์ฌ์ฉ์๋ฅผ ์ ์ธํด์ผ ํฉ๋๋ค.
๋ํ ์คํ์ด 6์ 1์ผ์๋ก ์์๋์๊ธฐ ๋๋ฌธ์ ๊ธฐ์กด ๊ฐ์ ์๋ค์ ๋์์ผ๋ก ์คํ์ ์งํํ๊ธฐ ์ํด ๊ฐ์ ์ผ์ 5์ 1์ผ ์ด์ ์ผ๋ก ์ถ๋ ค A/B test๋ฅผ ์ฌํด์ํด๋ณด๊ฒ ์ต๋๋ค.
์๋ก ์ค์ ํ ๋์กฐ์ง๋จ์ ์ธ์ด, ๊ตญ๊ฐ, ๊ธฐ๊ธฐ ๋ถํฌ ํ์ธํ๊ธฐ
์ ๊ท ๊ฐ์ ์๋ฅผ ์ ์ธํ ์ ๋์กฐ์ง๋จ์ด ์ธ์ด, ๊ตญ๊ฐ, ๊ธฐ๊ธฐ ๋ฑ์ ์ธ๋ถ ๋ณ์์ ์ํฅ์ ๋ฐ๋์ง ํ์ธํ๊ธฐ ์ํด ๋ค์ ๋ถํฌ๋ฅผ ํ์ธํฉ๋๋ค.
(์์ ์ฝ๋์ ๋์ผํ์ฌ ์ฝ๋๋ ์๋ตํ๊ฒ ์ต๋๋ค.)



ํ์ธ ๊ฒฐ๊ณผ, ์๋ก์ด ๋์กฐ์ง๋จ๊ณผ ์คํ์ง๋จ์ ์ธ์ด, ๊ตญ๊ฐ, ๊ธฐ๊ธฐ ๋ณ์๋ ์ ์ฒด ๋น์จ๊ณผ ๋์ผํ๊ฒ ๋ถํฌ๋์ด ์์ต๋๋ค.
๋์กฐ์ง๋จ๊ณผ ์คํ์ง๋จ์ engagement ๋น๊ต
-- 5.1 ~ 5.31๊น์ง์ engagement
-- 6.1 ~ 6.30๊น์ง์ engagement
-- test/control ์ง๋จ๋ณ๋ก ๋น๊ต
-- engagement์ login๊ณผ send_message๋ก ์ ์
with experiment_table as (
select
a.user_id,
a.experiment_group
from tutorial.yammer_experiments a
join tutorial.yammer_users b
on a.user_id = b.user_id
where a.experiment = 'publisher_update'
and b.created_at < '2014-05-01')
SELECT
ex.experiment_group,
count(distinct ex.user_id) as user_count,
(SUM(CASE WHEN e.event_name = 'login' and e.occurred_at BETWEEN '2014-05-01' AND '2014-05-31' THEN 1 ELSE 0 END) * 1.0)
/ count(distinct ex.user_id) AS old_login_ratio,
(SUM(CASE WHEN e.event_name = 'send_message' and e.occurred_at BETWEEN '2014-05-01' AND '2014-05-31' THEN 1 ELSE 0 END) * 1.0)
/ count(distinct ex.user_id) as old_sendm_ratio,
(SUM(CASE WHEN e.event_name = 'login' and e.occurred_at BETWEEN '2014-06-01' AND '2014-06-30'THEN 1 ELSE 0 END)*1.0)
/ count(distinct ex.user_id) AS new_login_ratio,
(SUM(CASE WHEN e.event_name = 'send_message' and e.occurred_at BETWEEN '2014-06-01' AND '2014-06-30' THEN 1 ELSE 0 END) * 1.0)
/ count(distinct ex.user_id) as new_sendm_ratio
FROM tutorial.yammer_events e
JOIN experiment_table ex
ON e.user_id = ex.user_id
GROUP BY ex.experiment_group
order by 2 desc

Old ratio๋ 5์, new ratio๋ ์คํ์ด ์งํ๋ 6์์ ์งํ๋ฅผ ์๋ฏธํฉ๋๋ค.
๋์กฐ๊ตฐ๊ณผ ์คํ๊ตฐ์ 5์ engagement ์งํ๋ฅผ ๋น๊ตํด๋ณธ ๊ฒฐ๊ณผ, ์คํ๊ตฐ์ engagement rate์ด ๋์กฐ๊ตฐ๋ณด๋ค ์ ์ด์ ๋ ๋์๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.
ํนํ login๊ณผ send_message ์งํ ๋ชจ๋์์ ์คํ๊ตฐ์ด ๋์กฐ๊ตฐ๋ณด๋ค ๋์ ์์น๋ฅผ ๊ธฐ๋กํ๊ณ ์์ต๋๋ค.
๋ฐ๋ผ์, A/B ํ ์คํธ์์ ๊ด์ฐฐ๋ ํฌ์คํ ์ ์ฐจ์ด๊ฐ ์ค์ ๋ก๋ ์คํ ๋ณ์์ธ ์๋ก์ด publish ๊ธฐ๋ฅ ๋๋ฌธ์ด ์๋ ๊ฐ๋ฅ์ฑ์ด ๋์ผ๋ฉฐ, ์ง๋จ ๊ฐ ์ฌ์ ์ฐจ์ด(pre-existing differences)์ ๊ธฐ์ธํ์ ๊ฐ๋ฅ์ฑ์ ๊ณ ๋ คํด์ผ ํฉ๋๋ค.
๋ฐ๋ผ์, ์ ์ ์ engagement์ ๋ฐ๋ผ ์ํฅ์ ๋ฐ๋ ํฌ์คํ ์๊ฐ ์๋
์ ์ ์ก์ ์ด ๋ก๊ทธ์ธํ ํ์ ํฌ์คํ ๊น์ง ์ด์ด์ก๋์ง, ๊ทธ ์ ํ์จ์ ์ธก์ ํ๋ ๊ฒ์ด ๋ ์ฌ๋ฐ๋ฅธ A/B test์ ๋ชฉํ์งํ๊ฐ ๋ ์ ์์ต๋๋ค.
๋ฐฐ์ด ์
A/B ํ ์คํธ์ ์ํ์ ์ ์ ํ ๋๋, ๊ตญ๊ฐ, ์ธ์ด ๋ฑ ์ธ๋ถ ๋ณ์๋ฅผ ํต์ ํ ์ํ์์ ๋ฌด์์๋ก ์ถ์ถํด์ผ ์คํ ๊ฒฐ๊ณผ์ ์ ๋ขฐ๋๋ฅผ ํ๋ณดํ ์ ์์ต๋๋ค.
ํนํ, ์ ๊ท ์ ์ ์ ๊ธฐ์กด ์ ์ ๋ ์๋ก์ฐ ๊ธฐ๋ฅ์ ๋ํ ๋ฐ์์ด ๋ค๋ฅผ ์ ์๊ธฐ ๋๋ฌธ์ a/b test๋ฅผ ์งํํ ๋์๋ ์ ๊ท ์ ์ ์ ๊ธฐ์กด ์ ์ ๋ฅผ ๋๋์ด์ ์งํํด์ผ ํฉ๋๋ค. ๋ฐ๋ผ์, ์ ๋ ์ ๊ท ์ ์ ๋ฅผ ์คํ์์ ์ ์ธํ์์ต๋๋ค.
๋ค์ ํฌ์คํ ์์๋ ์๋ก์ด ๋ชฉํ์งํ์ธ ์ ํ์จ์ ์ฐจ์ด๋ฅผ ๊ตฌํ๊ณ ๊ฒฐ๊ณผ๊ฐ ํต๊ณ์ ์ผ๋ก ์ ์๋ฏธํ์ง ํ์ธํ๊ธฐ ์ํด ์ํ์ฌ์ด์ฆ๋ฅผ ์ค์ ํ๊ณ t-test๋ฅผ ์งํํด๋ณด๊ฒ ์ต๋๋ค.