Условие
Прогнозируем продажи на 7 дней вперёд через GBDT. Какие лаговые и rolling features построить?
Решение
Подход
Базовые семьи фичей:
- Lag features:
y_{t-k}, k = 1, 7, 14, 28, 365. - Rolling aggregates: mean / std / min / max / sum по окну 7, 14, 28 дней.
- Exponentially weighted: EWMA с разными α.
- Differences:
y_t − y_{t-7}(week-over-week),y_t / y_{t-7}(рост). - Calendar features: dow, dom, month, holiday, week_of_year.
- Domain: цена, промо, погода, тренд показов рекламы.
- Target-shifted aggregates (для horizon h=7):
mean(y[t-14:t-7])— последние 7 точек, но с лагом 7 чтобы не было leakage.
Lag = horizon + shift
При forecast h=7 нельзя использовать y_{t-3} — на момент prediction t мы знаем только до t-1, но прогнозируем t+6. Чтобы предсказывать y_{t+6}, на входе должны быть только y_{t-?} для ? ≥ 1. Если предсказываем t+6 зная только до t-1, то фичи могут использовать y_{t-1}, y_{t-2}, .... Если же делается direct forecast для каждого horizon отдельно, при horizon h=7 нельзя смешивать данные.
Реализация
import pandas as pd
def build_ts_features(df, target='sales', horizon=7):
df = df.sort_values('date').reset_index(drop=True)
# Lag features — лаги ≥ horizon, чтобы избежать leakage
for lag in [horizon, horizon+1, horizon+7, horizon+14, horizon+28, horizon+365]:
df[f'lag_{lag}'] = df[target].shift(lag)
# Rolling — также сдвигаем на horizon
for w in [7, 14, 28]:
df[f'roll_mean_{w}'] = df[target].shift(horizon).rolling(w).mean()
df[f'roll_std_{w}'] = df[target].shift(horizon).rolling(w).std()
# EWMA
df['ewma_7'] = df[target].shift(horizon).ewm(span=7).mean()
df['ewma_28'] = df[target].shift(horizon).ewm(span=28).mean()
# Calendar
df['dow'] = df.date.dt.dayofweek
df['dom'] = df.date.dt.day
df['month'] = df.date.dt.month
df['is_weekend'] = df.dow >= 5
# Diffs
df['diff_7'] = df[target].shift(horizon) - df[target].shift(horizon+7)
return dfMulti-step forecast
Два подхода:
- Direct: отдельная модель на каждый horizon h=1...H.
- Recursive: одна модель, прогнозы подставляются как фичи для следующих шагов. Аккумулирует ошибку.
Categorical с TE
Для категорий «магазин», «товар» — target encoding с expanding window (только прошлые данные).
Подводные камни
- Future leakage: lag меньше horizon = модель видит будущее на train, но не на проде. Регресс прода.
- Rolling без
shift(horizon)— тот же leakage. - NaN в начале серии — обработать (drop / impute / fill backwards forbidden for time series).
- Train/val split по времени, не random. Иначе CV врёт.
- На длинных горизонтах direct лучше, на коротких — recursive (меньше моделей).
- Категорий магазина на проде нет (новый магазин) — нужен fallback (cluster mean).
Эталонный ответ
Features: lags ≥ horizon (shift(horizon) обязательно), rolling agg (mean/std/min/max) по 7/14/28 окну после shift, EWMA, calendar (dow/dom/month/holiday), diffs/ratios. Direct для длинных horizons, recursive для коротких. Train/val split по времени.