CUPED: вариант-редукция через pre-experiment данные

Senior A/B-тесты product

Условие задания

**Контекст:** Senior-собес в ML/growth-команду (Яндекс, Ozon, Авито). CUPED — метод уменьшения дисперсии метрики A/B-теста за счёт использования данных пользователя ДО эксперимента.

**Задача:** реализовать CUPED и сравнить с обычным t-test на симулированных данных.

**Данные:**

[см. код в задании]

**Задание:**

1. Сделать обычный t-test на `y_control` vs `y_treatment` — получить p-value и CI.
2. Применить CUPED: вычислить theta, посчитать скорректированные Y_cuped, снова t-test.
3. Показать во сколько раз уменьшилась дисперсия и уменьшился CI.

**Ожидаемый вывод:**
- Без CUPED: CI ±1.2, p=0.001
- С CUPED: CI ±0.6, p<0.0001 (при той же выборке значимость выше)

Пример данных

Структура для ориентира — реальные значения из эталонного решения.

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

def cuped_correction(y: np.ndarray, x: np.ndarray, theta: float) -> np.ndarray:
    """
    CUPED correction: Y_cuped = Y - theta * (X - X.mean())
    """
    return y - theta * (x - x.mean())


def analyze_ab(y_ctrl, y_trt, x_ctrl=None, x_trt=None, label='') -> dict:
    """
    Обычный t-test или CUPED t-test.
    """
    if x_ctrl is not None:
        # Считаем theta на объединённых данных — более точно
        y_all = np.concatenate([y_ctrl, y_trt])
        x_all = np.concatenate([x_ctrl, x_trt])
        theta = np.cov(y_all, x_all, ddof=1)[0, 1] / np.var(x_all, ddof=1)

        # Применяем correction
        y_ctrl_adj = cuped_correction(y_ctrl, x_ctrl, theta)
        y_trt_adj = cuped_correction(y_trt, x_trt, theta)
    else:
        y_ctrl_adj, y_trt_adj = y_ctrl, y_trt
        theta = None

    # t-test
    t_stat, p_value = ttest_ind(y_trt_adj, y_ctrl_adj, equal_var=False)

    # Средние и эффект
    mean_diff = y_trt_adj.mean() - y_ctrl_adj.mean()
    se = np.sqrt(y_trt_adj.var(ddof=1) / len(y_trt_adj) +
                 y_ctrl_adj.var(ddof=1) / len(y_ctrl_adj))
    ci_low = mean_diff - 1.96 * se
    ci_high = mean_diff + 1.96 * se

    # Дисперсия разности средних
    variance = se ** 2

    return {
        'label': label,
        'mean_diff': mean_diff,
        'p_value': p_value,
        'ci_low': ci_low,
        'ci_high': ci_high,
        'ci_half_width': 1.96 * se,
        'variance': variance,
        'theta': theta,
    }


# ───── Запуск на симулированных данных ─────
np.random.seed(42)
n = 10_000

base_level = np.random.normal(100, 30, n)
pre_metric = base_level + np.random.normal(0, 20, n)
post_metric_control = base_level + np.random.normal(0, 20, n)
post_metric_treatment = base_level + np.random.normal(0, 20, n) + 3

is_treatment = np.random.binomial(1, 0.5, n).astype(bool)
y_control = post_metric_control[~is_treatment]
y_treatment = post_metric_treatment[is_treatment]
x_control = pre_metric[~is_treatment]
x_treatment = pre_metric[is_treatment]

# Без CUPED
r1 = analyze_ab(y_control, y_treatment, label='Plain t-test')
# С CUPED
r2 = analyze_ab(y_control, y_treatment, x_control, x_treatment, label='CUPED')

# Сравнение
print(pd.DataFrame([r1, r2])[['label', 'mean_diff', 'ci_half_width', 'p_value', 'variance']])

# Variance reduction
vr = 1 - r2['variance'] / r1['variance']
print(f"\nVariance reduction: {vr*100:.1f}%")
print(f"Theta: {r2['theta']:.3f}")

# Корреляция между pre и post (объясняет redukция)
rho = np.corrcoef(np.concatenate([pre_metric]), np.concatenate([post_metric_control]))[0, 1]
print(f"ρ(pre, post) = {rho:.3f}")
print(f"Теоретическая redukция: {(1 - (1 - rho**2))*100:.1f}% = {rho**2*100:.1f}%")

Темы

cuped variance-reduction ab-test python statsmodels

Подсказки

Все тестовые задания →

Частые вопросы

Какой уровень знаний нужен для задачи "CUPED: вариант-редукция через pre-experiment данные"?

Это задание для уровня Senior. Senior-уровень — глубокое понимание темы, опыт решения нестандартных задач, обсуждение trade-off на собеседовании.

На каких собеседованиях встречается такая задача?

Подобные задания в категории «A/B-тесты» регулярно дают на собеседованиях аналитика данных в Яндекс, Сбер, Ozon, Авито, Тинькофф, Wildberries, T-Bank, X5, ВТБ и других крупных IT-компаниях. Тематика: cuped, variance-reduction, ab-test, python, statsmodels.

Сколько времени даётся на решение?

На реальном собеседовании на подобную задачу отводится 30-60 минут с обсуждением подходов, оптимизаций и trade-off. Для тренировки рекомендуем сначала решить самостоятельно, потом сверить с эталонным решением и подсказками.

Где ещё потренироваться по теме «A/B-тесты»?

На zasqlpython.ru есть другие задания в категории «A/B-тесты», продуктовые кейсы, справочник метрик, AI мок-собеседование с разбором ваших ответов.

← Все задания