В Python хватает инструментов для работы с временными рядами, но обычно приходится жонглировать тремя‑четырьмя пакетами с разными API. Darts — библиотека, которая собирает всё в одном месте: статистические модели, градиентный бустинг, нейросети — и работает по знакомой схеме fit() / predict(). Сегодня разберём её подробно: что умеет, где удобна, как использовать в задачах.
Установка
# Минимальная установка — статистические модели pip install darts # Полная установка — включая PyTorch-модели и Prophet pip install "u8darts[all]" # Если хотите точечно: pip install "u8darts[torch]" # нейросети pip install "u8darts[prophet]" # Facebook Prophet
Рекомендую ставить в чистый venv.
Главный кирпичик
В Darts всё крутится вокруг объекта TimeSeries. Это иммутабельный контейнер, который хранит временной индекс, значения, и умеет представлять стохастические ряды.
import pandas as pd import numpy as np from darts import TimeSeries # Из pandas DataFrame df = pd.DataFrame({ "date": pd.date_range("2022-01-01", periods=365, freq="D"), "sales": np.random.poisson(lam=50, size=365) + \ np.sin(np.arange(365) * 2 * np.pi / 365) * 15 # сезонность }) series = TimeSeries.from_dataframe(df, time_col="date", value_cols="sales") # Из numpy-массива с указанием частоты values = np.random.randn(100) series_np = TimeSeries.from_values(values, fill_missing_dates=True) # Из встроенных датасетов from darts.datasets import AirPassengersDataset, MonthlyMilkDataset air = AirPassengersDataset().load() # классика — авиапассажиры, 1949–1960 milk = MonthlyMilkDataset().load() # производство молока
TimeSeries поддерживает многомерные ряды, срезы по времени, арифметические операции. Можно складывать два ряда, умножать на скаляр — и временной индекс сохранится.
# Разделение на train/val train, val = air[:-36], air[-36:] # последние 3 года — валидация # Мультивариатный ряд df_multi = pd.DataFrame({ "date": pd.date_range("2022-01-01", periods=365, freq="D"), "sales": np.random.poisson(50, 365), "returns": np.random.poisson(5, 365), "page_views": np.random.poisson(500, 365) }) multi_series = TimeSeries.from_dataframe( df_multi, time_col="date", value_cols=["sales", "returns", "page_views"] )
Первые модели: от наивных до классических
Все модели в Darts — один интерфейс. fit(series) → predict(n). Вот так:
from darts.models import ( NaiveSeasonal, NaiveDrift, ExponentialSmoothing, AutoARIMA, Theta ) from darts.metrics import mape, rmse train, val = air[:-36], air[-36:] # Наивная сезонная модель: повторяем значения годичной давности naive = NaiveSeasonal(K=12) naive.fit(train) pred_naive = naive.predict(36) # Экспоненциальное сглаживание es = ExponentialSmoothing(seasonal_periods=12) es.fit(train) pred_es = es.predict(36) # AutoARIMA — автоматический подбор (p, d, q) arima = AutoARIMA() arima.fit(train) pred_arima = arima.predict(36) # Theta — метод, который выиграл M3-competition theta = Theta(theta=2) theta.fit(train) pred_theta = theta.predict(36) # Сравнение for name, pred in [("Naive", pred_naive), ("ExpSmoothing", pred_es), ("AutoARIMA", pred_arima), ("Theta", pred_theta)]: print(f"{name:15s} | MAPE: {mape(val, pred):.2f}% RMSE: {rmse(val, pred):.1f}")
Результат (на AirPassengers) будет таким:
Naive | MAPE: 8.13% RMSE: 45.2 ExpSmoothing | MAPE: 5.11% RMSE: 28.3 AutoARIMA | MAPE: 12.70% RMSE: 63.8 Theta | MAPE: 8.15% RMSE: 44.9
AutoARIMA на дефолтных параметрах не блещет, это нормально, у него свои сильные стороны на других данных. ExponentialSmoothing хорошо ловит и тренд, и сезонность.
Предобработка
Перед обучением нейросетей данные обычно нужно нормализовать. В Darts для этого есть Scaler и другие трансформеры:
from darts.dataprocessing.transformers import Scaler, MissingValuesFiller # Заполнение пропусков filler = MissingValuesFiller() series_filled = filler.transform(series) # Масштабирование (MinMaxScaler по дефолту) scaler = Scaler() train_scaled = scaler.fit_transform(train) val_scaled = scaler.transform(val) # После прогноза — обратное преобразование # pred_scaled = model.predict(36) # pred = scaler.inverse_transform(pred_scaled)
Трансформеры в Darts с состоянием. fit_transform запоминает параметры (min, max), а inverse_transform возвращает прогноз в оригинальный масштаб. Работают точно как sklearn.preprocessing, только для временных рядов.
Ковариаты
Большинство реальных задач предполагает, что помимо самого ряда у вас есть дополнительная информация: температура за окном влияет на продажи мороженого, день недели — на трафик сайта, праздники — на объём заказов.
В Darts ковариаты бывают двух видов:
past_covariates — данные, которые известны только до момента прогноза (включительно). Пример: фактическая погода за прошлые дни.
future_covariates — данные, известные заранее. Пример: день недели, месяц, запланированные акции.
from darts import TimeSeries, concatenate from darts.models import LinearRegressionModel from darts.utils.timeseries_generation import datetime_attribute_timeseries # Создаём future covariates из календарных признаков month_series = datetime_attribute_timeseries( air, attribute="month", one_hot=False ) year_series = datetime_attribute_timeseries( air, attribute="year", one_hot=False ) # Объединяем в мультивариатный covariate covariates = month_series.stack(year_series) # Масштабируем covariates тоже scaler_cov = Scaler() cov_scaled = scaler_cov.fit_transform(covariates) # Разделяем train_cov = cov_scaled[:len(train)] val_cov = cov_scaled[len(train):] # нужно покрыть горизонт прогноза # Модель с future covariates model = LinearRegressionModel( lags=12, # 12 прошлых значений target lags_future_covariates=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], output_chunk_length=12 ) model.fit(train, future_covariates=train_cov) pred = model.predict( n=36, future_covariates=cov_scaled # нужен полный ряд до конца прогноза ) print(f"LinReg + covariates MAPE: {mape(val, pred):.2f}%")
Многие модели поддерживают ковариаты.
А ещё есть фича add_encoders — прямо при создании модели можно сказать «добавь мне месяц и год как ковариаты», и Darts сгенерирует их автоматически:
from darts.models import NBEATSModel model = NBEATSModel( input_chunk_length=24, output_chunk_length=12, n_epochs=50, add_encoders={ "cyclic": {"future": ["month"]}, "datetime_attribute": {"future": ["year"]}, } ) model.fit(train) pred = model.predict(n=36)
Нейросети: N‑BEATS, TFT и компания
Darts оборачивает несколько нейросетевых архитектур через PyTorch Lightning. Два параметра:
input_chunk_length — сколько прошлых точек модель видит за раз.
output_chunk_length — сколько точек модель прогнозирует за раз.
Если вы вызываете predict(n) с n > output_chunk_length, модель работает авторегрессионно: прогнозирует output_chunk_length точек, подставляет их на вход и прогнозирует следующую порцию. Ошибки при авторегрессии накапливаются.
from darts.models import NBEATSModel, TFTModel, TCNModel from darts.dataprocessing.transformers import Scaler scaler = Scaler() train_scaled = scaler.fit_transform(train) val_scaled = scaler.transform(val) # N-BEATS — чистая нейросеть для временных рядов, # не требует ковариат (но может их использовать) nbeats = NBEATSModel( input_chunk_length=24, output_chunk_length=12, n_epochs=100, batch_size=32, random_state=42, # force_reset=True, # раскомментируем при повторном обучении # pl_trainer_kwargs={"accelerator": "gpu"} # для GPU ) nbeats.fit(train_scaled) pred_nbeats_scaled = nbeats.predict(n=36) pred_nbeats = scaler.inverse_transform(pred_nbeats_scaled) print(f"N-BEATS MAPE: {mape(val, pred_nbeats):.2f}%")
Для более сложных задач есть TFT (Temporal Fusion Transformer). Он поддерживает past_covariates, future_covariates и static_covariates одновременно. Мощная штука для мультисерийных задач (обучение на тысячах рядов сразу):
# TFT — нужны ковариаты для лучших результатов tft = TFTModel( input_chunk_length=24, output_chunk_length=12, hidden_size=64, lstm_layers=1, attention_head_size=4, dropout=0.1, n_epochs=50, batch_size=32, add_encoders={ "cyclic": {"future": ["month"]}, "datetime_attribute": {"future": ["year"]} }, random_state=42, ) tft.fit(train_scaled) pred_tft_scaled = tft.predict(n=36) pred_tft = scaler.inverse_transform(pred_tft_scaled) print(f"TFT MAPE: {mape(val, pred_tft):.2f}%")
XGBoost и LightGBM: деревья для временных рядов
Darts позволяет использовать gradient boosting для прогнозирования. Под капотом — создание табличного датасета из лагов целевой переменной и ковариат:
from darts.models import XGBModel, LightGBMModel # XGBoost с лагами xgb = XGBModel( lags=24, # 24 прошлых значения target lags_future_covariates=[0, 1, 2, 3, 4, 5], output_chunk_length=12, add_encoders={ "cyclic": {"future": ["month"]}, "datetime_attribute": {"future": ["year"]} }, ) xgb.fit(train) pred_xgb = xgb.predict(n=36) print(f"XGBoost MAPE: {mape(val, pred_xgb):.2f}%") # LightGBM — аналогично, часто работает быстрее lgbm = LightGBMModel( lags=24, output_chunk_length=12, add_encoders={"cyclic": {"future": ["month"]}}, ) lgbm.fit(train) pred_lgbm = lgbm.predict(n=36) print(f"LightGBM MAPE: {mape(val, pred_lgbm):.2f}%")
Модели‑деревья в Darts особенно удобны, потому что они обучаются быстро, не требуют масштабирования данных, поддерживают ковариаты, и их результаты можно объяснить через SHAP (Darts это тоже поддерживает).
Бэктестинг: а как модель будет работать в реальности?
Разовый split на train/val — это ок для первого знакомства. Но для прода нам нужен бэктестинг (walk‑forward validation): модель обучается на данных до момента T, прогнозирует на горизонт H, потом T сдвигается вперёд на stride шагов — и снова.
from darts.models import ExponentialSmoothing from darts.metrics import mape model = ExponentialSmoothing(seasonal_periods=12) # historical_forecasts — основной метод для бэктестинга backtest = model.historical_forecasts( series=air, start=0.7, # начать с 70% ряда forecast_horizon=12, # прогнозировать на 12 шагов stride=1, # сдвигать на 1 шаг каждый раз retrain=True, # переобучать модель на каждом шаге verbose=True ) # backtest — это TimeSeries прогнозов print(f"Backtest MAPE: {mape(air, backtest):.2f}%") # Визуализация import matplotlib.pyplot as plt air.plot(label="actual") backtest.plot(label="backtest forecast") plt.title("Walk-Forward Backtest") plt.legend() plt.tight_layout() plt.savefig("backtest.png", dpi=150) plt.close()
Для нейросетей retrain=True на каждом шаге слишком дорого. Используем retrain=False (модель обучается один раз) или retrain=10 (переобучение каждые 10 шагов):
# Бэктест нейросети без переобучения на каждом шаге nbeats = NBEATSModel( input_chunk_length=24, output_chunk_length=12, n_epochs=100, random_state=42 ) nbeats.fit(train_scaled) backtest_nn = nbeats.historical_forecasts( series=scaler.transform(air), start=0.7, forecast_horizon=12, stride=12, # сдвигаемся на output_chunk_length retrain=False, # не переобучаем verbose=True ) backtest_nn = scaler.inverse_transform(backtest_nn) print(f"N-BEATS Backtest MAPE: {mape(air, backtest_nn):.2f}%")
Ансамбли: комбинируем модели
Darts предлагает два типа ансамблей. NaiveEnsembleModel — простое среднее прогнозов. RegressionEnsembleModel — обученная регрессия на выходах моделей (stacked generalization):
from darts.models import ( NaiveEnsembleModel, RegressionEnsembleModel, ExponentialSmoothing, Theta, LinearRegressionModel, NaiveSeasonal ) # Наивный ансамбль — среднее двух моделей naive_ens = NaiveEnsembleModel( forecasting_models=[ ExponentialSmoothing(seasonal_periods=12), Theta(theta=2) ] ) naive_ens.fit(train) pred_ens = naive_ens.predict(36) print(f"Naive Ensemble MAPE: {mape(val, pred_ens):.2f}%") # Regression ensemble — обучает мета-модель поверх прогнозов reg_ens = RegressionEnsembleModel( forecasting_models=[ NaiveSeasonal(K=12), ExponentialSmoothing(seasonal_periods=12), Theta(theta=2) ], regression_train_n_points=24 # сколько точек для обучения мета-модели ) reg_ens.fit(train) pred_reg_ens = reg_ens.predict(36) print(f"Regression Ensemble MAPE: {mape(val, pred_reg_ens):.2f}%")
Regression ensemble часто даёт ощутимый прирост, потому что мета‑модель учится взвешивать прогнозы в зависимости от их сильных сторон.
Вероятностное прогнозирование
В продакшене часто нужен не точечный прогноз, а доверительный интервал. Darts поддерживает это нативно:
from darts.models import ExponentialSmoothing model = ExponentialSmoothing(seasonal_periods=12) model.fit(train) # num_samples > 1 — вероятностный прогноз pred_prob = model.predict(36, num_samples=500) # pred_prob содержит 500 сэмплов прогноза # Можно извлечь квантили: q05 = pred_prob.quantile_timeseries(0.05) q95 = pred_prob.quantile_timeseries(0.95) import matplotlib.pyplot as plt air.plot(label="actual") pred_prob.plot(label="forecast (median + 90% CI)") plt.legend() plt.tight_layout() plt.savefig("probabilistic.png", dpi=150) plt.close()
Для нейросетей вероятностное прогнозирование включается через likelihood:
from darts.models import NBEATSModel from darts.utils.likelihood_models import GaussianLikelihood nbeats_prob = NBEATSModel( input_chunk_length=24, output_chunk_length=12, n_epochs=100, likelihood=GaussianLikelihood(), # модель прогнозирует mu и sigma random_state=42 ) nbeats_prob.fit(train_scaled) pred_prob = nbeats_prob.predict(n=36, num_samples=500) pred_prob = scaler.inverse_transform(pred_prob)
Обнаружение аномалий
Darts умеет не только прогнозировать, но и детектить аномалии. Идея: обучаем модель, прогнозируем, сравниваем с фактом — большое расхождение = аномалия:
from darts.ad import QuantileDetector, ForecastingAnomalyModel from darts.models import ExponentialSmoothing # Аномалии через прогнозную модель anomaly_model = ForecastingAnomalyModel( model=ExponentialSmoothing(seasonal_periods=12), scorer="difference" # разница между прогнозом и фактом ) # Обучаем на «нормальных» данных anomaly_model.fit(train) # Детектим на новых данных anomaly_scores = anomaly_model.score(air) # Бинарный детектор по квантилю detector = QuantileDetector(high_quantile=0.99) detector.fit(anomaly_scores) binary_anomalies = detector.detect(anomaly_scores)
Полный пайплайн
Соберём всё вместе — пайплайн, который можно адаптировать под свою задачу:
import pandas as pd import numpy as np import matplotlib.pyplot as plt from darts import TimeSeries from darts.datasets import AirPassengersDataset from darts.dataprocessing.transformers import Scaler from darts.models import ( ExponentialSmoothing, AutoARIMA, Theta, NBEATSModel, RegressionEnsembleModel, NaiveSeasonal ) from darts.metrics import mape, rmse, mae # 1. Загрузка данных series = AirPassengersDataset().load() # 2. Split HORIZON = 36 train, val = series[:-HORIZON], series[-HORIZON:] # 3. Масштабирование (для нейросетей) scaler = Scaler() train_scaled = scaler.fit_transform(train) # 4. Обучение нескольких моделей results = {} # Классика for name, model in [ ("ExpSmoothing", ExponentialSmoothing(seasonal_periods=12)), ("AutoARIMA", AutoARIMA()), ("Theta", Theta(theta=2)), ]: model.fit(train) pred = model.predict(HORIZON) results[name] = {"pred": pred, "mape": mape(val, pred), "rmse": rmse(val, pred)} # Нейросеть nbeats = NBEATSModel( input_chunk_length=24, output_chunk_length=12, n_epochs=100, batch_size=32, random_state=42 ) nbeats.fit(train_scaled) pred_nbeats = scaler.inverse_transform(nbeats.predict(HORIZON)) results["N-BEATS"] = {"pred": pred_nbeats, "mape": mape(val, pred_nbeats), "rmse": rmse(val, pred_nbeats)} # Ансамбль ensemble = RegressionEnsembleModel( forecasting_models=[ NaiveSeasonal(K=12), ExponentialSmoothing(seasonal_periods=12), Theta(theta=2) ], regression_train_n_points=24 ) ensemble.fit(train) pred_ens = ensemble.predict(HORIZON) results["Ensemble"] = {"pred": pred_ens, "mape": mape(val, pred_ens), "rmse": rmse(val, pred_ens)} # 5. Сравнение print(f"{'Model':20s} | {'MAPE':>8s} | {'RMSE':>8s}") print("-" * 42) for name, r in sorted(results.items(), key=lambda x: x[1]["mape"]): print(f"{name:20s} | {r['mape']:7.2f}% | {r['rmse']:8.1f}") # 6. Визуализация лучшей модели best_name = min(results, key=lambda x: results[x]["mape"]) best_pred = results[best_name]["pred"] series.plot(label="Actual") best_pred.plot(label=f"Best: {best_name}") plt.title(f"Best model: {best_name} (MAPE: {results[best_name]['mape']:.2f}%)") plt.legend() plt.tight_layout() plt.savefig("forecast_comparison.png", dpi=150) plt.close()
Darts хорош, когда нужно быстро протестировать десяток моделей на одних и тех же данных с одним и тем же API. Darts не ок, если вам нужен полный контроль над архитектурой нейросети или у вас сильно кастомная задача, где стандартные обёртки только мешают.

Если хотите перейти от экспериментов к промышленным решениям, курс «Машинное обучение. Продвинутый уровень» (Machine Learning. Professional) даёт практику на реальных данных по нейросетям, обработке естественного языка, рекомендательным системам и работе с временными рядами, а также глубокую проработку подготовки данных и автоматизации производственных конвейеров. Это не обзор, а набор отточенных приёмов и упражнений, которые позволят воспроизводимо строить и оптимизировать модели в рабочем коде.
Чтобы узнать больше о формате обучения и задать вопросы преподавателям, приходите на бесплатные уроки:
12 марта, 20:00. «Метод градиентного спуска: лучше гор могут быть только горы…». Записаться
18 марта, 20:00. «Random Forest - мощный метод ансамблирования в ML». Записаться
