Раздел 42 · Аналитика данных с нуля

CUPED и стратификация: снижение дисперсии данными до теста

middle~15 мин

без входаВойди, чтобы отмечать прогресс, вести заметки и решать задачи в редакторе. войти

Фрея · риск-менеджер необанка «Монетка»

Следующий тест кешбэка хотят запустить на месяц, а не на квартал. Мне сказали, что точности не хватит. У нас же есть история каждого клиента за три месяца до теста. Неужели она ничем не поможет?

Поможет. Траты в кафе на клиента у теста выше на 697,59 ₽ за квартал, стандартная ошибка 197,60 ₽, интервал от 310 до 1085 ₽. Интервал широкий, потому что клиенты разные: один обедает в ресторанах, другой раз в месяц берёт кофе. В уроке про мощность мы видели, что дисперсия метрики прямо задаёт, сколько клиентов и дней нужно тесту. Сегодня уменьшим её, не трогая сам тест.

Взвешивание с поправкой на тару

Ты взвешиваешь варенье в банках. Банки разные, от 300 до 700 граммов стекла, и разброс веса огромный. Но вес каждой пустой банки известен заранее. Вычитаешь его, и остаётся почти один разброс варенья.

С клиентами так же. Большая часть разброса трат в кафе за время теста это привычки, которые были и до теста. Траты клиента за январь-март и есть «вес пустой банки». Вычтем ту часть трат, которую предсказывает история, и останется разброс, связанный с самим тестом и случаем.

Ковариата до теста

Сначала убедимся, что история лежит до теста. Каждого клиента назначали в группу в свой момент, assigned_at:

SELECT min(assigned_at), max(assigned_at) FROM cashback_groups;
          min           |          max
------------------------+------------------------
 2026-04-01 00:00:00+00 | 2026-04-02 05:00:00+00

Операции в базе начинаются 1 января. Значит, у каждого клиента есть три месяца до назначения. Ни одной оплаты в кафе между 1 апреля 00:00 и назначением клиента нет, поэтому граница «до и после» по assigned_at даёт те же траты за время теста, что и в уроке 54. Правило всё равно строгое: ковариата считается до того, как клиент увидел свой вариант. Собрать её в SQL ты сможешь в квесте ★★.

В cafe_cuped.csv по строке на клиента: pre_cafe_rub это траты в кафе до назначения (X), cafe_rub траты после (Y).

import numpy as np
import pandas as pd
from scipy import stats

clients = pd.read_csv("cafe_cuped.csv")
clients.groupby("test_group")[["pre_cafe_rub", "cafe_rub"]].mean()
            pre_cafe_rub     cafe_rub
test_group
control      3651.120724  3746.078471
test         3435.562500  4443.664959

До теста группа test тратила в кафе на 215,56 ₽ меньше. Это случайность жребия, p = 0,25, но она уже сидит внутри сырого эффекта.

CUPED

CUPED делает поправку в одну строку:

theta    = cov(X, Y) / var(X)
Y_cuped  = Y - theta * (X - mean(X))

theta это наклон прямой, по которой траты до теста предсказывают траты во время теста, как в уроке про регрессию. X - mean(X) показывает, насколько клиент тратил больше или меньше среднего до теста. Эту разницу, умноженную на наклон, мы и вычитаем.

x = clients["pre_cafe_rub"]
y = clients["cafe_rub"]
theta = np.cov(x, y)[0, 1] / x.var()     # 0.533
rho = np.corrcoef(x, y)[0, 1]            # 0.502
y_cuped = y - theta * (x - x.mean())
y_cuped.var() / y.var()                  # 0.748

Дисперсия упала до 1 - rho**2 = 0,748 от исходной: стандартное отклонение трат было 4393 ₽, стало 3799 ₽. Это не совпадение, а свойство формулы: при таком theta поправка убирает ровно ту долю дисперсии, которую объясняет X, а это rho**2.

Теперь тот же тест на Y_cuped:

ЭффектСтандартная ошибкаz95% интервал
Сырые траты697,59 ₽197,60 ₽3,53310-1085 ₽
CUPED812,57 ₽170,46 ₽4,77478-1147 ₽

Интервал сузился на 14%, p-value упал с 0,0004 до 0,000002. Стандартная ошибка падает как корень из числа клиентов, поэтому без CUPED для такой точности понадобилось бы в 1 / 0,748 = 1,34 раза больше клиентов. Или, наоборот, с CUPED той же точности хватает три четверти выборки. Для Фреи это и есть ответ: история клиентов работает как дополнительные клиенты в тесте.

урок из курса

Урок из курса «Аналитика данных с нуля». Курс покупается один раз и остаётся навсегда: 500 ₽.

Оплата пока не подключена: премиум и курсы открываются промокодом.