временные рядыпрогнозPython

Прогнозирование временных рядов в Python: с чего начать

2026-07-11 11 мин

Если нужно спрогнозировать продажи, посещаемость или нагрузку на сервер, начните не с Prophet и не с нейросетей, а с трёх вещей: постройте график, разложите ряд на тренд и сезонность и посчитайте, насколько хорош самый простой прогноз «завтра будет как сегодня». В большинстве рабочих задач такой бейзлайн оказывается достаточно точным, а сложная модель либо выигрывает пару процентов, либо неожиданно проигрывает. Дальше разберу это по шагам — с кодом на Python и без магии.

Что такое временной ряд и чем он отличается от обычной таблицы?

Временной ряд — это последовательность наблюдений, привязанных к моментам времени и упорядоченных по этому времени: выручка по дням, число активных пользователей по неделям, температура по часам. Ключевое отличие от обычной таблицы в том, что порядок строк несёт информацию. Вы не можете перемешать строки, как в датасете для классической регрессии, потому что значение сегодня зависит от того, что было вчера. Это свойство называют автокорреляцией, и именно оно ломает многие привычные инструменты: обычная валидация с random split здесь врёт, потому что модель «подсматривает» в будущее.

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

Из чего состоит ряд: тренд, сезонность и остатки?

Почти любой ряд удобно мысленно разложить на три части. Тренд — это медленное движение вверх или вниз: бизнес растёт, аудитория стареет, рынок насыщается. Сезонность — это повторяющиеся колебания с фиксированным периодом: по будням трафика больше, чем в выходные; в декабре продажи выше, чем в феврале; ночью нагрузка ниже, чем днём. Остатки (их ещё называют шумом или резидуалами) — это всё, что не объяснилось трендом и сезонностью: разовые акции, сбои, случайные всплески.

Раскладка бывает двух видов. Аддитивная — когда компоненты складываются: ряд = тренд + сезонность + остаток. Она подходит, если амплитуда сезонных колебаний примерно постоянна. Мультипликативная — когда они перемножаются: ряд = тренд * сезонность * остаток. Её берут, если размах колебаний растёт вместе с уровнем ряда (сезонный всплеск на 10% выглядит как +100 единиц при уровне 1000 и как +1000 при уровне 10000). Быстрый лайфхак: если при росте тренда «гармошка» сезонности расширяется, скорее всего у вас мультипликативная история, и её часто лечат логарифмом — после log() мультипликативный ряд становится аддитивным.

Зачем раскладывать ряд на компоненты (decompose)?

Декомпозиция нужна не ради красивого графика, а чтобы понять, с чем вы имеете дело, до того как выбирать модель. Она отвечает на вопросы: есть ли вообще тренд, какой период у сезонности, насколько велик шум и нет ли в остатках подозрительных выбросов (те самые акции и сбои, которые стоит пометить как аномалии). В statsmodels это делается в несколько строк:

import pandas as pd
from statsmodels.tsa.seasonal import seasonal_decompose

# df: колонки date, orders — число заказов по дням
s = df.set_index("date")["orders"].asfreq("D")

result = seasonal_decompose(s, model="additive", period=7)  # недельная сезонность
result.trend      # медленная составляющая
result.seasonal   # повторяющийся паттерн по дням недели
result.resid      # то, что осталось
result.plot()

Обратите внимание на два параметра. period=7 задаёт длину сезона — для дневных данных с недельным паттерном это 7, для месячных с годовым — 12. Угадать период помогает как раз график и здравый смысл про бизнес. asfreq("D") выравнивает ряд по регулярной сетке дат и вскрывает пропуски: если в данных нет строки за какой-то день, модель об этом должна знать, а не молча съесть дырку. Пропуски обычно заполняют явно — нулём, если это отсутствие событий, или интерполяцией, если это дефект сбора.

Что такое стационарность и зачем она вообще нужна?

Ряд называют стационарным, если его статистические свойства не меняются во времени: среднее держится на одном уровне, разброс примерно постоянный, а связь между соседними точками зависит только от расстояния между ними, а не от календаря. Проще говоря — стационарный ряд «не идёт никуда» и колеблется вокруг постоянного уровня.

Зачем это? Классические статистические модели вроде ARIMA математически требуют стационарности — на нестационарных данных их прогнозы разъезжаются. Ряд с трендом и сезонностью почти всегда нестационарен, поэтому его приводят к стационарному: берут разности (ряд минус его же вчерашнее значение убирает тренд), сезонные разности (минус значение неделю назад убирает недельную сезонность), логарифмируют для стабилизации разброса. Формально проверяют тестом Дики-Фуллера (adfuller в statsmodels): если p-value маленькое, гипотезу о нестационарности отклоняем.

Важная оговорка: не всем моделям это нужно. Prophet и экспоненциальное сглаживание умеют работать с трендом и сезонностью напрямую, им приводить ряд к стационарности не надо. Так что стационарность — это про конкретное семейство моделей, а не универсальный обязательный ритуал. Если хочется закрепить статистическую базу под этим, разбор проверок и интерпретаций удобно держать под рукой в справочнике по Python.

Какие наивные бейзлайны стоит посчитать первыми?

До любой умной модели считаем три простых прогноза — они бесплатны и часто на удивление сильны. Первый: последнее значение (naive) — прогноз на завтра равен значению сегодня. Второй: скользящее среднее — прогноз равен среднему за последние N точек, что сглаживает шум. Третий, самый важный для сезонных данных: сезонный наив — прогноз на следующий понедельник равен прошлому понедельнику. Он бесплатно учитывает недельный паттерн, и обогнать его сложнее, чем кажется.

import pandas as pd

s = df.set_index("date")["orders"].asfreq("D")

# режем по времени: последние 28 дней — тест
train, test = s.iloc[:-28], s.iloc[-28:]

naive        = pd.Series(train.iloc[-1], index=test.index)          # последнее значение
moving_avg   = pd.Series(train.tail(7).mean(), index=test.index)     # среднее за неделю
seasonal_naive = s.shift(7).iloc[-28:]                               # значение неделю назад

Смысл в том, что бейзлайн — это планка. Если ваша навороченная модель не бьёт сезонный наив, она не нужна: вы потратите время на поддержку сложной штуки, которая не лучше одной строчки кода. Собрать и сравнить такие прогнозы можно прямо в браузере в Python-тренажёре, а закрепить приёмы работы с pandas — на практических задачах.

Как измерить точность прогноза?

Единственно правильной метрики нет — выбор зависит от того, что важнее бизнесу, поэтому обычно смотрят сразу несколько. MAE (средняя абсолютная ошибка) — это среднее от |факт минус прогноз|, измеряется в тех же единицах, что и ряд (рубли, заказы), интуитивно понятна, но её нельзя сравнивать между рядами разного масштаба. MAPE (средняя абсолютная процентная ошибка) — среднее от |факт минус прогноз| делить на |факт|, в процентах, поэтому сопоставима между разными рядами. У неё есть коварная проблема: если факт близок к нулю, деление разносит ошибку до бесконечности, а на нулях она вообще не определена. Поэтому для прерывистого спроса и рядов, где часто бывают нули, берут WAPE (взвешенная абсолютная процентная ошибка) — сумма |факт минус прогноз| делить на сумму факта. WAPE не взрывается на маленьких значениях и по смыслу отвечает на вопрос «на сколько процентов от общего объёма мы ошиблись».

import numpy as np

def mae(y, yhat):  return np.mean(np.abs(y - yhat))
def mape(y, yhat): return np.mean(np.abs((y - yhat) / y)) * 100
def wape(y, yhat): return np.sum(np.abs(y - yhat)) / np.sum(np.abs(y)) * 100

Практический совет: считайте метрику не на одном отрезке, а на нескольких сдвигающихся окнах (rolling backtest) — так вы поймёте, стабильна ли модель или ей просто повезло на одном хвосте. И держите в голове бизнес-контекст: если вы прогнозируете метрику вроде DAU, ошибка в 5% на выходных может быть терпимой, а тот же процент на пиковой распродаже — уже деньги.

Prophet, ARIMA или экспоненциальное сглаживание — что выбрать?

Коротко, без религиозных войн. Экспоненциальное сглаживание (ETS, Holt-Winters) — рабочая лошадка для рядов с трендом и сезонностью: быстрое, устойчивое, мало параметров, отличная отправная точка после бейзлайнов. ARIMA (и SARIMA с сезонностью) — классика статистики, гибкая и хорошо изученная, но требует приведения к стационарности и аккуратного подбора параметров; на коротких или очень шумных рядах легко переусложнить. Prophet от команды Meta заточен под бизнес-ряды: он раскладывает ряд на тренд, несколько сезонностей и праздники, легко принимает регрессоры и не боится пропусков — за что его любят аналитики, которым нужен разумный прогноз быстро, а не идеальный. Есть и более тяжёлые подходы — градиентный бустинг на лаговых признаках и нейросети, — но их берут, когда рядов много и простое перестаёт хватать; на одном ряде из пары сотен точек они чаще проигрывают.

Мой рабочий порядок такой: бейзлайн → экспоненциальное сглаживание или Prophet → и только если есть измеримый выигрыш, что-то сложнее. Выбор инструмента почти всегда зависит от длины ряда, числа рядов и от того, нужны ли вам объяснимость и праздники.

Как собрать минимальный пайплайн на Python?

Соберём всё вместе на Prophet: ему нужны две колонки — ds (дата) и y (значение). Не забываем резать тест по времени и сравнивать с бейзлайном.

from prophet import Prophet
import pandas as pd

d = df.rename(columns={"date": "ds", "orders": "y"})
train, test = d.iloc[:-28], d.iloc[-28:]

m = Prophet(weekly_seasonality=True, yearly_seasonality=True)
m.fit(train)

future = m.make_future_dataframe(periods=28)   # горизонт — 28 дней
forecast = m.predict(future)

yhat = forecast.set_index("ds")["yhat"].iloc[-28:].values
print("WAPE Prophet:", wape(test["y"].values, yhat))

Дальше сравниваете WAPE Prophet с WAPE сезонного наива. Если разница в вашу пользу и стабильна на нескольких окнах — берёте Prophet. Если нет — оставляете наив и экономите себе поддержку. Такой аккуратный подход к валидации и метрикам любят спрашивать на собеседованиях: типовые формулировки собраны в вопросах для интервью, а разборы прикладных ситуаций — в бизнес-кейсах.

Какие ошибки чаще всего портят прогноз?

Самая частая — утечка будущего в тест из-за случайного split вместо разреза по времени. Вторая — прогноз без бейзлайна: без планки вы не знаете, хороша ваша модель или просто существует. Третья — слепая вера в MAPE на рядах с нулями; берите WAPE. Четвёртая — игнор пропусков и выбросов: одна невыровненная дата или неучтённая распродажа перекашивает и декомпозицию, и обучение. Пятая — переусложнение: нейросеть на 150 точках почти всегда хуже Holt-Winters. И шестая — прогноз в вакууме без бизнес-смысла: спросите себя, какая ошибка в деньгах терпима, и подбирайте модель под это, а не под красивую цифру метрики.

Если хочется довести это до автоматизма — не читать, а решать: собрать ряд, посчитать метрики и сравнить модели удобнее руками в Python-тренажёре. Полный доступ ко всем задачам, кейсам и разборам открывается в Pro — без него для старта хватает и бесплатной части, так что начните с бейзлайнов уже сегодня.

Закрепи Python на задачах
402 Python-задачи через pandas/numpy/scipy — попробуй бесплатно.
Python-тренажёр →