Собесов

Кейс — прогноз заказов Bolt Food на март 2020 по странам

Кейсы и метрикиForecastingСложнаяSenior

Условие

Дана таблица заказов Bolt Food (по 28 февраля 2020). По доступным данным сделать прогноз: сколько заказов будет в марте 2020 в каждой из показанных стран?

Подсказка от компании: использовать Python или BI (не Excel/Google Sheets).

Решение

Подход

«Прогноз» = модель временного ряда + аккуратное обращение со сценариями. План:

  1. Построить дневной ряд orders по каждой стране.
  2. Разделить на trend + seasonality (см. парную задачу про сезонность).
  3. Выбрать модель: naive baseline → Holt-Winters → Prophet → ML.
  4. Сделать back-test (валидация на отрезке внутри данных).
  5. Дать прогноз на март.
  6. Главное: обсудить, что февраль 2020 — начало COVID-19, и любая чисто статистическая экстраполяция «как было» на март 2020 — неверна. Дать сценарии.

Шаг 1. Подготовка дневного ряда

import pandas as pd, numpy as np
from statsmodels.tsa.holtwinters import ExponentialSmoothing
 
df = pd.read_excel("bolt_food.xlsx", sheet_name="Data", parse_dates=["Created Date"])
df["delivered"] = (df["Order State"] == "delivered").astype(int)
daily = (df.groupby(["Country", df["Created Date"].dt.normalize()])
           .agg(orders=("delivered", "sum"))
           .reset_index().rename(columns={"Created Date": "date"}))

Шаг 2. Naive baseline

Без них нельзя — это якорь, относительно которого мерять качество.

  • Naive last 7 days: прогноз дня = тот же день недели в прошлой неделе.
  • Average DoW: прогноз = среднее по DoW за весь доступный период.
def baseline_dow(ts: pd.Series, horizon_dates):
    by_dow = ts.groupby(ts.index.dayofweek).mean()
    return pd.Series([by_dow[d.dayofweek] for d in horizon_dates], index=horizon_dates)

Шаг 3. Holt-Winters

Для weekly seasonality (m = 7):

def hw_forecast(ts: pd.Series, horizon: int):
    ts = ts.asfreq("D").fillna(0)
    if len(ts) < 21:    # нужно ≥ 3 периода
        return None
    model = ExponentialSmoothing(
        ts, trend="add", seasonal="add", seasonal_periods=7,
        initialization_method="estimated"
    ).fit(optimized=True)
    return model.forecast(horizon)
 
forecasts = {}
for country, sub in daily.groupby("Country"):
    ts = sub.set_index("date")["orders"]
    fc = hw_forecast(ts, horizon=31)
    forecasts[country] = fc

add + add — для рядов без явной мультипликативной структуры. Если амплитуда сезонности растёт с уровнем, использовать seasonal="mul".

Шаг 4. Prophet (опционально, если данных достаточно)

from prophet import Prophet
 
def prophet_forecast(ts: pd.Series, horizon: int):
    df_ = ts.reset_index().rename(columns={"date": "ds", "orders": "y"})
    m = Prophet(weekly_seasonality=True, yearly_seasonality=False, daily_seasonality=False)
    m.fit(df_)
    future = m.make_future_dataframe(periods=horizon, freq="D")
    fc = m.predict(future)
    return fc.set_index("ds")["yhat"].iloc[-horizon:]

Prophet удобен, если хотим добавить holidays, regressors (например, lockdown dummy).

Шаг 5. Back-test

Качество модели = MAPE / RMSE на отложенном куске. Например, обучаем на январе, валидируем на первых трёх неделях февраля:

def backtest(ts, horizon=14):
    train, test = ts.iloc[:-horizon], ts.iloc[-horizon:]
    fc = hw_forecast(train, horizon=horizon)
    mape = (np.abs(test.values - fc.values) / np.maximum(test.values, 1)).mean()
    return mape
 
for country, sub in daily.groupby("Country"):
    ts = sub.set_index("date")["orders"]
    print(country, "MAPE =", round(backtest(ts), 3))

Если MAPE < 0.15 — модель пригодна. Если 0.30+ — слишком шумные данные, fallback на baseline.

Шаг 6. Главное: COVID-19

Февраль 2020:

  • Италия — первые случаи 31.01, рост в феврале, лockdown с 9 марта (национально). До 28 февраля — растущая тревожность, но ограничения только локальные.
  • Португалия — первые случаи 02.03; до 28.02 — обычный режим.
  • Финляндия / Эстония — единичные случаи к концу февраля.
  • Гана — никаких ограничений до середины марта.

Что это значит для прогноза:

  1. Чисто временной модели в марте 2020 верить нельзя. Она экстраполирует «обычный» март, а реальность будет резко другой.
  2. Эффект на food-delivery двойственный:
    • Lockdown → рестораны закрыты для dine-in → люди заказывают доставку → рост спроса.
    • Закрытие части ресторанов и проблемы с курьерами → падение supply → может упасть delivered.
  3. Без знания дат локдауна и реакции компании любая цифра — гадание.

Шаг 7. Сценарный прогноз

Правильный senior-ответ — три сценария на каждую страну:

Сценарий Допущение Прогноз
Base Без COVID-эффекта (модель Holt-Winters) XX заказов
Boom Lockdown с середины марта, спрос +30% после 1.0X1.0 \cdot X для первой половины + 1.3X1.3 \cdot X для второй
Bust Локдаун + закрытие ресторанов, supply −50% 1.0X1.0 \cdot X + 0.5X0.5 \cdot X

С каждым сценарием — диапазон, не точное число. Бизнесу нужны диапазоны и допущения, чтобы планировать capacity.

Шаг 8. Доверительные интервалы

Holt-Winters сам считает CI:

fc_obj = model.get_forecast(horizon)  # для statsmodels SARIMAX/ETSResults
mean = fc_obj.predicted_mean
ci   = fc_obj.conf_int(alpha=0.05)

Для Prophet — yhat_lower, yhat_upper. Эти интервалы — только статистическая неопределённость, не учитывают COVID.

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

  1. Слепое доверие модели. Holt-Winters в марте 2020 даст «обычный март» — это будет неверно для большинства стран. Senior должен это явно проговорить.
  2. Слишком короткий период обучения. Если данных всего 1–2 месяца, модели сезонности (HW, SARIMA, Prophet) будут переобучены или не сойдутся. Используйте простые baseline (DoW average).
  3. Маленькие страны. В Гане 10–20 заказов/день — статистический шум. Прогноз с MAPE 50%+. Дайте интервал, не точку.
  4. # of orders vs gmv. Разные метрики ведут себя по-разному (cancellation rate смещает orders, не gmv). Уточните, что именно прогнозируете.
  5. Праздники. Март 2020: 17.03 St. Patrick's в IE, 8.03 в Восточной Европе. Они входят в модель неявно — лучше явно учесть как regressor.
  6. Tilt в выгрузке. Если данные за разный период по странам, модель начнёт «не туда». Проверьте период по каждой стране отдельно.
  7. Forecast horizon = 31 день. На горизонте 30+ дней любой временной модели MAPE растёт быстро. Чем дальше — тем шире CI.
  8. Outliers и пропуски в delivered: за один сбой ETL может «провалиться» весь день. До прогноза — фиксы или интерполяция.

Эталонный ответ (структура презентации)

  1. EDA по странам: дневные ряды, DoW-индекс, тренды.
  2. Модель и валидация: Holt-Winters c m=7, back-test MAPE.
  3. Базовый прогноз (без COVID): таблица «прогноз заказов в марте 2020 по странам, с CI».
  4. COVID-disclaimer: явно сказать, что чисто статистическая модель в марте 2020 не годится.
  5. Сценарии: Base / Boom / Bust с допущениями.
  6. Рекомендации бизнесу: к чему готовиться, какие данные начать собирать оперативно (ежедневные supply-метрики, отмены по причине COVID), как обновлять модель по мере поступления.

Главное на собесе: показать, что знаете возможности и пределы моделей и понимаете, что в марте 2020 чистый forecast ≠ ответственный ответ.

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

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

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