Как посчитать CUPED в SQL

Закрепи формулу cuped в Карьернике
Запомнить надолго — 5 коротких сессий с задачами на эту тему. Бесплатно
Тренировать cuped в Telegram

Зачем 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 больше → быстрее достигается значимость.

Закрепи формулу cuped в Карьернике
Запомнить надолго — 5 коротких сессий с задачами на эту тему. Бесплатно
Тренировать cuped в Telegram

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 / линейная регрессия).

Связанные темы

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 встроенные. Не требует никаких аддонов.