Feature engineering для time series на собеседовании Data Scientist

Проверь себя · 1/3разбор после ответа
Какая формула корректно описывает стандартную ошибку доли для разницы двух независимых долей в unpooled виде?

Почему это спрашивают

Прогноз временного ряда почти всегда упирается не в модель, а в признаки. Даже сильный градиентный бустинг ничего не выжмет из голого столбца дат — весь сигнал приходит из того, как вы превратили историю в фичи: лаги, скользящие окна, сезонность. Поэтому на собесе DS по time series разбор feature engineering — обязательный блок.

Интервьюер смотрит на две вещи: умеете ли вы собрать осмысленный набор признаков под задачу и, главное, не протекает ли у вас будущее в прошлое. Утечка данных (data leakage) — самая частая и самая дорогая ошибка на временных рядах, поэтому её проверяют почти в каждом вопросе.

Lag-признаки

Lag-признак — это значение таргета в прошлом, поданное на вход модели. Самый прямой способ дать модели «память»: что было вчера, неделю и месяц назад.

df['lag_1'] = df['sales'].shift(1)    # вчера
df['lag_7'] = df['sales'].shift(7)    # неделю назад (тот же день недели)
df['lag_30'] = df['sales'].shift(30)  # месяц назад

Дневные, недельные и месячные лаги — база для любой задачи с суточной сезонностью. Недельный лаг особенно ценен: он ловит эффект дня недели, потому что сравнивает понедельник с понедельником.

Ключевая оговорка. Нельзя заглядывать в будущее: на момент времени t доступны только данные строго до t. shift с положительным аргументом сдвигает ряд назад и это безопасно, а вот shift(-1) возьмёт завтрашнее значение — это прямая утечка. И ещё нюанс, о котором любят спросить: если прогноз строится на 7 дней вперёд, то lag_1 на инференсе недоступен (вчерашних фактов ещё нет), и брать можно только лаги не меньше горизонта прогноза либо считать прогноз рекурсивно.

Скользящие статистики

Скользящее окно агрегирует несколько прошлых точек в один признак — так модель видит тренд и волатильность, а не одно значение.

df['rolling_mean_7'] = df['sales'].rolling(7).mean()   # уровень за неделю
df['rolling_std_30'] = df['sales'].rolling(30).std()   # волатильность за месяц
df['rolling_max_14'] = df['sales'].rolling(14).max()   # пик за две недели

Среднее по окну сглаживает шум и показывает уровень, стандартное отклонение — меру разброса, максимум и минимум — экстремумы за период. Расширяющееся окно (expanding()) агрегирует всё от начала ряда до текущей точки — полезно, когда важна вся накопленная история.

Здесь тоже прячется утечка: pandas по умолчанию считает окно назад, и это правильно, но rolling(7, center=True) центрирует окно и захватывает будущие точки — на трейне так делать нельзя. И помните, что скользящее среднее «знает» текущую точку: если оно потом используется как признак для предсказания этой же точки, добавьте shift(1).

Сезонные признаки

Календарные компоненты — час, день недели, месяц — несут сильный сигнал, но подавать их как обычные числа неправильно: для модели 23 час и 0 час окажутся на разных концах шкалы, хотя это соседние моменты. Спасает циклическое кодирование через синус и косинус:

df['hour_sin'] = np.sin(2 * np.pi * df['hour'] / 24)
df['hour_cos'] = np.cos(2 * np.pi * df['hour'] / 24)
df['day_of_week'] = df['ts'].dt.dayofweek
df['day_of_month'] = df['ts'].dt.day
df['week_of_year'] = df['ts'].dt.isocalendar().week

Пара sin/cos замыкает шкалу в круг, и 23:00 оказывается рядом с 00:00. Для древесных моделей (бустинги, случайный лес) циклическое кодирование не так критично — они и так режут по порогам, — а вот линейным моделям и нейросетям оно заметно помогает.

Готовься к собесу аналитика как в Duolingo
10 минут в день — SQL, Python, A/B, метрики. 1700+ вопросов в Telegram
Открыть Карьерник в Telegram

Праздники и события

Праздники и выходные ломают обычную сезонность, поэтому их выделяют в отдельные признаки. Для российского календаря удобна библиотека holidays:

import holidays
ru_holidays = holidays.RU()
df['is_holiday'] = df['date'].apply(lambda d: d in ru_holidays)
df['is_weekend'] = df['day_of_week'].isin([5, 6])

Помимо календарных праздников важны бизнес-события — маркетинговые кампании, распродажи, запуски продукта. Их кодируют флагом активности на нужный период:

df['campaign_active'] = df['date'].between('2026-05-01', '2026-05-15')

Такие флаги объясняют модели всплески, которые иначе выглядят как необъяснимые выбросы. Полезно добавлять и «окрестности» события: день до и день после праздника часто ведут себя не так, как сам праздник.

Кросс-серийные признаки

Когда рядов много (магазины, товары, регионы), сигнал можно черпать из соседей — особенно если отдельный ряд короткий или разреженный:

  • Иерархические. Суммарные продажи по всей категории для товара внутри неё. Агрегат по группе стабильнее, чем шумный отдельный ряд.
  • Географические. Среднее по ближайшим магазинам — ловит локальные факторы (погода, события в городе), общие для точек рядом.
  • Кластерные. Среднее внутри группы похожих товаров. Помогает новинке, у которой ещё нет своей истории, опереться на поведение похожих SKU.

Кросс-серийные фичи особенно выручают на разреженных данных: там, где своей истории мало, поведение связанных рядов даёт модели опору. Но и здесь агрегаты должны считаться только по прошлому — иначе снова утечка.

Утечка данных — главная ловушка

Утечка (data leakage) на временных рядах — когда в признаки просачивается информация из будущего, которой на момент прогноза не существует. Модель показывает отличные метрики на валидации и разваливается в проде. Типичные источники:

  • Центрированные или заглядывающие вперёд окна (center=True, shift(-1)) — берут будущие точки.
  • Глобальная нормализация по всему датасету — среднее и стандартное отклонение, посчитанные по всем данным включая тест, подмешивают будущее в трейн. Скейлер надо фитить только на трейне.
  • Заполнение пропусков будущимbfill тянет назад значения из будущего. На рядах используют ffill.
  • Случайный k-fold — перемешивает время и учит модель на будущем, предсказывая прошлое. Нужен временной сплит: TimeSeriesSplit или walk-forward валидация, где трейн всегда раньше теста.

Именно об этом чаще всего гоняют на собесе: покажите, что вы сначала думаете «а знал бы я это в момент прогноза?», и только потом добавляете признак.

Связанные темы

FAQ

Как выбрать, какие лаги брать?

Отталкивайтесь от сезонности задачи и горизонта прогноза. Для суточных данных базовый набор — lag_1, lag_7, lag_14, lag_30 плюс лаги, кратные явным периодам (365 для годовой сезонности). Помогает автокорреляционная функция (ACF/PACF): пики на ней показывают, на каких лагах сохраняется зависимость. И всегда проверяйте, что выбранные лаги будут доступны на инференсе при вашем горизонте прогноза.

Почему сезонность кодируют через sin/cos, а не просто номером часа?

Календарные величины цикличны: после 23 часа идёт 0, после декабря — январь. Если подать их числом, модель решит, что 23 и 0 максимально далеки, хотя это соседние моменты. Пара sin/cos замыкает шкалу в окружность и сохраняет близость соседних значений. Для линейных моделей и нейросетей это важно; деревья справляются и с сырыми номерами.

Как избежать утечки данных при скользящих окнах?

Три правила: окно смотрит только назад (без center=True), при использовании агрегата как признака для текущей точки добавляйте shift(1), а нормализацию и заполнение пропусков делайте только по прошлому. И валидируйте временным сплитом, а не случайным k-fold, — тогда утечка через окна всплывёт сразу.

В чём разница между скользящим и расширяющимся окном?

Скользящее окно (rolling(n)) агрегирует фиксированное число последних точек и реагирует на недавние изменения — хорошо для локального тренда и волатильности. Расширяющееся (expanding()) берёт всю историю от начала ряда до текущей точки и даёт устойчивую долгосрочную оценку уровня. Часто в модель кладут оба — они ловят сигнал на разных горизонтах.

Как валидировать модель на временных рядах?

Только с сохранением порядка времени: трейн всегда раньше теста. Используют TimeSeriesSplit из sklearn или walk-forward (расширяющееся окно обучения со сдвигом теста вперёд). Обычный перемешивающий k-fold запрещён — он учит модель на будущем и предсказывает прошлое, что даёт оптимистичные метрики и провал в проде.

Это официальная информация?

Нет. Статья основана на общепринятых практиках feature engineering для временных рядов. Конкретные требования зависят от компании, задачи и уровня позиции.


Тренируйте Data Science — откройте тренажёр с 1500+ вопросами для собесов.