Прогнозирование 200+ временных рядов с Etna: полный гайд от EDA до продакшена | AiManual
AiManual Logo Ai / Manual.
19 Янв 2026 Гайд

Прогнозирование 200+ временных рядов с библиотекой Etna: от EDA до production-ready кода

Пошаговый гайд по прогнозированию сотен временных рядов с библиотекой Etna. Обработка аномалий, масштабируемое ML-решение, production-ready код от Senior DevOps

Представьте: вам прилетает датасет с 250 временными рядами — продажи по магазинам, трафик по серверам, потребление энергии по регионам. Задача — построить прогноз на месяц вперед для каждого. Классический подход — написать 250 отдельных скриптов или один монстр-скрипт, который падает на третьем ряду из-за пропусков. Знакомо? Это боль.

Библиотек для временных рядов много: Prophet, statsmodels, sktime. Но когда рядов больше десятка, они начинают вести себя как капризные дети — каждому нужен индивидуальный подход. Etna от Tinkoff создана именно для этого: она рассматривает множество рядов как единый объект, но при этом умеет работать с каждым индивидуально. И да, она заточена под продакшн, а не только для исследовательских тетрадок.

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

Почему Etna, а не очередной самописный костыль?

Потому что она уже решила проблемы, о которых вы, возможно, еще не подумали. Например, как корректно обработать пропуски в 50 рядах одновременно, но с разной логикой? Как добавить внешние регрессоры (праздники, промо-акции) ко всем рядам, но не ко всем? Как оценивать качество модели на 200 рядах, не утонув в метриках? Etna дает ответы из коробки.

И самое главное — она не боится production. Встроенная поддержка логирования, валидации, экспорта моделей. Это не игрушка для исследований, а инструмент, который должен работать в пайплайне.

💡
Если вы уже экспериментировали с RAG-системами для корпоративных данных, то знаете, что масштабирование — это ключ. Тот же принцип здесь: единый пайплайн для множества сущностей. Кстати, про то, как оживлять архивы с помощью ИИ, я писал в гайде по RAG-чатботу.

1Загрузка данных: не просто CSV, а TSDataset

Первая ошибка — пытаться сохранить 250 рядов в 250 отдельных файлах. Вторая — свалить все в один DataFrame с мультииндексом и потом три часа разбираться с ним. Etna работает с форматом TSDataset — это специальная структура, которая хранит все ряды вместе, но сохраняет их идентичность.

import pandas as pd
from etna.datasets import TSDataset

# Допустим, у вас данные в 'wide' формате: каждая колонка — один ряд
df = pd.read_csv('200_timeseries.csv', parse_dates=['timestamp'])
df = df.set_index('timestamp')

# Преобразуем в TSDataset
ts = TSDataset(df, freq="D")  # 'D' для дневных данных
print(ts)

Здесь freq — критически важный параметр. Если ваши данные идут с пропусками дат, Etna автоматически дополнит их и отметит пропущенные значения. Это лучше, чем позже ловить ошибки в прогнозах.

Не пугайтесь, если в выводе увидите 'exog' и 'endog'. Endog — это ваши целевые ряды, exog — внешние регрессоры. Пока что exog пусто, но мы добавим праздники позже.

2EDA: смотрим на все и сразу

EDA для одного ряда — это график, автокорреляция, разложение. Для 200 рядов — это ад, если делать вручную. Etna дает методы для агрегированного анализа.

# Посмотрим на статистику по всем рядам
print(ts.describe())

# Визуализируем несколько случайных рядов
import matplotlib.pyplot as plt
ts.plot(segments=["series_1", "series_50", "series_150"])
plt.show()

Но настоящая магия — встроенные методы для обнаружения аномалий. Потому что прежде чем прогнозировать, нужно починить данные.

from etna.analysis import plot_anomalies
from etna.transforms import DensityOutliersTransform

# Используем метод межквартильного размаха для поиска выбросов
transform = DensityOutliersTransform(in_column="target", window_size=30, distance_coef=3.0)
ts.fit_transform([transform])
plot_anomalies(ts, transform)

Трансформ DensityOutliersTransform найдет точки, которые выбиваются из распределения, и пометит их. Вы можете заменить их на NaN или интерполировать. Главное — сделать это до обучения модели, иначе модель выучит аномалии как нормальное поведение.

3Обработка аномалий: не удалять, а интерполировать

Удалить аномалию — просто. Но если у вас ежедневные данные, пропуск создаст дыру, которая испортит прогноз. Лучше интерполировать.

from etna.transforms import TimeSeriesImputerTransform

# Заполним пропуски линейной интерполяцией
imputer = TimeSeriesImputerTransform(in_column="target", strategy="linear")
ts.fit_transform([imputer])

А что если аномалия — это не выброс, а сдвиг уровня из-за промо-акции? Тогда нужно добавить внешний регрессор — флаг акции. Etna позволяет добавить exog-признаки для каждого ряда отдельно.

Самый частый провал на этом этапе — неразличение глобальных и локальных аномалий. Глобальная аномалия (например, Ковид) затрагивает все ряды. Локальная (сломался датчик в одном магазине) — только один. Обрабатывайте их по-разному.

4Построение модели: один алгоритм на все ряды? Не всегда

Здесь Etna показывает свою силу. Вы можете обучить одну модель на всех рядах (например, CatBoost), но если ряды разнородны, лучше использовать ансамбль моделей или даже разные модели для разных групп рядов.

from etna.models import CatBoostModelPerSegment
from etna.pipeline import Pipeline
from etna.transforms import DateFlagsTransform, LagTransform

# Создадим трансформы для фичей
transforms = [
    DateFlagsTransform(day_number_in_week=True, day_number_in_month=True),
    LagTransform(lags=[7, 14, 30], in_column="target"),
]

# Модель CatBoost для каждого сегмента (ряда) отдельно
model = CatBoostModelPerSegment()

# Пайплайн — единая цепочка преобразований и модели
pipeline = Pipeline(model=model, transforms=transforms, horizon=30)

# Разделим данные на train/test
train_ts, test_ts = ts.train_test_split(test_size=30)

# Обучим
pipeline.fit(train_ts)

Почему CatBoostModelPerSegment? Потому что он обучит отдельную CatBoost-модель для каждого ряда. Это ресурсоемко, но дает лучшую точность, если ряды ведут себя по-разному. Если ряды похожи (например, продажи однотипных магазинов), используйте CatBoostModelMultiSegment — одна модель на все.

💡
Выбор между per-segment и multi-segment — это trade-off между точностью и скоростью/памятью. Напоминает дилемму при настройке гибридного поиска в RAG: больше компонентов — лучше релевантность, но выше задержка.

5Прогнозирование и оценка: не доверяйте одной метрике

Сгенерируем прогноз и посмотрим на метрики.

# Прогноз
forecast_ts = pipeline.forecast()

# Оценка
from etna.metrics import MAE, SMAPE
metrics = [MAE(), SMAPE()]

# Вычислим метрики для каждого ряда
for segment in forecast_ts.segments:
    segment_metrics = {}
    for metric in metrics:
        value = metric(forecast_ts[:, segment, "target"], test_ts[:, segment, "target"])
        segment_metrics[metric.name] = value
    print(f"{segment}: {segment_metrics}")

Но смотреть на 200 строк с метриками — бесполезно. Нужно агрегировать. Etna умеет считать метрики по всем сегментам, но я советую добавить свою логику: например, выделить топ-10 рядов с худшим SMAPE и разобрать их отдельно. Возможно, для них нужна другая модель.

6Упаковка в продакшн: Docker, API и мониторинг

Обученная модель — это просто Python-объект. В продакшне она должна отвечать на запросы, обновляться и логировать все. Вот схема, которая работает.

Сначала сериализуем пайплайн.

import pickle

with open('etna_pipeline.pkl', 'wb') as f:
    pickle.dump(pipeline, f)

Создаем простой FastAPI-сервис.

from fastapi import FastAPI
import pandas as pd
import pickle

app = FastAPI()

with open('etna_pipeline.pkl', 'rb') as f:
    pipeline = pickle.load(f)

@app.post("/forecast")
async def forecast(historical_data: dict):
    # historical_data — это словарь с историей для каждого ряда
    df = pd.DataFrame(historical_data)
    ts = TSDataset(df, freq="D")
    forecast_ts = pipeline.forecast(ts)
    # Преобразуем прогноз в словарь для ответа
    return forecast_ts.to_dict()

Упаковываем в Docker с учетом версий Python и библиотек. Критически важно зафиксировать версию Etna, потому что API может меняться.

FROM python:3.9-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

И добавляем мониторинг. Не просто логи, а отслеживание качества прогнозов по метрикам в реальном времени. Я использую Prometheus + Grafana, но можно начать с простого логирования отклонений прогноза от фактических значений, когда они поступают.

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

Нюансы, которые сломают ваш пайплайн

  • Пропуски в данных не в конце, а в середине. Etna хорошо обрабатывает пропуски в конце, но если дыра в середине исторических данных, нужно проверить, как трансформы (например, лаги) справятся с этим. Тестируйте на искусственных пропусках.
  • Разная длина рядов. Один ряд — 3 года истории, другой — 3 месяца. Модель per-segment справится, но для multi-segment это проблема. Выровняйте длины или используйте per-segment.
  • Внешние регрессоры с разной доступностью. Праздники известны на годы вперед, а погоду — только на неделю. Для прогноза на месяц вперед вам нужны все регрессоры на весь горизонт. Планируйте это заранее.
  • Память. 200 рядов с 1000 наблюдений каждый — это около 200к точек. Плюс лаги, плюс фичи. CatBoost съест несколько гигабайт. Мониторьте использование памяти, особенно в Docker-контейнере.

Частые ошибки и как их избежать

ОшибкаПочему происходитКак исправить
Прогнозы — просто прямая линияМодель не уловила сезонность. Возможно, не добавили лаги или датные фичи.Добавьте LagTransform с лагами, соответствующими сезонности (7 для недели, 30 для месяца). Используйте DateFlagsTransform.
Качество резко падает на определенных рядахАномалии в этих рядах не обработаны или ряды имеют другую природу.Выделите эти ряды в отдельную группу и постройте для них отдельную модель. Проверьте аномалии.
Сервис падает при запросе прогнозаВходные данные не соответствуют формату, ожидаемому пайплайном. Например, нет нужных колонок.Добавьте строгую валидацию входных данных в API. Используйте Pydantic для проверки схемы.
Обучение занимает часыИспользуете per-segment модель со сложными трансформами для сотен рядов.Увеличьте вычислительные ресурсы. Или перейдите на multi-segment модель. Или используйте отбор фич.

И последний совет: не пытайтесь построить идеальную модель с первого раза. Запустите базовый пайплайн, оцените качество, найдите слабые места. Может оказаться, что для 80% рядов простая линейная регрессия дает достаточную точность, а остальные 20% нужно обрабатывать отдельно. Это сэкономит вам кучу времени и нервов.

Прогнозирование временных рядов — это не магия, а инженерия. Etna — отличный инструмент, который убирает рутину, но не отменяет необходимости думать. Удачи в продакшене!