Time series CV и фичи на собеседовании Data Scientist
Карьерник — Duolingo для аналитиков: 10 минут в день тренируй SQL, Python, A/B, статистику, метрики и ещё 3 темы собеса. 1500+ вопросов в Telegram-боте. Бесплатно.
Содержание:
Зачем нужна особая CV
В обычной CV (k-fold) данные перемешиваются и делятся на k частей. На time series это сломано: модель «видит будущее» во время обучения и кажется идеальной на CV. На проде падает.
Главная боль без правильной CV — DS показывает 0.95 R² на CV, продакт счастлив, через месяц на проде — 0.4. Причина: модель училась на завтра, предсказывая вчера. На собесе классический вопрос: «как делать CV для time series?» — ответ должен быть про walk-forward / expanding window и обязательно gap.
Walk-forward и expanding window
Walk-forward (rolling window):
Fold 1: train [t1..t6], val [t7..t8]
Fold 2: train [t3..t8], val [t9..t10]
Fold 3: train [t5..t10], val [t11..t12]Окно скользит вперёд, размер train фиксированный. Хорошо при concept drift — старые данные могут быть менее релевантны.
Expanding window (anchored):
Fold 1: train [t1..t6], val [t7..t8]
Fold 2: train [t1..t8], val [t9..t10]
Fold 3: train [t1..t10], val [t11..t12]Train растёт от начала. Хорошо, когда больше данных = лучшая модель и нет drift.
В sklearn — TimeSeriesSplit:
from sklearn.model_selection import TimeSeriesSplit
tscv = TimeSeriesSplit(n_splits=5, gap=7)
for train_idx, val_idx in tscv.split(X):
...TimeSeriesSplit — expanding window. Walk-forward — нужно делать руками.
Gap для предотвращения leakage
Gap — пропуск между train и val. Зачем: при создании lag-фич с lag=7 нельзя тренироваться на дне T-1 и валидироваться на T, потому что лаги для T рассчитываются по T-1.
train: [..., t-9, t-8] gap: [t-7..t-1] val: [t]Размер gap зависит от используемых лагов и rolling windows. Если есть rolling_30, gap должен быть ≥ 30 дней.
Альтернатива: сразу при splitting'е определить, какие lags безопасно использовать на каждом фолде. Сложнее, но точнее.
Lag features
Самые мощные фичи в time series — значения таргета и других переменных в прошлом.
df['sales_lag_1'] = df['sales'].shift(1) # вчерашние продажи
df['sales_lag_7'] = df['sales'].shift(7) # неделю назад (то же weekday)
df['sales_lag_30'] = df['sales'].shift(30)Какие лаги:
- 1, 7, 14, 28, 365 — типичные для daily-данных
- Период seasonality (год для daily, неделя для hourly)
- Доменные значения (для retail — Q1/Q2/Q3 backshift)
Грабля: target в прошлом. На validation/test эти лаги нужно строить осторожно — нельзя использовать ground truth, который мы пытаемся предсказать.
Rolling stats и calendar features
Rolling statistics — агрегаты в скользящем окне:
df['sales_roll_7_mean'] = df['sales'].shift(1).rolling(7).mean()
df['sales_roll_7_std'] = df['sales'].shift(1).rolling(7).std()
df['sales_roll_30_max'] = df['sales'].shift(1).rolling(30).max()shift(1) обязателен: иначе строка с date=t содержит rolling, который включает sales[t] = тот самый таргет. Утечка.
Calendar features:
- День недели (
dayofweek) - День месяца, день года
- Месяц, квартал
- Праздник (флаг + days_to_next_holiday)
- Сезон, выходной/будний
Cyclical encoding для периодических: dayofweek_sin, dayofweek_cos — чтобы модель видела «вс → пн» как близкие, не как 6 → 0:
df['dow_sin'] = np.sin(2 * np.pi * df['dayofweek'] / 7)
df['dow_cos'] = np.cos(2 * np.pi * df['dayofweek'] / 7)Внешние сигналы: погода, праздники, акции, цены конкурентов. Часто дают +5–15% к качеству.
Target leakage в time series
Target leakage — фича содержит информацию из будущего относительно момента предсказания.
Самые частые источники:
- Использование
mean(target)безshift(1)(включает текущее значение) - Future-aware aggregation: «средний таргет за следующие 7 дней» в фиче
- Forward-fill from future
- Encoded statistics on full dataset before split (
groupby().transform('mean')на всех данных)
Профилактика:
- Все aggregates считать только по past
- Encode категории через target encoding с CV-разбиением (out-of-fold target encoding)
- На каждой строке t использовать только данные с date < t
Если CV даёт сильно лучшие метрики, чем продакшн — almost always leakage.
Частые ошибки
KFold вместо TimeSeriesSplit. Случайное перемешивание для time series = модель видит будущее. Только walk-forward / expanding.
Без gap при использовании rolling_N. Train кончается на t-1, val начинается на t. Rolling_30 на t использует [t-30..t-1] — может включать данные из train. Gap = max(rolling_window).
StandardScaler на full dataset. Mean/std считается по всем данным, включая будущее → leakage. Скейлить только на train, применять к val.
target encoding без out-of-fold. Считаем mean(target) by category на всём датасете → leakage. Use out-of-fold encoding или smoothed (Bayesian).
Использовать timestamp напрямую. Модель учится на «дате» как непрерывной фиче — будет плохо экстраполировать на будущее. Декомпозировать в calendar features.
Не учитывать concept drift. Старая модель деградирует. Регулярный retrain или online learning.
Сравнивать модели на одном fold. Time series fold-to-fold variance высокая. Усреднять по нескольким fold.
Связанные темы
- Cross-validation простыми словами
- Hyperparameter tuning на собесе DS
- Feature engineering в pandas
- Подготовка к собесу Data Scientist
- Что такое overfitting
FAQ
Можно ли использовать k-fold на time series?
Только если задача не temporal (не зависит от времени). Если предсказываем будущее — никогда. K-fold даст оптимистичные метрики.
Walk-forward или expanding — что выбирать?
Зависит от concept drift. Если поведение меняется со временем (рынок, тренды) — walk-forward. Если данные стабильны и больше = лучше — expanding. На практике пробовать оба.
Что такое purged k-fold?
Вариант k-fold, в котором между train и val убираются образцы с overlapping информацией. Используется в финансовых time series (López de Prado).
Как тестировать модель на проде?
Backtest: применять модель «как если бы она была живой» к историческим периодам. На каждый момент t — train на [..., t-1], predict t. Метрики усреднять по большому периоду.
Time series CV для multi-horizon (предсказывать неделю вперёд)?
Использовать direct (одна модель на каждый horizon) или recursive (модель на 1 шаг, рекурсивно). Direct надёжнее на длинных horizons; recursive накапливает ошибки.
Это официальная информация?
Нет. Статья основана на работах по time series ML (López de Prado, Hyndman) и документации sklearn / Darts.
Тренируйте Data Science — откройте тренажёр с 1500+ вопросами для собесов.