Условие
После запуска push-уведомлений conversion рос на 3 п.п. Менеджер говорит «push дал +3 п.п.». Что не так?
Решение
Проблема: наблюдательная разница ≠ causal эффект
Те, кто получил push, уже были более вовлечены (они открыли уведомления = signal). Без push они тоже купили бы — мы измерили корреляцию, а не incremental lift.
Правильный замер — holdout
| Группа | Действие |
|---|---|
| Test (90% базы) | получают push |
| Holdout (10% базы) | не получают push |
Conversion lift = conv(test) − conv(holdout)
= 8% − 5% = 3 п.п. (causal)
Юзеры в test и holdout схожи (рандомизация), поэтому разница = чистый эффект push.
Если holdout сделать нельзя
- Geo-holdout: один регион получает, другой нет, сравнить тренды.
- Synthetic control: модельный контроль по похожим регионам/юзерам.
- Difference-in-differences: до/после × treat/control.
- Propensity score matching: подобрать в контроле «таких же» юзеров.
Размер holdout
10% обычно достаточно для крупных продуктов. Стоимость holdout = недополученная выручка от 10% юзеров. Чем дороже изменение, тем больший holdout оправдан.
Long-term holdout
Для повторных каналов (email, push) — постоянный 5% holdout. Иначе через год не сможете оценить, действительно ли email-канал нужен.
SQL
WITH groups AS (
SELECT user_id,
MOD(ABS(HASHTEXT(user_id::text)), 100) < 10 AS in_holdout
FROM users
)
SELECT
in_holdout,
AVG(converted::int) AS conv,
COUNT(*) AS n
FROM users JOIN groups USING (user_id)
GROUP BY in_holdout;Подводные камни
- Self-selection: «открыл push» — это уже сигнал интереса. Меряйте отправку, а не открытие.
- Holdout без рандомизации = bias. Хеш user_id даёт стабильный random.
- Холдаут «leak» — если канал делится с другими (например, email + push на одного user_id), нужно консистентное распределение.
- Test-control contamination: пуш «вирально» обсуждаемый — холдаут видит через соц.сети. Не критично для бытовых пушей, критично для громких релизов.
- Маленький holdout (<1%) даёт большую неопределённость в lift — лучше 5-10%.
Эталонный ответ
Causal lift меряется через holdout: conv(treat) − conv(control) на рандомизированных группах. Наблюдательная разница смещена self-selection (открыл push = уже мотивирован).