CUPED в A/B-тесте: когда снижение дисперсии работает
Формула CUPED, предэкспериментальная ковариата и выигрыш в точности на данных DragonKeep. Почему у новых игроков метода нет, а у старых выигрыш всего 0,11%.
Содержание статьи
CUPED снижает шум A/B-оценки с помощью признака, известного до назначения варианта. Для пользовательского исхода Y и предэкспериментального X берут Y* = Y − θ(X−среднее X), где θ=Cov(X,Y)/Var(X). При подходящих условиях дисперсия отдельного скорректированного исхода снижается на ρ², но реальный выигрыш надо измерять на своих данных. У новичков учебного onboarding_v2 история до экспозиции отсутствует: все X=0, поэтому стандартный CUPED по прежним сеансам здесь невозможен. У старых игроков A/A-примера связь до и после почти нулевая, и снижение дисперсии лишь 0,11%.
Когда дисперсия мешает решению
Два игрока могут получить одинаковый экран, но один заходил в игру ежедневно ещё до теста, другой едва помнит пароль. После запуска их сеансы будут сильно различаться и без эффекта продукта. Если прежняя активность предсказывает будущую, часть наблюдаемого разброса можно объяснить заранее известной характеристикой. Тогда сравнение вариантов на остаточном шуме точнее, чем сравнение сырых пользовательских Y.
CUPED, Controlled-experiment Using Pre-Experiment Data, не создаёт новых пользователей и не делает дизайн валидным. Метод использует корреляцию предэкспериментальной ковариаты с исходом. Он полезен, когда измерение X одинаково доступно для обеих групп до воздействия, а исход Y считается на той же единице независимого назначения. Если тест назначен аккаунту, а ковариата собрана по отдельным устройствам, сначала определите, как они складываются в аккаунт.
Разброс сам по себе не повод добавлять коррекцию. Вначале выпишите исход и период, на которых будет принято решение: например, сеансы за семь дней после экспозиции на назначенный аккаунт. Затем спросите, есть ли заранее измеренный показатель, который действительно предсказывает этот исход. Если тест начинается с новой установки, «прошлые сеансы» звучат привлекательно только в презентации; в данных их нет. Если признаки есть лишь у старых платящих клиентов, применение CUPED на всех клиентах может изменить состав анализируемой популяции. Сохраните нули и пропуски как отдельную проблему дизайна.
Формула и смысл центрирования
Пусть Yᵢ — число сеансов за семь дней после экспозиции, Xᵢ — число сеансов того же игрока за семь дней до неё. Возьмём θ как выборочную Cov(X,Y)/Var(X) на объединённых группах и вычислим Yᵢ*=Yᵢ−θ(Xᵢ−X̄). Игроку с необычно большим X отнимется прогнозируемая часть Y; игроку с малым X она добавится. Центрирование по общей средней сохраняет читаемый уровень метрики.
Если распределение X одинаково между вариантами в рандомизированном дизайне и X не затронут назначением, ожидаемая разница средних поправочного слагаемого равна нулю. Поэтому асимптотически оценка эффекта остаётся ориентирована на тот же причинный контраст. В конкретной конечной выборке скорректированная разница обычно изменится: случайный дисбаланс прошлой активности вычитается. Параметр θ, оценённый на тех же наблюдениях, добавляет небольшую конечновыборочную неопределённость; для строгой реализации используйте корректную дисперсию или независимую оценку коэффициента.
Удобная ручная проверка: если θ положителен, игрок с X выше общего среднего получает скорректированный Y ниже исходного; при отрицательном θ — выше. После корректировки среднее Y* по объединённой выборке остаётся равным среднему Y, поскольку сумма центрированных X равна нулю. Разность групп при этом может сместиться вверх или вниз. Если в вычислительном отчёте изменилась общая средняя, вероятно, использованы разные центры для групп или потеряны игроки при соединении. Этот контроль простой, но быстро ловит дефект реализации до обсуждения статистической значимости.
Y* = Y − θ(X − mean X); θ = Cov(X,Y) / Var(X)X измерен до воздействия, Var(X)>0, единица — назначенный пользователь.
Откуда берётся обещание ρ²
Разверните Var(Y−θX)=Var(Y)+θ²Var(X)−2θCov(X,Y). При θ=Cov(X,Y)/Var(X) квадратная форма минимальна и равна Var(Y)·(1−ρ²), где ρ — корреляция X и Y. Это объясняет идею: при |ρ|=0 метод не помогает, при |ρ|=0,6 теоретическая дисперсия скорректированного индивидуального исхода ниже на 36%. Знак корреляции не мешает: оптимальный θ тоже сменит знак.
Для планирования выборки часто используют приближение: чтобы сохранить прежнюю стандартную ошибку при прочих равных, объём можно уменьшить примерно в той же пропорции, что и дисперсию. Но точное сокращение срока зависит от трафика, асимметрии групп, оценивания θ, стабильности корреляции и полной зрелости метрики. Нельзя обещать «сократить тест на треть» по чужому ρ; сперва проверьте связь на собственной предэкспериментальной истории.
Формула ρ² относится к дисперсии индивидуального скорректированного Y при оптимальном фиксированном коэффициенте в той же популяции. Для стандартной ошибки разности двух групп дополнительно важны их размеры, дисперсии и способ оценки θ. Если ковариата связана с исходом неодинаково в control и test, один общий коэффициент может быть неидеален для каждой группы. В рабочем отчёте покажите обе стандартные ошибки, а не только вычисленный квадрат корреляции. Именно поэтому ниже отдельно приведены 0,028548 и 0,028531: это масштаб выигрыша для сравниваемой разности в фактической таблице.
| |ρ| | ρ² | Как читать |
|---|---|---|
| 0 | 0% | ковариата не помогает |
| 0,3 | 9% | небольшое снижение шума |
| 0,6 | 36% | существенный потенциал при валидной ковариате |
Новые игроки: честный нулевой пример
В onboarding_v2 назначено 17 003 игрока. Для каждого exposure_date совпадает с install_date: человек попал в эксперимент в день установки. До этого дня в учебной истории сеансов нет. Если взять X как число сеансов за семь дней перед экспозицией, все X равны нулю и Var(X)=0. Деление на ноль в θ — не техническая помеха, которую стоит замаскировать константой; нужной информации для коррекции действительно нет.
Можно искать другие действительно предлечебные признаки: канал привлечения, устройство, страна или когорту установки, если они зафиксированы до назначения и их связь с исходом измерима. Но это уже иной covariate adjustment, а не чудесное восстановление отсутствующей истории. Для новичков важнее достаточный трафик, стабильная рандомизация и корректный горизонт D7. MDE и размер выборки задают реальную цену неопределённости до запуска.
| Эксперимент | Кто назначен | До-период | Решение |
|---|---|---|---|
| onboarding_v2 | новые игроки при установке | все X=0 | нет: θ не определён |
| menu_color | часть существующих игроков | X меняется | можно измерить ρ, но SRM ломает причинный вывод |
Старые игроки: ковариата есть, выгода почти исчезла
В A/A-примере menu_color у 2 934 из 20 279 назначенных есть хотя бы один сеанс в семидневном до-периоде. Среднее X=0,360 сеанса, среднее Y за семь дней после=2,988. Корреляция между X и Y −0,0332; θ=−0,0626. Оптимальная поправка снимает лишь ρ²=0,001101, то есть 0,11% общей дисперсии индивидуального исхода. Это реальная величина на этих учебных данных, не обещанные 36% из иллюстрации.
Сырая разница test минус control по сеансам на игрока −0,0150, скорректированная −0,0163. Стандартные ошибки примерно 0,028548 и 0,028531. Разница в точности практически незаметна. Даже валидная ковариата не обязана быть полезной: при небольшой или неустойчивой корреляции CUPED усложняет расчёт без ощутимой экономии трафика. Выбирайте X по прогнозной силе до просмотра исхода нужного A/B.
Отрицательный ρ здесь не означает, что метод «повышает» шум. θ тоже отрицателен, а снижение дисперсии определяется квадратом ρ. Но при |ρ| около 0,033 этот квадрат крошечный. Важно не подбирать длину до-периода после просмотра Y ради максимального выигрыша: семидневное окно, месячное окно, число платежей и давность регистрации — множество кандидатов, их свободный перебор по тому же исходу создаст оптимизм. Для следующего теста сравните кандидатов на прежних данных, закрепите один и проверьте его в новой когорте. Это организационный контроль столь же существенен, как формула θ.
Шкала начинается с нуля; различие 0,028548 против 0,028531 очень мало. Это иллюстрация точности, не доказательство эффекта.
SRM не исправляется коррекцией
menu_color запланирован как A/A с разбиением 50/50, но в экспозициях 10 929 control и 9 350 test — примерно 54/46. Это явный sample ratio mismatch, заложенный в учебный генератор. Никакая поправка на прежние сеансы не объясняет, почему назначение не соответствует плану. Даже если CUPED сузил бы интервал, причинный вывод по такому A/A сначала требует диагностики сплита и логирования.
Поэтому числа выше служат только иллюстрацией алгебры и фактической корреляции в старшей популяции. По ним нельзя утверждать «цвет меню снизил активность» или оценивать производственную выгоду метода. Для проверки системы назначений посмотрите A/A-тест и SRM и диагностику sample ratio mismatch. Сначала устраните неисправность разбиения, потом возвращайтесь к оценке эффекта.
Код: проверить наличие истории и ρ
Пример собирает по одному X и Y на назначенного игрока, включая нулевые значения. Экспозиции для каждого эксперимента берутся отдельно. Сначала проверяется, что до-период в онбординге пуст; затем на menu_color выводятся ρ, θ и снижение дисперсии. session_id агрегируется по пользователю, чтобы повторные строки JOIN не стали отдельными назначениями. Код не запускает причинный тест по menu_color, потому что сплит у него уже сломан.
import numpy as np
import pandas as pd
base='dataset/gamedev/'
e=pd.read_csv(base+'exposures.csv',parse_dates=['exposure_date'])
s=pd.concat([pd.read_csv(base+'sessions.csv',parse_dates=['session_date']),pd.read_csv(base+'extra_sessions_onboarding_v2.csv',parse_dates=['session_date'])])
def windows(exp):
a=e.loc[e.exp_id.eq(exp),['user_id','variant','exposure_date']]
j=a.merge(s[['user_id','session_id','session_date']],on='user_id',how='left')
pre=j.loc[j.session_date.ge(j.exposure_date-pd.Timedelta(days=7))&j.session_date.lt(j.exposure_date)]
post=j.loc[j.session_date.ge(j.exposure_date)&j.session_date.lt(j.exposure_date+pd.Timedelta(days=7))]
out=a[['user_id','variant']].merge(pre.groupby('user_id').session_id.nunique().rename('X'),on='user_id',how='left')
return out.merge(post.groupby('user_id').session_id.nunique().rename('Y'),on='user_id',how='left').fillna({'X':0,'Y':0})
new=windows('onboarding_v2')
old=windows('menu_color')
assert new.X.var()==0
x,y=old.X.to_numpy(),old.Y.to_numpy()
rho=np.corrcoef(x,y)[0,1]
theta=np.cov(x,y,ddof=1)[0,1]/np.var(x,ddof=1)
y_adj=y-theta*(x-x.mean())
gain=1-np.var(y_adj,ddof=1)/np.var(y,ddof=1)
print(len(new),round(rho,6),round(theta,6),round(gain*100,4))
assert abs(gain-.001101)<.000001Как выбирать ковариату до просмотра результата
Хорошая X наблюдается до варианта, существует у большей части назначенных и предсказывает ту же или близкую величину Y. Для частоты использования приложения полезна прежняя частота; для дохода — исторический доход при достаточной плотности покупок. Но «похожее название» не гарантирует сильной связи: menu_color показал, что семидневные сеансы до и после здесь почти не коррелируют. Измеряйте долю объяснённой дисперсии в независимой истории или заранее определённом обучающем периоде.
Канал привлечения может быть предлечебным, но категорию нужно кодировать и оценивать иначе, чем одну непрерывную X. Несколько ковариат ведут к регрессионной корректировке; добавление большого числа случайных или постлечебных признаков усложняет интерпретацию и оценку неопределённости. Запишите источник, временной срез, обработку пропусков и план проверки баланса до того, как увидите результат A/B.
Если вы измеряете новую пользовательскую метрику только у тех, кто вернулся после недели, данные X могут существовать у всех, а Y — лишь у «выживших». CUPED не решит проблему отбора исхода: сравнение будет зависеть от варианта через вероятность возвращения. Сначала восстановите намерение лечить, то есть включение всех назначенных с заранее оговорённым нулём или иным наблюдаемым исходом. Затем уже на этой корректной пользовательской таблице оценивайте ковариацию. Иначе очень красивое снижение дисперсии может сопровождать смещённую оценку эффекта.
Когда возникает утечка эффекта
Если вместо сеансов до экспозиции взять активность в первый день после нового онбординга, изменение продукта уже могло повлиять на X. Вычитание θX тогда удалит часть настоящего эффекта или создаст смещение. Признак, обновлённый после назначения, не становится «предэкспериментальным» от того, что лежит в таблице пользователя. Сверяйте временные метки с моментом рандомизации и экспозиции, особенно при запоздалой доставке событий.
Утечка бывает тоньше: модель активности обучена на данных, содержащих результаты теста, или когортный рейтинг пересчитан после релиза. Даже если отдельная колонка имеет вчерашнюю дату, процедура её построения могла видеть будущее. Для каждого X полезно сохранить lineage: какие сырые события, до какого времени и какой версией кода его создали. Коррекция дисперсии имеет смысл только после этой проверки.
Сколько пользователей можно сэкономить
При ρ²=0,001101 приблизительное сокращение нужного объёма при той же стандартной ошибке — 0,11%, если остальные условия стабильны. Для гипотетического |ρ|=0,6 это было бы около 36%, но такой корреляции в рассмотренных экспериментах нет. Переводить проценты объёма непосредственно в календарные дни тоже нельзя: приток пользователей меняется по неделям, а исход D7 должен дозреть после последней экспозиции.
Для практического плана пересчитайте MDE или мощность с собственной оценкой дисперсии скорректированной метрики, затем добавьте окно полного наблюдения и проверки качества. Если потенциальная экономия меньше колебания трафика, приоритетнее исправить сбор данных, зафиксировать метрику и исключить SRM. Не вводите CUPED в production только ради теоретической возможности уменьшить sample size.
План экономии должен учитывать, что команда обычно принимает решение не по одному исходу. Даже если CUPED снизит шум основной метрики, тест нельзя закончить раньше, если защитная метрика требует большего периода или большей выборки. Для D7 у последнего назначенного игрока неизбежно ждут семь дней до результата; снижение нужного числа игроков не превращает эти семь дней в ноль. В протоколе удобно отдельно записывать время набора, время дозревания и время проверки качества данных. Только первая часть может сократиться за счёт меньшей целевой выборки.
Пять ошибок, после которых CUPED вредит
Первая: вычислить X по событиям после экспозиции. Можно вычесть настоящий эффект и получить ложное отсутствие пользы. Вторая: делить Cov на Var(X)=0 у новичков. Код сломается или тихо подставит бессмысленный коэффициент. Третья: обещать экономию по формуле ρ² без измерения фактической связи. В menu_color такая экономия составляет только 0,11%.
Четвёртая: игнорировать SRM, потому что скорректированная SE уменьшилась. Группы останутся подозрительными. Пятая: считать X и Y по сессиям как по независимым единицам при пользовательском назначении. Интервал станет искусственно точным. Шестая: обучить θ на данных, которые уже видели итог теста, а затем трактовать обычную стандартную ошибку как будто коэффициент фиксирован извне. Нужна соответствующая процедура оценки дисперсии или независимая оценка θ.
Частые вопросы о CUPED
CUPED всегда уменьшает дисперсию? При оптимальном коэффициенте на той же популяции и идеальной оценке дисперсия исхода не растёт. В конечной выборке оценённый θ и нестабильная корреляция могут не дать практического выигрыша для разности групп.
Можно применить CUPED к новым пользователям? Да, если есть информативная предлечебная ковариата, например известный до назначения канал. Но прежних сеансов у них нет; этот конкретный X не работает.
Меняется ли смысл эффекта? При валидной предэкспериментальной X и корректном дизайне цель остаётся разницей исхода между вариантами. При постлечебной X смысл и смещение меняются.
CUPED сокращает длительность теста вдвое? Только при очень сильной и устойчивой корреляции можно ожидать соответствующий выигрыш; в данных этой статьи он отсутствует.
Решение и первоисточник
Для onboarding_v2 CUPED по прежним сеансам не применяем: Var(X)=0. Для menu_color не заявляем ни продуктовый эффект, ни полезное сокращение теста: SRM не исправлен, а снижение дисперсии всего 0,11%. Если в следующем эксперименте будет зрелая когорта и валидная история, измерьте ρ до чтения исхода, затем сравните скорректированную и сырую точность по пользователям. Сохраните обе оценки в отчёте.
Метод описан в статье Deng, Xu, Kohavi, Walker, «Improving the Sensitivity of Online Controlled Experiments by Utilizing Pre-Experiment Data» (WSDM, 2013). Для выбора primary и защитных метрик используйте хаб A/B-тестирования; для практики полного цикла анализа — SQL-симулятор.
Материалы по теме

Ошибки первого и второго рода в A/B-тесте: цена решения
Что такое α, β и мощность, как размер выборки меняет риск и почему p-value не доказывает отсутствие эффекта. Симуляция 1 000 экспериментов на данных DragonKeep.

Бутстреп в статистике: интервалы для метрик A/B-теста
Как построить процентильный бутстреп по пользователям: разница средней выручки, медиана и ARPPU на учебном A/B-тесте, код Python и ограничения метода.

Ratio-метрики в A/B-тесте: дельта-метод и кластеры
Как считать отношение сумм, дельта-метод, линеаризацию и бутстреп по пользователям. На сессиях учебного A/B-теста среднее пользовательских долей меняет знак.