CUPED в A/B-тесте: как использовать метрику до эксперимента
В A/B-тесте новая рекомендация товаров должна увеличить число просмотров карточек на пользователя. Но одни люди и до эксперимента просматривали много карточек, а другие почти не заходили. Случайное распределение групп может дать варианту B чуть больше активных людей. В результате разница средних во время теста смешивает эффект изменения с привычками пользователей.
Если для тех же рандомизированных пользователей есть метрика за период до показа варианта, её можно использовать для уменьшения такого шума. Приём называют CUPED. Для каждого пользователя берут значение X до эксперимента и Y во время него, затем считают скорректированное Y* = Y − θ(X − среднее X). Коэффициент θ = Cov(X,Y) / Var(X) показывает, какая часть текущей метрики связана с прежней активностью.
Сначала согласовать строки, потом считать коэффициент
Одна строка должна соответствовать единице рандомизации. Если вариант назначался пользователю, агрегируйте и X, и Y по пользователю; не размножайте пользователя соединением с отдельными событиями. Период для X должен завершиться до воздействия изменения. Когда у нового пользователя истории нет, заранее определите правило заполнения или отдельный анализ. Исключение новых пользователей из обеих групп меняет популяцию, для которой оценивается эффект: вывод будет относиться только к пользователям с историей.
В учебном наборе вариант B случайно получил пользователей с более высокой прежней активностью. Числа показывают арифметику коррекции на шести строках.
from statistics import mean
# Один ряд на рандомизированного пользователя: (группа, до, во время).
rows = [
('A', 2, 4), ('A', 4, 6), ('A', 6, 8),
('B', 4, 7), ('B', 6, 9), ('B', 8, 11),
]
x = [r[1] for r in rows]
y = [r[2] for r in rows]
x_bar, y_bar = mean(x), mean(y)
theta = sum((a-x_bar)*(b-y_bar) for a,b in zip(x,y)) / sum((a-x_bar)**2 for a in x)
def group_mean(group, adjusted):
values = [post - theta*(pre-x_bar) if adjusted else post
for arm,pre,post in rows if arm == group]
return mean(values)
print('theta:', round(theta, 3))
print('raw B-A:', round(group_mean('B', False)-group_mean('A', False), 3))
print('CUPED B-A:', round(group_mean('B', True)-group_mean('A', True), 3))
Код напечатает theta: 1.136, raw B-A: 3 и CUPED B-A: 0.727. До коррекции средние во время теста равны 6 и 9, но средние до теста равны 4 и 6. Общий коэффициент, оценённый по шести строкам, убирает часть различия, связанную с прежней активностью: 3 − 1.136 × (6 − 4) ≈ 0.727. Этот коэффициент оценивает связь двух метрик в выборке; он не равен истинному эффекту варианта B. По шести наблюдениям нельзя судить о точности оценки.
Что проверить перед выводом об эффекте
В рабочем анализе недостаточно сравнить две напечатанные разницы. По скорректированным пользовательским значениям нужно оценить неопределённость: стандартную ошибку и доверительный интервал либо подходящий тест, согласованный с дизайном эксперимента. Если коэффициент оценён на тех же данных, процедура расчёта интервала должна учитывать оценивание коэффициента; для аккуратного пайплайна полезны регрессионный анализ с индикатором группы или бутстрап на уровне единицы рандомизации.
Сильная связь X с Y обычно даёт больший выигрыш в точности. При слабой связи коррекция почти ничего не меняет. Метрика, измеренная после начала воздействия, уже может зависеть от варианта и потому не годится как обычная предэкспериментальная ковариата. Сравните охват историей и распределение X между группами; заметный перекос вместе с проблемой распределения участников требует отдельной диагностики.
Периоды до и во время теста должны выражать сопоставимое поведение. Если X — число просмотров за неделю, а Y — за две недели, формула остаётся вычислимой, но связь и коэффициент надо оценивать с учётом разных окон. Особенно аккуратно сопоставляйте пользователей с неполной историей: один день до теста нельзя без объяснения считать такой же полной неделей, как семь дней другого пользователя.
Оценка эффекта остаётся разностью групповых средних после корректировки. Само среднее скорректированной метрики не стоит показывать как новое фактическое число просмотров: это аналитическая величина, построенная для более точного сравнения вариантов. В отчёте удобно держать рядом исходные средние, скорректированную разницу и её интервал. Тогда продуктовая команда видит и наблюдаемый масштаб метрики, и основание для решения.
Курс «A/B тесты и анализ данных на Python» ведёт от выбора продуктовой метрики и расчёта размера выборки к анализу uplift, доверительных интервалов и частых ошибок эксперимента. В нём есть задачи с Python и SQL на прикладных метриках, а итоговая работа охватывает полный цикл теста. Откройте программу курса, чтобы встроить такую корректировку в осмысленный анализ эксперимента.
Перед интерпретацией любой скорректированной метрики полезно проверить распределение участников: разбор SRM в A/B-тесте показывает, что искать при неожиданном соотношении групп.
Продолжить на практике
- A/B тесты и анализ данных на Python ↗
Практический курс по A/B-тестированию и продуктовым метрикам: формулируем гипотезы, считаем размер выборки, запускаем эксперименты и анализируем результаты. От первой идеи до внедрения изменений в продукт — без «воды» и лишней теории.
