Собесов

World of Tanks — прогноз DAU на 3 месяца вперёд

ML / Data ScienceПрогнозирование временных рядовСредняяMiddle

Условие

В файле data_2.csv — данные о числе активных игроков за период 2016–2017:

  • DAY_ID — дата;
  • DAU — Daily Active Users.

Постройте прогноз DAU на 3 месяца 2018 года (январь — март). Обоснуйте методику и подход.

Решение

Подход

DAU live-сервиса имеет несколько типичных компонент:

  1. Тренд — медленное снижение/рост.
  2. Недельная сезонность — выходные обычно выше будней.
  3. Годовая сезонность — лето просадка, зима подъём (для PC-онлайн-игр).
  4. События / релизы — резкие пики и провалы (новогодний BP, релиз патча).
  5. Внешние шоки — новости, конкуренты, аутаджи.

Для прогноза на 90 дней с горизонтом 2 года истории подходят:

  • Prophet — лучший из коробки для бизнес-данных с выраженной сезонностью.
  • SARIMA — классический, требует careful diagnostics.
  • Декомпозиция STL + ARIMA на residuals — гибкий вариант.
  • Ensemble (Prophet + SARIMA, медиана прогнозов) — снижает дисперсию.

Реализация

EDA

import pandas as pd
import matplotlib.pyplot as plt
from statsmodels.tsa.seasonal import STL
 
df = pd.read_csv("data_2.csv", parse_dates=["DAY_ID"]).rename(
    columns={"DAY_ID": "ds", "DAU": "y"})
df = df.sort_values("ds").reset_index(drop=True)
 
# Базовая визуализация
fig, ax = plt.subplots(figsize=(14, 4))
ax.plot(df["ds"], df["y"])
plt.title("DAU 2016-2017")
 
# Декомпозиция STL (period=7 для недельной)
res_week = STL(df.set_index("ds")["y"], period=7).fit()
res_year = STL(df.set_index("ds")["y"], period=365).fit()

Из EDA узнаём:

  • Размер недельной сезонности (часто ±10–15% от среднего).
  • Размер годовой сезонности (часто ±20%).
  • Наличие пиков (например, новогодний период).
  • Тренд (растущий / падающий / стабильный).

Подход 1. Prophet с праздниками и оверрайдами

from prophet import Prophet
 
m = Prophet(
    yearly_seasonality=True,
    weekly_seasonality=True,
    daily_seasonality=False,
    changepoint_prior_scale=0.05,
)
 
# Праздники РФ — частая причина пиков
holidays_ru = pd.DataFrame({
    "holiday": "ru_newyear",
    "ds": pd.to_datetime(["2017-01-01", "2017-01-02", "2018-01-01", "2018-01-02"]),
    "lower_window": 0, "upper_window": 7
})
m.holidays = holidays_ru
 
m.fit(df)
 
future = m.make_future_dataframe(periods=90, freq="D")
fcst = m.predict(future)
 
# Картинка
fig = m.plot(fcst); fig = m.plot_components(fcst)

Подход 2. SARIMA как baseline

from statsmodels.tsa.statespace.sarimax import SARIMAX
m = SARIMAX(df.set_index("ds")["y"],
            order=(1, 1, 1), seasonal_order=(1, 1, 1, 7))
fit = m.fit(disp=False)
fcst_sarima = fit.get_forecast(steps=90)

Годовая сезонность в SARIMA задаётся seasonal_order=(P, D, Q, 365) — но это тяжело учесть в pure SARIMA; обычно либо детрендируем «вручную» (вычитая STL-yearly), либо используем Prophet.

Валидация

Backtest на последних 90 днях 2017:

train = df[df["ds"] < "2017-10-01"]
test  = df[df["ds"] >= "2017-10-01"]
 
m = Prophet(yearly_seasonality=True, weekly_seasonality=True)
m.fit(train)
fcst = m.predict(test[["ds"]])
 
import numpy as np
mape = np.mean(np.abs((test["y"].values - fcst["yhat"].values) / test["y"].values)) * 100
print(f"MAPE: {mape:.2f}%")

Хорошее значение для DAU live-сервиса: MAPE 5–10% на 90-дневном горизонте.

Анализ / интерпретация

В отчёте:

  • Прогноз с CI (yhat_lower, yhat_upper).
  • Декомпозиция: тренд (стабилен/спадает), недельная сезонность (выходные +X%), годовая сезонность (январь — обычно пик).
  • Допущения: «нет крупных релизов / событий в Q1 2018». Если ожидается релиз — модель не учитывает его без явного override (add_regressor в Prophet).
  • Сценарии: pessimistic / base / optimistic с разными changepoint_prior_scale.

Для бизнеса: «На январь 2018 года ожидаем DAU около X тыс., с пиком Y тыс. в первую неделю января (новогодние праздники). Февраль и март — стабильно ≈ Z тыс. с просадкой в будни на 10–15%».

Подводные камни

  1. Использовать только train без backtest. Без валидации MAPE/MAE прогноз — «доверьте мне». Бэктест — обязателен.
  2. Игнорировать праздники. Новогодний пик может быть +50% — без его явного учёта прогноз будет систематически промахиваться.
  3. Слишком гибкая модель. Высокий changepoint_prior_scale → overfit на исторических локальных пиках, плохой долгосрочный прогноз.
  4. Прогноз на изменения, которых не было в истории. Если запланирован релиз нового мода — модель не знает. Накладывайте поверх руками.
  5. Авторегрессия для DAU — спорно. «DAU вчера → DAU сегодня» — есть, но это часто мешает учитывать сезонность. Prophet/SARIMA лучше pure AR.
  6. Не учитывать обрыв ряда. Если в данных есть пропуски (сервер не работал) — заполнение 0 ломает модель. Используйте интерполяцию или удалите.
  7. «Тренд» — обманчивый сигнал. Падение DAU на хвосте может быть локальным (просадка, ивент закончился), а Prophet продлит как тренд. Контролируйте n_changepoints.

Альтернативы

  • Ensemble: медиана Prophet + SARIMA + naive seasonal — стабильнее одиночной модели.
  • DeepAR (если есть много продуктов / гео и можно учиться на похожих рядах).
  • Hierarchical forecasting: прогнозы на разных уровнях агрегации (страна / платформа) с reconciliation.

Эталонный ответ

Подход: Prophet с включённой годовой и недельной сезонностью + кастомные праздники (Новый год). Валидация — backtest на последних 90 днях 2017. Метрика — MAPE. Главное в ответе — обосновать выбор метода (что в данных есть тренд + 2 уровня сезонности + праздники) и не забыть про валидацию.

Хочешь увидеть разбор?

Зарегистрируйся бесплатно — откроется развёрнутое решение этой задачи и ещё 4 на выбор.

Зарегистрироваться и увидеть разбор
Уже есть аккаунт? Войти