Собесов

Сценарий ML: лаги и rolling features для time series

ML / Data ScienceFeature engineeringСредняяMiddle

Условие

Прогнозируем продажи на 7 дней вперёд через GBDT. Какие лаговые и rolling features построить?

Решение

Подход

Базовые семьи фичей:

  1. Lag features: y_{t-k}, k = 1, 7, 14, 28, 365.
  2. Rolling aggregates: mean / std / min / max / sum по окну 7, 14, 28 дней.
  3. Exponentially weighted: EWMA с разными α.
  4. Differences: y_t − y_{t-7} (week-over-week), y_t / y_{t-7} (рост).
  5. Calendar features: dow, dom, month, holiday, week_of_year.
  6. Domain: цена, промо, погода, тренд показов рекламы.
  7. 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 df

Multi-step forecast

Два подхода:

  • Direct: отдельная модель на каждый horizon h=1...H.
  • Recursive: одна модель, прогнозы подставляются как фичи для следующих шагов. Аккумулирует ошибку.

Categorical с TE

Для категорий «магазин», «товар» — target encoding с expanding window (только прошлые данные).

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

  1. Future leakage: lag меньше horizon = модель видит будущее на train, но не на проде. Регресс прода.
  2. Rolling без shift(horizon) — тот же leakage.
  3. NaN в начале серии — обработать (drop / impute / fill backwards forbidden for time series).
  4. Train/val split по времени, не random. Иначе CV врёт.
  5. На длинных горизонтах direct лучше, на коротких — recursive (меньше моделей).
  6. Категорий магазина на проде нет (новый магазин) — нужен 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 по времени.

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

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

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