Условие
В файле data_1.csv — данные о боях двух групп игроков, разделённых по доступности изменений в настройках/конфигурации:
- Group A — изменения доступны с
2022-10-27по2022-11-29. - Group B — контрольная, изменения не доступны.
Период данных: 2022-10-01 — 2022-11-29 (т.е. ~4 недели до изменений и 5 недель после).
Поля:
player_id,battle_id,dt— день боя;player_group(A или B, не меняется в течение периода);in_battle_presence_time— секунды в бою;damage_dealt— урон;kills_made— уничтоженные танки;vehicle_lvl— уровень техники;account_created_at— дата создания аккаунта.
Задача: проанализируйте перформанс изменений и выводы для бизнеса.
Решение
Подход
Это A/B-тест с pre-period (4 недели «до» + 5 недель «после»). Идеальная ситуация для:
- Проверки эквивалентности групп в pre-period (sanity check).
- DiD (difference-in-differences) для оценки эффекта.
- Сегментного анализа (разные техники, разные «возрасты» аккаунтов реагируют по-разному).
Реализация — план анализа
Шаг 1. Sanity-check групп
import pandas as pd
import numpy as np
from scipy import stats
df = pd.read_csv("data_1.csv", parse_dates=["dt", "account_created_at"])
df["period"] = np.where(df["dt"] >= "2022-10-27", "post", "pre")
# Размеры групп
print(df.groupby("player_group")["player_id"].nunique())
# Распределение vehicle_lvl, account_age в группах в pre-period
pre = df[df["period"] == "pre"]
ks = stats.ks_2samp(
pre[pre["player_group"] == "A"]["vehicle_lvl"],
pre[pre["player_group"] == "B"]["vehicle_lvl"]
)
print(f"KS for vehicle_lvl: p = {ks.pvalue:.3f}")Если в pre-period группы значимо разные — нельзя просто сравнивать «А vs Б после изменений», нужны DiD или CUPED.
Шаг 2. Ключевые метрики
| Метрика | Что показывает |
|---|---|
| Battles per player per day | engagement |
| Damage per battle | core gameplay performance |
| Kills per battle | агрессивность игры |
| In-battle time per session | продолжительность вовлечения |
| DAU | retention |
| Win rate (если есть) | баланс игры |
Шаг 3. Difference-in-differences
metric = "damage_dealt"
# Усредняем по игроку и периоду
agg = (df.groupby(["player_id", "player_group", "period"])
[metric].mean()
.reset_index())
# Pivot: pre / post
wide = agg.pivot_table(index=["player_id", "player_group"],
columns="period", values=metric).dropna().reset_index()
wide["delta"] = wide["post"] - wide["pre"]
# Сравниваем delta между группами
deltaA = wide[wide["player_group"] == "A"]["delta"]
deltaB = wide[wide["player_group"] == "B"]["delta"]
t_stat, p = stats.ttest_ind(deltaA, deltaB, equal_var=False)
print(f"DiD effect on {metric}: A={deltaA.mean():.2f}, B={deltaB.mean():.2f}, p={p:.4f}")DiD устраняет систематические различия между группами и общий тренд во времени.
Шаг 4. Сегментный анализ
Эффект может различаться для:
- Новых vs опытных игроков (
account_age = dt - account_created_at). - Разных уровней техники (low-tier vs mid-tier vs top-tier).
- «Тяжёлых» vs «лёгких» игроков (по числу боёв в pre-period).
df["account_age_days"] = (df["dt"] - df["account_created_at"]).dt.days
df["account_seg"] = pd.cut(df["account_age_days"],
bins=[-1, 30, 365, np.inf],
labels=["new", "active", "veteran"])
# Эффект по сегментам
for seg in df["account_seg"].cat.categories:
sub = df[df["account_seg"] == seg]
# ... повторить DiD ...Шаг 5. Визуализация
- Time-series по дням, две линии (A vs B), вертикальная линия — момент включения изменений. Видно, отскочила ли А вверх после
2022-10-27. - Распределения «delta» по игрокам в каждой группе — гистограмма / boxplot.
- Heatmap по сегментам: строки — сегменты, столбцы — метрики, ячейки — лифт A vs B.
Анализ / интерпретация
В отчёте для бизнеса:
Изменения повысили урон за бой на X% в группе A статзначимо (p = 0.001, 95% CI [Y; Z]). Эффект концентрирован в сегменте «активных» игроков (1 мес — 1 год) — у них рост Q%, у новых и ветеранов рост незначим. Drop-off по DAU не зафиксирован (гард). Рекомендация: раскатывать на 100% игроков с дополнительным мониторингом сегмента «новых» — для них эффект незначим, и важно убедиться, что фича не ухудшит их опыт.
Подводные камни
- Сравнивать post-period напрямую без проверки pre-эквивалентности — частая ошибка. Используйте DiD или CUPED.
- Многократное тестирование. На 5+ метриках с уровнем
α=0.05можно получить ложные срабатывания. Поправка Бонферрони / BH. - Корреляция внутри игрока. Один игрок участвует в десятках боёв → нужно усреднять до уровня игрока перед t-test, иначе nominal p-value занижен.
- Сегмент «новых» игроков. В pre-period они могли только зарегистрироваться → данных мало, дисперсия большая.
- Survivor bias. В post-period могут отсутствовать игроки, ушедшие из игры до изменений — это часть эффекта (или нет?).
- Выбросы. «Bot detection» через очень высокие damage / battles per day. Винзоризация по 99%-перцентилю или фильтр аномалий.
- Дисбаланс типов техники. Если в группе A случайно больше игроков на low-tier, средний урон будет ниже — нужен страт или контроль на vehicle_lvl.
Альтернативы
- CUPED с pre-period как ковариантой — снижает дисперсию эффекта.
- Bootstrap на уровне игрока для непараметрических CI.
- Mixed-effects model (lme4 / pymer4) — учитывает random effect игрока.
Эталонный ответ
Структура отчёта:
- Sanity-check групп в pre-period.
- DiD по 4–5 ключевым метрикам (battles per player, damage per battle, kills, time-in-battle, DAU).
- Сегментный анализ (новые / активные / ветераны, разные tier).
- Визуализации: time-series A vs B с маркером запуска, гистограмма deltas, heatmap по сегментам.
- Бизнес-вывод с рекомендацией (раскатать / не раскатать / докрутить) и условиями (мониторинг сегмента X, гарды).