Условие
Лига Ставок выпустила новую версию приложения, в которой исправили проблему с заключением пари (раньше было много ошибок). Обновление не A/B: его постепенно раскатывают всем. Не выложили в сторы.
Менеджер просит проанализировать, стали ли пользователи делать больше ставок после обновления.
Дано: user_id, platform, install_date (дата установки обновления), bets_date, bets (дата и кол-во ставок).
Решение
Каркас: квази-эксперимент pre-post
A/B нет, контроль явный отсутствует. Доступные дизайны:
- Pre-post within user: для каждого пользователя сравнить bets/day за N дней до
install_dateи N дней после. - DiD-аналог: пользователи, обновившиеся раньше, vs обновившиеся позже (на одной и той же календарной дате).
- Interrupted Time Series: на агрегатном уровне (DAU bets) — сменилось ли trend после релиза.
Реализация (1 — Pre-post)
import pandas as pd
import numpy as np
df = pd.read_csv('liga_stavok.csv')
df['install_date'] = pd.to_datetime(df['install_date'])
df['bets_date'] = pd.to_datetime(df['bets_date'])
df['lt_day'] = (df['bets_date'] - df['install_date']).dt.days
# pre vs post 14 days
pre = df[(df['lt_day'] >= -14) & (df['lt_day'] < 0)].groupby('user_id')['bets'].mean()
post = df[(df['lt_day'] >= 0) & (df['lt_day'] < 14)].groupby('user_id')['bets'].mean()
paired = pd.merge(pre.rename('pre'), post.rename('post'), left_index=True, right_index=True)
paired['delta'] = paired['post'] - paired['pre']
print(paired['delta'].describe())
# paired t-test
from scipy import stats
t, p = stats.ttest_rel(paired['post'], paired['pre'])Реализация (2 — DiD-like)
Сравниваем «early adopters» (обновились в первые 7 дней раскатки) и «late adopters» (через 30+ дней). Для каждой когорты — pre-period (до релиза) и post-period (после релиза). Возможно поймать «выживший выборка» эффект, но при правильной идентификации — DiD-оценка.
Реализация (3 — ITS)
daily = df.groupby('bets_date')['bets'].sum().reset_index()
# импульсивный тест: change point detection
from ruptures import Pelt
algo = Pelt(model="rbf").fit(daily['bets'].values)
points = algo.predict(pen=10)Или линейная регрессия с break-point на release date:
bets_t = β0 + β1 * t + β2 * post + β3 * t * post + ε
β2 — сдвиг уровня в момент релиза.
Проверка / интерпретация
- Sanity: количество user-pairs > 1000 для значимости.
- Бустраппинг — устойчивее t-test (распределение ставок имеет heavy-tail).
- Сегментация: по platform (iOS/Android), новые/возвращающиеся.
Подводные камни
- Selection bias в pre-post: пользователь, который активно ставил pre, мог обновиться. Сравнение «до vs после» среди обновлённых — не каузальный эффект.
- Survivor bias: те, кто перестал ставить после установки, могут быть unobservable; те, кто продолжил — overrepresented.
- Сезонность: спортивные события (Лига Чемпионов, ЧМ) — резкий рост ставок без всякого релиза. Используйте YoY или контрольную группу.
- Привычка пользователей: пользователи 14 дней до релиза могли быть активны редко; после релиза — выросли «по совпадению» (новый сезон).
- «Постепенно отправляем»: пользователи получают update в разное время. Лучше работать с relative time
lt_day, не absolute. - Гипотеза «больше ставок»: но возможно средний размер ставки или success rate — что важнее. Уточните, что именно метрика успеха.
Что лучше всего ответить менеджеру
«Да, стало больше ставок: medio per user/day +X.XX (CI [a, b]), p < 0.05 на paired t-test. Но это не A/B, а pre-post — есть confounders (сезонность, выжившая аудитория). Чтобы убедиться, что эффект именно от bug-fix, нужно: (a) контрольная группа на сегменте, который ещё не обновился; (b) контроль на сезонности (YoY same-period); (c) расследование bug-rate в логах — упал ли в пост-периоде.»
Эталонный ответ
Pre-post within user — paired t-test или Wilcoxon на mean bets/day. Дополнительно DiD «early vs late adopters» и ITS на DAU. Главное — указать, что без A/B каузально оценить нельзя; идентифицируйте confounders (сезонность, survivor bias, постепенная раскатка).