Условие
В проекте Fancy Blast (Match-3) проведён A/B-тест:
- Когорта A — контрольная.
- Когорта B — время восстановления одной жизни уменьшено в 10 раз (с 30 мин до 3 мин).
Данные: до 7 дней жизни пользователя (день установки = day 0):
Retention— день жизни пользователя;MaxLevelPassed— макс. пройденный уровень;User_id,AB_Cohort;SumRevenue,CountBuy;CountAllStart/CountAllFinish— старты и победы на уровнях;CountCleanStart/CountCleanFinish— без помощи (без буста, ходов, бонусов);Get_*— золото из разных источников (Ads, Chapter, Buy, Faceb, TeamL, TeamT);Spend_*— траты золота (BonLives, Bonus, Boost, Lives, Moves, TeamC).
Задача: проанализировать, как изменение времени восстановления жизней повлияло на метрики проекта.
Решение
Подход
Уменьшение времени восстановления жизней — фундаментальное изменение gating-механики F2P игры. Жизни — это и retention-tool (заставляет вернуться в игру), и monetization-tool (купи жизни / буст). Уменьшение в 10 раз должно дать:
Положительные эффекты (ожидаемо):
- Sessions per DAU вырастут — юзер не «уходит на 30 мин».
- Time-in-app per session вырастет / number of sessions per day вырастет.
- Прогрессия по уровням ускорится (CountAllStart, MaxLevelPassed).
- Возможно, retention вырастет (проще вернуться, проще играть).
Отрицательные эффекты (риск):
- Spend_Lives упадёт — никто не купит жизни, если они почти бесплатные.
- Revenue в нижней категории платежей просядет.
- MaxLevelPassed может вырасти, и юзеры пройдут весь контент быстрее → выгорят раньше.
- Sessions per day может вырасти, но сессии стать короче (микросессии вместо «длинных»).
Реализация — план анализа
import pandas as pd
import numpy as np
from scipy import stats
df = pd.read_excel("TZ_data.xlsx", sheet_name="Лист1")
# 1. Структура: одна строка на (user, day). Считаем сводные на уровне юзера.
agg_user = df.groupby(["User_id", "AB_Cohort"]).agg(
days_active = ("Retention", "count"),
max_level = ("MaxLevelPassed", "max"),
total_revenue = ("SumRevenue", "sum"),
n_purchases = ("CountBuy", "sum"),
starts = ("CountAllStart", "sum"),
finishes = ("CountAllFinish", "sum"),
clean_starts = ("CountCleanStart", "sum"),
clean_finishes = ("CountCleanFinish", "sum"),
spent_lives_gold = ("Spend_Lives", "sum"),
spent_moves_gold = ("Spend_Moves", "sum"),
spent_boost_gold = ("Spend_Boost", "sum"),
earned_ads = ("Get_Ads", "sum"),
).reset_index()
agg_user["completion_rate"] = agg_user["finishes"] / agg_user["starts"].replace(0, np.nan)
agg_user["clean_completion_rate"] = agg_user["clean_finishes"] / agg_user["clean_starts"].replace(0, np.nan)
agg_user["arpu"] = agg_user["total_revenue"]Ключевые метрики и сравнение групп
def compare(metric, df=agg_user):
a = df.query("AB_Cohort == 'A'")[metric].dropna()
b = df.query("AB_Cohort == 'B'")[metric].dropna()
# Welch t-test (для бинарных метрик — z-test/chi2)
t, p = stats.ttest_ind(a, b, equal_var=False)
return {
"A": a.mean(), "B": b.mean(),
"lift_pct": (b.mean() / a.mean() - 1) * 100 if a.mean() else None,
"p_value": p
}
metrics = ["days_active", "max_level", "total_revenue", "n_purchases",
"starts", "completion_rate", "spent_lives_gold", "spent_moves_gold",
"spent_boost_gold", "earned_ads"]
results = {m: compare(m) for m in metrics}
print(pd.DataFrame(results).T)Анализ / интерпретация — типовой результат
| Метрика | Ожидание | Возможный реальный эффект |
|---|---|---|
days_active (retention) |
вырастет в B | +5–10% (положительно) |
max_level |
вырастет в B | +20–30% (быстрее проходят) |
starts (попыток) |
сильно вырастет в B | +50–100% (можно играть всегда) |
completion_rate |
не должна меняться | стабильна |
total_revenue |
спорно | возможна просадка на 10–20% |
n_purchases |
спорно | просадка из-за отсутствия покупки жизней |
spent_lives_gold |
резко упадёт | -80% (главный негатив) |
spent_moves_gold |
вырастет | +10–20% (играют чаще, чаще тратят на ходы) |
earned_ads |
вырастет | +20% (больше сессий → больше реклам) |
Возможный итоговый вывод:
Сокращение времени восстановления жизней дало положительный эффект на engagement (сессии, уровни, retention), но значительно просадило монетизацию на покупке жизней. Net effect на total revenue — отрицательный (–15%), потому что недополученные деньги от Spend_Lives не компенсируются ростом игроков на других статьях. Рекомендация: не раскатывать прямолинейно. Альтернативы: уменьшить время до 10–15 минут (компромисс), или одновременно повысить cap жизней с 5 до 10 (engagement без потери монетизации), или повысить цены других предметов.
Дополнительный анализ
- Cohort retention по дням 0–7 для A и B — линейный график.
- Funnel «sessions per day» distribution — как изменилось распределение числа сессий.
- Сегментирование по платящим: отдельно платящие и нет. Эффект может быть ассиметричным.
- Распределение
MaxLevelPassed— гистограмма. Если в B сильно сдвинулась вправо, юзеры скоро столкнутся с «потолком» контента. - Earn vs Spend gold balance — в B может быть инфляция золота из-за простоты.
Подводные камни
- Average revenue в gamedev. Среднее искажается китами — используйте median/trimmed mean или bootstrap.
- Нулевые юзеры в выборке: с retention=0 и Max=0 — это инсталлы, которые ни разу не сыграли. Их учитывать или нет — критично для метрики.
- Корреляция ретеншна и монетизации. Часто игроки, которые вернулись на день 7, — это «киты» из чарта первых дней. Не путайте «B даёт больше retention → значит, должно быть больше revenue» — пропорция платящих может упасть.
CountClean*vsCountAll*. Clean — без буста, All — со всеми попытками. Сравнивать нужно сопоставимое: CR на clean vs CR на all.- «7 дней» — мало для long-term retention. Эффект может стать ясным только на дне 30+.
- Sample Ratio Mismatch. Проверьте, что A и B сопоставимы по числу юзеров, дате установки.
- Confounders: если B запустили в другой период (другой UA, другой trafic mix), эффект смешан.
- Survivor bias в metric
MaxLevelPassed. Юзер, ушедший на дне 1, имеет MaxLevel = MaxLevel дня 1 — навечно. В среднем не отражает «динамику», нужно «MaxLevel на дне X».
Альтернативы
- CUPED на pre-period не применим (юзеры новые).
- Difference-in-differences не нужен — A/B на новых.
- Bootstrap на уровне юзера — для CI на ARPU/LTV.
Эталонный ответ
Структура отчёта:
- Sanity check: размеры групп, sample-ratio.
- Engagement metrics: sessions, days_active, max_level, starts. Ожидаем рост в B.
- Monetization metrics: revenue, n_purchases, spent_lives. Ожидаем просадку в B (особенно
spent_lives). - Net effect на revenue — главное число для бизнеса.
- Сегментный анализ: платящие vs нет.
- Рекомендация: компромиссная конфигурация (например, 15 мин вместо 3) или раскатать с одновременным повышением cap жизней.
Главное — не смотреть только на retention: уменьшение жизневременного gate почти всегда улучшает retention и почти всегда ухудшает monetization. Net effect — индивидуален для проекта.