Были ли стратегии принятия решения в последнем A/B тесте
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
# Стратегия принятия решения в A/B тесте: от планирования до развёртывания
Контекст: Онбординг для мобильного приложения
Компания: Travel booking app (20M DAU) Проблема: 35% пользователей отвалиается после первого запуска (opening day churn) Гипотеза: Добавим интерактивный tutorial вместо static slideshow
Фаза 1: Планирование и расчёт размера
Шаг 1: Определение метрик
Primary metric: Day-1 Retention (процент пользователей, вернувшихся на день 2)
- Baseline: 65%
- MDE (Minimum Detectable Effect): 2% lift → 67%
- Это 3% относительное улучшение, что имеет смысл для бизнеса
Secondary metrics:
- Day-7 Retention
- Feature discovery rate (сколько фич пользователь попробовал на день 1)
- Time spent on onboarding
- Tutorial completion rate
Guardrail metrics (не должны деградировать):
- App install time (не должна > 2 сек)
- Crash rate on day 1 (не должна > 0.5%)
Шаг 2: Расчёт sample size
Формула: n = (Z_α/2 + Z_β)² * (p1(1-p1) + p2(1-p2)) / (p2 - p1)²
Параметры:
- Baseline conversion (p1) = 0.65
- Target conversion (p2) = 0.67
- α (significance level) = 0.05 → Z_α/2 = 1.96
- β (power) = 0.20 → Z_β = 0.84
- Power = 1 - β = 0.80 (80% мощность)
Расчёт:
n = (1.96 + 0.84)² * (0.65*0.35 + 0.67*0.33) / (0.67 - 0.65)²
n = (2.80)² * (0.2275 + 0.2211) / 0.0004
n = 7.84 * 0.4486 / 0.0004
n = 8,795 пользователей в группе
Общий размер = 8,795 * 2 = 17,590 пользователей
Длительность теста = 17,590 / (20M DAU) = ~0.1% трафика
Дни = Длительность теста / (фракция трафика) = 1 день 100%, или 100 дней на 1%
Выбор: 100% трафика на 1 день, ИЛИ 1% трафика на 100 дней
Выбрал: 1% трафика на 7 дней (для покрытия недельных паттернов)
Шаг 3: Определение периода теста
Почему 7 дней?
- День недели может влиять (weekend vs weekday пользователи)
- Нужна полная неделя для среднего поведения
- Минимум 2-3 дня для статистики, но 7 дней = safe choice
Старт теста: Вторник 9 AM (среди недели) Конец теста: Вторник 9 AM (ровно неделя)
Это покрывает 2 выходных дня и нейтрализует недельные паттерны.
Фаза 2: Мониторинг во время теста
День 1 утро (6 часов после старта)
-- Быстрая проверка здоровья теста
SELECT
variant,
COUNT(DISTINCT user_id) as users,
SUM(CASE WHEN completed_onboarding = 1 THEN 1 ELSE 0 END) as completed,
ROUND(100.0 * SUM(CASE WHEN completed_onboarding = 1 THEN 1 ELSE 0 END) / COUNT(*), 2) as completion_rate,
ROUND(AVG(crash_occurred), 4) as crash_rate
FROM onboarding_events
WHERE event_date = CURRENT_DATE
AND variant IN ('control', 'treatment')
GROUP BY variant;
-- Результат после 6 часов:
variant | users | completed | completion_rate | crash_rate
----------|-------|-----------|-----------------|----------
control | 3,200 | 2,080 | 65.0% | 0.001
treatment| 3,150 | 2,150 | 68.2% | 0.003
Что проверяю:
- ✅ Распределение 50/50? Да, 3,200 vs 3,150
- ✅ Нет аномалий? Crash rate немного выше в treatment (0.3%), но приемлемо
- ⚠️ Нет раннего прерывания? Вижу +3.2% lift, но это раннее, ждём дальше
День 2 (24 часа)
-- День 1 retention (основная метрика)
SELECT
variant,
COUNT(DISTINCT CASE WHEN saw_onboarding = 1 THEN user_id END) as day0_users,
COUNT(DISTINCT CASE WHEN saw_onboarding = 1 AND returned_day1 = 1 THEN user_id END) as day1_returned,
ROUND(100.0 * COUNT(DISTINCT CASE WHEN saw_onboarding = 1 AND returned_day1 = 1 THEN user_id END) /
COUNT(DISTINCT CASE WHEN saw_onboarding = 1 THEN user_id END), 2) as day1_retention
FROM users_daily_activity
WHERE day0_date = CURRENT_DATE - INTERVAL '1 day'
AND variant IN ('control', 'treatment')
GROUP BY variant;
-- Результат:
variant | day0_users | day1_returned | day1_retention
----------|-----------|---------------|---------------
control | 3,200 | 2,080 | 65.0%
treatment| 3,150 | 2,162 | 68.6%
Интерпретация:
- Lift: +3.6 процентных пункта (от 65.0% до 68.6%)
- Это +5.5% относительный lift
- Но это после 24 часов, ещё не поздно? Нет, нужно ждать конца недели
День 4 (точка "раннего остановления")
Статистический тест:
from scipy.stats import chi2_contingency
# День 4 результаты
control_returned = 2,080
control_not_returned = 1,120
treatment_returned = 2,162
treatment_not_returned = 988
# Chi-square тест
contingency_table = [
[control_returned, control_not_returned],
[treatment_returned, treatment_not_returned]
]
chi2, p_value, dof, expected = chi2_contingency(contingency_table)
# p_value = 0.0031 < 0.05 ✅ СТАТИСТИЧЕСКИ ЗНАЧИМА!
В этот момент я встречаюсь с Product Lead:
"У нас есть результаты. Day-1 retention улучшился на 3.6pp (от 65% к 68.6%), p-value = 0.003 (значима). Но у меня есть 2 вопроса перед тем как дать go/no-go:
- Guardrail метрики в норме? Crash rate немного выше (0.3% vs 0.1%), но в пределах приемлемо
- Как выглядит вторая неделя? Ждём день 7 для Day-7 Retention?"
Product Lead говорит: "Аналитик, это важное улучшение. Можем ли мы развернуть раньше?"
Фаза 3: Стратегия принятия решения
Мой фреймворк для этого вопроса
Есть 3 стратегии принятия решения:
Стратегия 1: Fixed Horizon (Фиксированный период) — ПРАВИЛЬНО в нашем случае
Определяем период заранее (7 дней)
Не останавливаем даже если результаты "очевидны" на день 2
Почему это работает:
- Избегаем selection bias (ранние adopters != весь трафик)
- Покрываем день-недельные паттерны
- Избегаем ошибки первого рода (false positive)
Моё мнение: "Рекомендую Fixed Horizon. Вот почему:
- Selection bias. День 1-4 пользователи - это ранние adopters, они более engaged независимо от фичи
- Power. Мы рассчитали мощность на 7 дней. Прерывание на день 4 снижает мощность
- Guardrail. Crash rate повышена. Нужно видеть долгосрочный тренд
- Регрессия к среднему. Может быть флуктуация, день 5-7 покажет правду"
Стратегия 2: Sequential Testing (Sequential analysis) - ЕСЛИ бы нужно было быстрее
Мониторим p-value каждый день
Если p-value < 0.005 (более строгий порог) - можем остановить
Почему это работает:
- Позволяет остановить очень очевидные случаи
- Использует sequential probability ratio test (SPRT)
- Контролирует family-wise error rate
Эта стратегия для срочных случаев, но требует math expertise.
Стратегия 3: Adaptive Horizon - НИКОГДА
Смотрим на результаты, потом решаем как долго гонять
❌ НИКОГДА так не делай - это p-hacking
Мой вывод для Product Lead
"Вот моя рекомендация:
Вариант 1 (Мой выбор): Жди 7 дней. Это стандартный подход, защищает от ошибок.
Вариант 2: Если очень срочно - можем использовать Sequential Testing (нужен более строгий p-value < 0.005). Это даст нам возможность остановить на день 3-4, но требует более высокого уровня значимости.
Вариант 3: Если просто не терпится - можем сделать так: продолжаем тест 7 дней, но с раннего прерывания на день 4, если p-value < 0.01. Это компромисс.
Советую выбрать Вариант 1, но выбор за вами."
Фаза 4: Итоговое решение (День 7)
День 7 результаты
-- День 7 результаты (полная неделя)
SELECT
variant,
COUNT(DISTINCT user_id) as cohort_size,
-- День 1 retention
ROUND(100.0 * SUM(CASE WHEN day1_opened = 1 THEN 1 ELSE 0 END) / COUNT(*), 2) as day1_ret,
-- День 7 retention
ROUND(100.0 * SUM(CASE WHEN day7_opened = 1 THEN 1 ELSE 0 END) / COUNT(*), 2) as day7_ret,
-- Feature adoption
ROUND(AVG(features_used), 1) as avg_features,
-- Engagement
ROUND(AVG(session_time_mins), 0) as avg_session_time
FROM onboarding_cohort
WHERE cohort_date = CURRENT_DATE - INTERVAL '7 days'
GROUP BY variant;
-- Результаты:
variant | cohort_size | day1_ret | day7_ret | avg_features | avg_session_time
----------|------------|----------|----------|------------|----------------
control | 3,200 | 65.0% | 42.1% | 3.2 | 18
treatment| 3,150 | 68.6% | 45.3% | 4.8 | 22
Статистические тесты
Day-1 Retention:
- Control: 65.0% (2,080 / 3,200)
- Treatment: 68.6% (2,161 / 3,150)
- Difference: +3.6pp
- Chi-square p-value: 0.0031 ✅ ЗНАЧИМА
- 95% CI: [1.2%, 6.0%]
Day-7 Retention:
- Control: 42.1% (1,347 / 3,200)
- Treatment: 45.3% (1,427 / 3,150)
- Difference: +3.2pp
- Chi-square p-value: 0.0087 ✅ ЗНАЧИМА
- 95% CI: [0.8%, 5.6%]
Guardrail Metrics:
✅ Crash rate: control 0.10%, treatment 0.11% (OK)
✅ App load time: control 1.2s, treatment 1.2s (OK)
Итоговое решение
Критерии для GO:
- ✅ Primary metric улучшена: +3.6% на day-1, +3.2% на day-7
- ✅ Статистически значима: p < 0.05 для обеих
- ✅ Guardrail метрики не деградировали
- ✅ Secondary metrics улучшены: +50% feature discovery (3.2 → 4.8)
- ✅ Business impact: +3.6pp on 20M DAU = 720K дополнительных активных пользователей
РЕШЕНИЕ: GO - развёртываем фичу
План развёртывания
Д-1: Code review и подготовка
Д-0: 50% трафика (1 час, монитор)
Д-1: 100% трафика на мобильных платформах
Д+7: Проверка метрик в боевых условиях
Д+30: Итоговый анализ настоящего impact
Фаза 5: Post-launch мониторинг
День 1 после развёртывания (100%)
-- Проверка что нет неожиданных проблем
SELECT
DATE(event_time) as date,
COUNT(DISTINCT user_id) as users,
ROUND(100.0 * SUM(CASE WHEN crash = 1 THEN 1 ELSE 0 END) / COUNT(*), 3) as crash_rate,
ROUND(AVG(session_time), 0) as avg_session_time
FROM events
WHERE event_date = CURRENT_DATE
GROUP BY DATE(event_time);
-- Если crash_rate вдруг > 0.5% - отката!
Итог: Моя стратегия принятия решения
Ключевые принципы
- Fixed Horizon. Определяю период заранее, не меняю
- Multiple metrics. Смотрю не только primary, но и guardrails
- Statistical rigor. Требую p < 0.05, не раньше
- Business context. Не только статистика, но и реальный impact
- Post-launch monitoring. После развёртывания проверяю реальные результаты
Почему это работает
- Избегаю selection bias
- Не переоцениваю ранние результаты
- Защищаю компанию от ошибок первого рода
- Обеспечиваю confidence в развёртываемые фичи