Как посчитать CUPED в SQL
Содержание:
Зачем CUPED
CUPED (Controlled-experiment Using Pre-Experiment Data) — техника снижения дисперсии в A/B-тесте за счёт исторической ковариаты. Реальный эффект: тот же MDE достигается на 20–50% меньшей выборке или быстрее по времени. Microsoft опубликовали в 2013, с тех пор стандарт в продуктовой A/B-аналитике.
Идея ковариаты
Если у юзера была revenue до эксперимента (X), и текущая метрика — revenue в эксперименте (Y), они коррелируют. Корректируем Y:
Y_cuped = Y − θ × (X − mean(X))где θ = cov(X, Y) / var(X). Дисперсия Y_cuped меньше, чем у Y, на (1 − ρ²) × var(Y), где ρ — корреляция X и Y.
CUPED в SQL
WITH base AS (
SELECT
u.user_id,
u.variant,
u.revenue_pre AS x, -- ковариата: revenue за 30 дней ДО эксперимента
COALESCE(SUM(o.amount), 0) AS y -- метрика: revenue в эксперименте
FROM ab_users u
LEFT JOIN orders o
ON o.user_id = u.user_id
AND o.created_at >= u.bucket_at
WHERE u.experiment_id = 'paywall_v3'
GROUP BY u.user_id, u.variant, u.revenue_pre
),
theta_calc AS (
SELECT
COVAR_POP(y, x) / NULLIF(VAR_POP(x), 0) AS theta,
AVG(x) AS mean_x
FROM base
),
adjusted AS (
SELECT
b.variant,
b.y,
b.y - t.theta * (b.x - t.mean_x) AS y_cuped
FROM base b
CROSS JOIN theta_calc t
)
SELECT
variant,
AVG(y) AS mean_y_raw,
AVG(y_cuped) AS mean_y_cuped,
VAR_SAMP(y) AS var_y_raw,
VAR_SAMP(y_cuped) AS var_y_cuped,
1 - VAR_SAMP(y_cuped) / NULLIF(VAR_SAMP(y), 0) AS variance_reduction
FROM adjusted
GROUP BY variant;Mean остаётся прежним (ковариата центрирована), а variance падает.
Adjusted t-test
После CUPED считаем стандартный Welch's t-test на y_cuped вместо y:
WITH cuped_stats AS (
SELECT
variant,
COUNT(*) AS n,
AVG(y_cuped) AS mean,
VAR_SAMP(y_cuped) AS variance
FROM adjusted
GROUP BY variant
),
pair AS (
SELECT
MAX(CASE WHEN variant = 'A' THEN n END) AS n_a,
MAX(CASE WHEN variant = 'A' THEN mean END) AS mean_a,
MAX(CASE WHEN variant = 'A' THEN variance END) AS var_a,
MAX(CASE WHEN variant = 'B' THEN n END) AS n_b,
MAX(CASE WHEN variant = 'B' THEN mean END) AS mean_b,
MAX(CASE WHEN variant = 'B' THEN variance END) AS var_b
FROM cuped_stats
)
SELECT
mean_b - mean_a AS diff_cuped,
(mean_b - mean_a) / SQRT(var_a / n_a + var_b / n_b) AS t_statistic_cuped
FROM pair;Та же логика, что в t-test, но дисперсии после CUPED ниже → t-stat больше → быстрее достигается значимость.
Variance reduction в процентах
SELECT
(1 - VAR_SAMP(y_cuped) / NULLIF(VAR_SAMP(y), 0)) * 100 AS variance_reduction_pct
FROM adjusted;Типичное значение для revenue в продукте — 20–40%, для conversion почти 0 (бинарные метрики плохо снижаются ковариатой).
Частые ошибки
Ошибка 1. Ковариата из периода эксперимента.
Если X собран после bucket_at — это leakage, нарушение независимости. Берите строго pre-experiment.
Ошибка 2. Считать theta в каждой группе отдельно. Theta должна оцениваться на pooled данных (обе группы вместе), иначе оценка смещена.
Ошибка 3. CUPED на binary-метриках. Для конверсии корреляция с pre-period покупками близка к 0 — variance reduction почти не работает. CUPED имеет смысл для continuous метрик.
Ошибка 4. Новые юзеры без истории. Pre-period revenue = 0 для всех новичков → не помогает. Для cohort-based экспериментов CUPED иногда теряет силу.
Ошибка 5. Несколько ковариат сразу. Базовый CUPED — одна ковариата. Если хочется несколько — это уже OLS с регрессорами (CUPAC / линейная регрессия).
Связанные темы
- Как посчитать t-test в SQL
- Как посчитать sample size в SQL
- CUPED простыми словами
- CUPED: снижение дисперсии в A/B
FAQ
Какая ковариата подойдёт?
Та, что коррелирует с метрикой. Для revenue — pre-period revenue. Для retention — pre-period activity.
Variance reduction 20% — это много?
Снижение n на 36% при том же MDE. Серьёзно ускоряет эксперимент.
CUPED меняет точечную оценку?
Нет, mean остаётся прежним. Только variance падает → CI уже.
Что вместо theta для нескольких ковариат?
Линейная регрессия на pre-period фичах — называется CUPAC.
Можно ли в Postgres без extension?
Да, COVAR_POP и VAR_POP встроенные. Не требует никаких аддонов.