Группы «разъехались»: паника или норма?
// Условие
Джун из команды пишет в панике: «У нас в тесте группы разъехались — 40 200 против 39 800, целых 400 юзеров разницы! Это же SRM, тест испорчен?!» Дизайн сплита — 50/50.
result — dict с ключами p_value_srm, p_value_effect.// Данные
user_id, group (A/B), converted (0/1) — одна строка на юзера// Решение
Подсказка
SRM-чек: stats.chisquare([n_a, n_b], f_exp=[total/2, total/2]). Смотри на p-value: маленький (< 0.001) — сплит сломан, большой — всё в порядке.
Разница размеров групп при честной рандомизации — это биномиальный шум: ±400 из 80 000 — обычное дело.
p-value эффекта — z-тест двух долей, как обычно.
Сначала попробуй сам → Показать решение
df = load_data()
from scipy import stats
vc = df['group'].value_counts()
n_a, n_b = int(vc['A']), int(vc['B'])
total = n_a + n_b
# SRM: p ≈ 0.157 — отклонение 400 юзеров из 80 000 полностью объяснимо шумом
p_srm = stats.chisquare([n_a, n_b], f_exp=[total / 2, total / 2]).pvalue
s = df.groupby('group')['converted'].agg(['sum', 'count'])
pa = s.loc['A', 'sum'] / s.loc['A', 'count']
pb = s.loc['B', 'sum'] / s.loc['B', 'count']
p_pool = df['converted'].mean()
se = (p_pool * (1 - p_pool) * (1 / n_a + 1 / n_b)) ** 0.5
z = (pb - pa) / se
p_effect = 2 * (1 - stats.norm.cdf(abs(z)))
result = {'p_value_srm': p_srm, 'p_value_effect': p_effect}
print(result)
Что ответишь джуну?
Рандомизация — это подбрасывание монетки 80 000 раз: ровно 40 000 решек почти никогда не выпадает, и ±400 — типичное отклонение (p ≈ 0.16). SRM-чек делать обязательно, но тревожиться — когда p крошечный (< 0.001). Идеально равные группы, кстати, сами по себе подозрительны: значит, кто-то «подровнял» сплит руками. Хороший аналитик отличает шум от поломки — и учит этому команду.