Yammer ๋ถ์ ํ๋ก์ ํธ
"์ผ๋จธ(Yammer)"๋ ๋ง์ดํฌ๋ก์ํํธ ์ฐํ์ ๊ธฐ์ ์ฉ ์์ ๋คํธ์ํฌ ์๋น์ค ํ์ฌ์ ๋๋ค. Mode์์ ๊ฐ์์ ์ผ๋จธ์ฌ์ ๋ฐ์ดํฐ๋ฅผ ์ ๊ณตํฉ๋๋ค. ์ด๋ฅผ ํ์ฉํ์ฌ ํ์ ๊ณผ ์ ์ฌํ ๋ฌธ์ ์ํฉ์ ๊ฐ์ ํ๊ณ ํด๊ฒฐํ๋ ํ๋ก์ ํธ๋ฅผ ์งํํ๊ฒ ์ต๋๋ค.
A/B test ์ํฉ
์ผ๋จธ์ฌ์์ ์๋ก์ด ํฌ์คํ
(publisher) ๊ธฐ๋ฅ์ ํ
์คํธํ๊ธฐ ์ํด A/B test๋ฅผ 6์ 1์ผ๋ถํฐ 6์ 30์ผ๊น์ง ์งํํ์์ต๋๋ค.
์ผ๋จธ์ฌ์ ๋ก๊ทธ์ธํ ์ผ๋ถ ์ฌ์ฉ์๋ค์ ๊ธฐ์กด ๋ฒ์ (๋์กฐ๊ตฐ)์ ๋ณด์๊ณ , ๋ค๋ฅธ ์ฌ์ฉ์๋ค์ ์๋ก์ด ๋ฒ์ (์คํ๊ตฐ)์ ๋ณด์์ต๋๋ค.
7์ 1์ผ A/B ํ
์คํธ ๊ฒฐ๊ณผ๋ฅผ ํ์ธํ ๊ฒฐ๊ณผ, ์คํ๊ตฐ์ด ๋์กฐ๊ตฐ ๋๋น ํฌ์คํ
๊ฐ์๊ฐ 50% ๋ ๋๋ค๋ ๊ฒ์ ๋ฐ๊ฒฌํ์ต๋๋ค. ์ด๋ ๊ฒ์ ํ๋์ด ํฌ๊ฒ ์ฆ๊ฐํ ๊ฒ์
๋๋ค.
โญ๏ธ ์ด๋ฒ ๊ธ์ ํตํด ์ผ๋จธ์ฌ์์ ์งํํ A/B test๊ฐ ํธํฅ๊ณผ ์ค๋ฅ์์ด ์ ๋๋ก ์งํ๋์๋์ง ํ์ธํ๊ธฐ ์ํด SQL ์ฝ๋๋ฅผ ๋ฏ์ด๋ณด๊ฒ ์ต๋๋ค!
A/B test ๊ฒฐ๊ณผ ํ์ธ

์ฟผ๋ฆฌ ํ์ธ
โฌ๏ธ Yammer Analyst(๊ฐ์)๊ฐ ์์ฑํ A/B test ๊ฒฐ๊ณผ ์ถ์ถ ์ฟผ๋ฆฌ
-- cte c์ ์ ๊ท๋ถํฌํ ํ
์ด๋ธ ์กฐ์ธํด์ ํต๊ณ์ ๊ฒ์ ์งํ
-- ์งํ ์ฐจ์ด, ์งํ ํฅ์๋, t_stat, p_value ๊ตฌํ๊ธฐ
SELECT c.experiment,
c.experiment_group,
c.users,
c.total_treated_users,
ROUND(c.users/c.total_treated_users,4) AS treatment_percent,
c.total,
ROUND(c.average,4)::FLOAT AS average,
ROUND(c.average - c.control_average,4) AS rate_difference,
ROUND((c.average - c.control_average)/c.control_average,4) AS rate_lift,
ROUND(c.stdev,4) AS stdev,
ROUND((c.average - c.control_average) /
SQRT((c.variance/c.users) + (c.control_variance/c.control_users))
,4) AS t_stat,
(1 - COALESCE(nd.value,1))*2 AS p_value
FROM (
-- cte c : ..?
SELECT *,
MAX(CASE WHEN b.experiment_group = 'control_group' THEN b.users ELSE NULL END) OVER () AS control_users,
MAX(CASE WHEN b.experiment_group = 'control_group' THEN b.average ELSE NULL END) OVER () AS control_average,
MAX(CASE WHEN b.experiment_group = 'control_group' THEN b.total ELSE NULL END) OVER () AS control_total,
MAX(CASE WHEN b.experiment_group = 'control_group' THEN b.variance ELSE NULL END) OVER () AS control_variance,
MAX(CASE WHEN b.experiment_group = 'control_group' THEN b.stdev ELSE NULL END) OVER () AS control_stdev,
SUM(b.users) OVER () AS total_treated_users
FROM (
-- cte b : ๋์กฐ/์คํ์ง๋จ์ ๋ชฉํ์งํ์ ํ๊ท , ์ ์ฒด ์, ํ์คํธ์ฐจ, ๋ถ์ฐ ๊ตฌํ๊ธฐ
SELECT a.experiment,
a.experiment_group,
COUNT(a.user_id) AS users,
AVG(a.metric) AS average,
SUM(a.metric) AS total,
STDDEV(a.metric) AS stdev,
VARIANCE(a.metric) AS variance
FROM (
-- cte a : ์คํ๊ตฐ/๋์กฐ๊ตฐ ์ ์ ์ send_message countํ๊ธฐ : ๋ชฉํ metric
SELECT ex.experiment,
ex.experiment_group,
ex.occurred_at AS treatment_start,
u.user_id,
u.activated_at,
COUNT(CASE WHEN e.event_name = 'send_message' THEN e.user_id ELSE NULL END) AS metric
FROM (
-- cte ex : ์คํ publisher_update ํ
์ด๋ธ ์์ฑ
SELECT user_id,
experiment,
experiment_group,
occurred_at
FROM tutorial.yammer_experiments
WHERE experiment = 'publisher_update'
) ex
-- ex
JOIN tutorial.yammer_users u
ON u.user_id = ex.user_id
JOIN tutorial.yammer_events e
ON e.user_id = ex.user_id
AND e.occurred_at >= ex.occurred_at
AND e.occurred_at < '2014-07-01'
AND e.event_type = 'engagement'
GROUP BY 1,2,3,4,5
) a
-- a
GROUP BY 1,2
) b
-- b
) c
-- c
LEFT JOIN benn.normal_distribution nd
ON nd.score = ABS(ROUND((c.average - c.control_average)/SQRT((c.variance/c.users) + (c.control_variance/c.control_users)),3))
์ฟผ๋ฆฌ ๋ฏ์ด๋ณด๊ธฐ
์ ์ฝ๋๋ ์๋ธ์ฟผ๋ฆฌ๊ฐ ๋ง์์ ๋จ๊ณ๋ณ๋ก ํ๋ฒ ์ฐจ๊ทผ์ฐจ๊ทผ ๋ฏ์ด๋ณด๊ฒ ์ต๋๋ค!
โช๏ธ CTE ex :
with ex as (
SELECT user_id,
experiment,
experiment_group,
occurred_at
FROM tutorial.yammer_experiments
WHERE experiment = 'publisher_update'
)
ํ์์ ๋ค์์ ์คํ์ด ์งํ๋ ํ ๋ ๊ทธ ์ค 'publisher_update' ์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์จ๋ค
โช๏ธ CTE a : ์คํ ๊ธฐ๊ฐ ๋ด์ ์คํ/๋์กฐ์ง๋จ์ด ๋ชฉํ์งํ์ธ 'send_message'๋ฅผ ํ ์๋ฅผ countํ๊ธฐ
a as (
SELECT ex.experiment,
ex.experiment_group,
ex.occurred_at AS treatment_start,
u.user_id,
u.activated_at,
COUNT(CASE WHEN e.event_name = 'send_message' THEN e.user_id ELSE NULL END) AS metric
FROM ex
JOIN tutorial.yammer_users u
ON u.user_id = ex.user_id
JOIN tutorial.yammer_events e
ON e.user_id = ex.user_id
AND e.occurred_at >= ex.occurred_at
AND e.occurred_at < '2014-07-01'
AND e.event_type = 'engagement'
GROUP BY 1,2,3,4,5
)
- yammer_events(์ ์ ํ๋ ๋ก๊ทธ ํ ์ด๋ธ), yammer_users(์ ์ ๊ณ์ ํ ์ด๋ธ)์ ์กฐ์ธํ๋ค.
- ์คํ ์ข ๋ฅ (experiment), ์คํ/๋์กฐ ์ง๋จ (experiment_group), ์คํ ์์ ๊ธฐ๊ฐ (ex.occurred_at), ์ ์ ID (user_id), ๊ณ์ ํ์ฑํ ์๊ฐ(activated_at)์ ๊ธฐ์ค์ผ๋ก ๊ทธ๋ฃนํํ๋ค.
- ์ ์ ๋ณ๋ก 'send_message'(ํฌ์คํ )์ ํ๋์ ํ ์๋ฅผ countํ๋ค. -> ์คํ์ ๋ชฉํ์งํ
์กฐ๊ฑด
- e.occurred_at (ํฌ์คํ ํ ์๊ฐ)์ด ex.occurred_at (์คํ/๋์กฐ์ง๋จ์ ์ฒ์น๋ฐ์ ์๊ฐ) ์ดํ์ ๋ฐ์
- 2014๋ 7์ 1์ผ ์ ์ ํ๋ ๋ก๊ทธ๋ฅผ ํํฐ๋ง (ex.occurred_at์ 2014๋ 6์ 1์ผ๋ถํฐ 30์ผ๊น์ง ์งํ)
โช๏ธ CTE b : ๋์กฐ์ง๋จ / ์คํ์ง๋จ์ ๋ชฉํ์งํ ๋น๊ตํ๊ธฐ
b as (
SELECT a.experiment,
a.experiment_group,
COUNT(a.user_id) AS users,
AVG(a.metric) AS average,
SUM(a.metric) AS total,
STDDEV(a.metric) AS stdev,
VARIANCE(a.metric) AS variance
FROM a
GROUP BY 1,2
)
- ์คํ/๋์กฐ์ง๋จ ์ ์ ์ / ์ ์ ๋ณ๋ก ํฌ์คํ (๋ชฉํ์งํ)์ ํ ํ์์ ํ๊ท / ์ง๋จ๋ณ ์ ์ฒด ํฌ์คํ ์ / ์ง๋จ๋ณ ํฌ์คํ ํ์คํธ์ฐจ & ๋ถ์ฐ

โช๏ธ CTE c :
c as (
SELECT *,
MAX(CASE WHEN b.experiment_group = 'control_group' THEN b.users ELSE NULL END) OVER () AS control_users,
MAX(CASE WHEN b.experiment_group = 'control_group' THEN b.average ELSE NULL END) OVER () AS control_average,
MAX(CASE WHEN b.experiment_group = 'control_group' THEN b.total ELSE NULL END) OVER () AS control_total,
MAX(CASE WHEN b.experiment_group = 'control_group' THEN b.variance ELSE NULL END) OVER () AS control_variance,
MAX(CASE WHEN b.experiment_group = 'control_group' THEN b.stdev ELSE NULL END) OVER () AS control_stdev,
SUM(b.users) OVER () AS total_treated_users
FROM b)
- ๋์กฐ์ง๋จ์ ์ ์ ์, ํฌ์คํ ์ ํ๊ท , ํฌ์คํ ์ ์ฒด ์, ํฌ์คํ ๋ถ์ฐ & ํ์คํธ์ฐจ๋ฅผ ๊ตฌํด์ค๋ค ..? (์ ํ๋์ง ๋ชจ๋ฅด๊ฒ ์)
- + ์ ์ฒด ์คํ์ ์ฐธ๊ฐํ ์ ์ ์๋ฅผ ๊ตฌํด์ค๋ค
โช๏ธ ์ต์ข select ๋ฌธ : t-ํต๊ณ๋์ผ๋ก ํต๊ณ์ ์ ์์ฑ ๊ฒ์ฆํ๊ธฐ
SELECT c.experiment,
c.experiment_group,
c.users,
c.total_treated_users,
ROUND(c.users/c.total_treated_users,4) AS treatment_percent,
c.total,
ROUND(c.average,4)::FLOAT AS average,
ROUND(c.average - c.control_average,4) AS rate_difference,
ROUND((c.average - c.control_average)/c.control_average,4) AS rate_lift,
ROUND(c.stdev,4) AS stdev,
ROUND((c.average - c.control_average) /
SQRT((c.variance/c.users) + (c.control_variance/c.control_users))
,4) AS t_stat, -- t-test
(1 - COALESCE(nd.value,1))*2 AS p_value
FROM c
LEFT JOIN benn.normal_distribution nd -- t-test์ p-value ํ์ธ
ON nd.score = ABS(ROUND((c.average - c.control_average)/SQRT((c.variance/c.users) + (c.control_variance/c.control_users)),3))
- ๋์กฐ๊ตฐ ๋๋น ์คํ๊ตฐ์ ์งํ๊ฐ ์ผ๋ง๋ ๋ณํ๊ฐ ์์๋์ง ํ์ธํ๋ค -> rate_difference, rate_lift
- two-sample t-test (๋์ง๋จ์ ํ๊ท ๋น๊ต)๋ฅผ ์งํํ๋ค. -> ๋์กฐ์ง๋จ๊ณผ ์คํ์ง๋จ์ ๋ชฉํ์งํ์ ํ๊ท ์ฐจ์ด๊ฐ ์ ์๋ฏธํ์ง ๊ฒ์ฆ

- t-test์ p-value๋ฅผ ๊ณ์ฐํ๋ค
-> t๋ถํฌ๋ z๋ถํฌ์ ์ ์ฌํ๊ธฐ ๋๋ฌธ์ mode์ benn.normal_distribution (์ ๊ท๋ถํฌ ํ
์ด๋ธ)์ ๊ฐ์ ธ์์ t-๊ฐ์ ๋์ ํ๋ฅ (nd.value)๋ฅผ ๊ตฌํด์ค๋ค.
-> ์์ธก๊ฒ์ ์ด๊ธฐ ๋๋ฌธ์ (1 - nd.value) ์ 2๋ฅผ ๊ณฑํด์ค์ p-value๋ฅผ ๊ตฌํ๋ค.
๊ฒฐ๊ณผ ํด์
์ฟผ๋ฆฌ๋ฅผ ๋ฏ์ด๋ณด๋ฉด์ SQL์ ํ์ฉํด์ A/B test์ ๊ฒฐ๊ณผ๋ฅผ ๋์ถํ๊ณ ํต๊ณ์ ๊ฒ์ ์ ์งํํ๋ ๋ฐฉ๋ฒ์ ์์๋ณผ ์ ์์์ต๋๋ค.
๊ทธ๋ฌ๋ ์ฟผ๋ฆฌ๋ฅผ ํ์ธํด๋ณด๋ฉด ํด๋น A/B test์์ ๋ค์์ ์ค๋ฅ๊ฐ ๋ฒํด์ง ๊ฒ์ ์ ์ ์์ต๋๋ค.
๋ค์ ํฌ์คํธ์์๋ ์ด๋ค ์ค๋ฅ๊ฐ ๋ฒํด์ก๋์ง ๊ทธ๋ฆฌ๊ณ ์ด๋ป๊ฒ ๊ณ ์ณ์ผ ์ ์๋ฏธํ A/B test ๊ฒฐ๊ณผ๋ฅผ ๋์ถํ ์ ์์์ง ์์๋ณด๊ฒ ์ต๋๋ค!