понедельник, 3 ноября 2025 г.

Прогнозирование в розничной торговле с библиотекой SKTIME

Разберем задачу прогнозирования месячной выручки 50-ти магазинов розничной сети одежды.  У нас есть данные по месячным выручкам каждого магазина с января 2022 года по сентябрь 2025 года. Необходимо рассчитать прогноз на период с октября 2025 года по февраль 2027 года. Для решения этой задачи будем использовать библиотеку sktime.

Загрузка и визуализация данных

Библиотека sktime – библиотека для прогнозирования временных рядов. Она использует библиотеку pandas для представления временных рядов: pd.Series для одномерных временных рядов и последовательностей; pd.DataFrame для многомерных временных рядов и последовательностей. Индекс объекта Series и индекс объекта DataFrame используются для представления индекса временного ряда или индекса последовательности. 

# импортируем необходимые библиотеки
import numpy as np
import pandas as pd
from time import time
from sktime.utils.plotting import plot_series
from sktime.forecasting.model_selection import temporal_train_test_split
# импортируем класс ForecastingHorizon, задающий горизонт
from sktime.forecasting.base import ForecastingHorizon
# импортируем функцию, вычисляющую метрику качества sMAPE
from sktime.performance_metrics.forecasting import (
mean_absolute_percentage_error,
MeanAbsolutePercentageError)
from sktime.forecasting.arima import ARIMA, AutoARIMA
from sktime.forecasting.exp_smoothing import ExponentialSmoothing
from sktime.forecasting.statsforecast import StatsForecastAutoARIMA
from sktime.forecasting.compose import EnsembleForecaster
from sktime.forecasting.bats import BATS
from sktime.forecasting.fbprophet import Prophet

#Загружаем данные
df = pd.read_excel('month_rev_51_shops.xlsx', parse_dates=['month'], index_col='month')
print(df.head(3))

Sh1      Sh2      Sh3      Sh4      Sh5      Sh6      Sh7  \
month                                                                       
2022-01-31  2364488  1036838  3648782  1330480  1668548  2407337  2258058   
2022-02-28  1492999   870461  2053257   950569  1062742  1372846  1352454   
2022-03-31  2018963  1066751  2749680  1430781  1387147  2403290  2253839   

                Sh8      Sh9     Sh10  ...     Sh42     Sh43     Sh44  \
month                                  ...                              
2022-01-31  4727988  1569439  2802047  ...  3050872  1114756  4555560   
2022-02-28  2559290  1250363  1784248  ...  1869267   883099  2333665   
2022-03-31  4159812  1702816  3119833  ...  2148136  1200477  2184147   

               Sh45     Sh46     Sh47     Sh48     Sh49     Sh50       Sh51  
month                                                                        
2022-01-31  1437763  3284044  2058178  2809661  3661559  2460779  132473028  
2022-02-28   957407  1821049  1230815  1562765  1785294  1227001   85571611  
2022-03-31  1237669  2493867  1205422  1670421  2530268  2014150  120354786  

[3 rows x 51 columns]


В нашем датафрейме 50 колонок на каждый магазин и 51-я колонка, это их суммарная месячная выручка.
Для начала для предварительных исследований будем работать с одной колонкой - суммарной выручкой. 

df1=df[['Sh51']]
df1.head(3)


Sh51
month
2022-01-31132473028
2022-02-2885571611
2022-03-31120354786



# визуализируем наш временной ряд
plot_series(df1,labels=None, markers='o', colors='red', title='Sh51', x_label='month', y_label='rev',)

Будем решать нашу основную задачу в два этапа : на первом этапе мы разобьем наши

данные на обучающую и тестовую части и с помощью четырех методов 

прогнозирования сделаем прогнозы и оценим их точность,

на втором этапе мы обучим наши модели на всех наших данных, но сделаем прогноз

в том числе на период, по которому у нас есть фактические данные, что даст нам

возможность выбрать прогноз, который покажет наилучшую точность на фактических

данных. Конкретно это будет выглядеть так : обучаем модели на всех 45 месяцах,

с января 2022 по сентябрь 2025 и делаем прогноз на период с января 2025 по 

февраль 2027 и по точности за период с января 2025 по сентябрь 2025 выберем

наиболее походящий прогноз.

С помощью функции temporal_train_test_split() мы можем разбить набор на обучающую

и тестовую выборки с учетом временной структуры. В наших данных выручки за 45

месяцев, разобьем их на две части : обучающая 36 месяцев с января 2022 по 

декабрь 2024 и тестовая 9 месяцев с января по сентябрь 2025.

y_train, y_test = temporal_train_test_split(df1, test_size=9)

# визуализируем результаты разбиения
plot_series(y_train, y_test, labels=['y_train', 'y_test'])
print(y_train.shape[0], y_test.shape[0])

36 9


Этапы построения прогнозной модели в sktime

Процесс построения прогнозной модели в sktime достаточно прост и включает следующие этапы: 
- предварительная подготовка данных;
- определение горизонта прогнозирования (здесь используется массив NumPy или объект ForecastingHorizon); 
- создание прогнозной модели (forecaster); 
- обучение прогнозной модели с помощью метода .fit();
 - получение прогнозов с помощью метода .predict(). 
Создание, обучение, получение прогнозов модели осуществляются с помощью интерфейса, аналогичного scikit_learn.

Создание горизонта модели

Для начала прогнозирования необходимо указать горизонт прогнозирования и передать его нашему алгоритму прогнозирования.  Горизонты прогнозирования могут быть абсолютными или относительными. Абсолютный горизонт привязан к конкретным временным точкам (временным меткам) в будущем. Относительный горизонт привязан
к разнице во времени по отношению к текущему моменту.
В первом варианте будем использовать абсолютный горизонт. Для этого надо используем класс ForecastingHorizon, задающий горизонт.
Объект ForecastingHorizon принимает в качестве входных данных абсолютные индексы, но считает входные данные абсолютными или относительными в зависимости от флага is_relative (по умолчанию задано значение False, т. е. предполагается абсолютный горизонт). Если мы передаем разности во времени по отношению к текущему моменту, то ForecastingHorizon автоматически задает относительный горизонт. Если же мы передаем индексы объекта pandas, то ForecastingHorizon автоматически задает абсолютный горизонт.

# задаем абсолютный горизонт прогнозирования, передаем индексы
# объекта pandas, по умолчанию у нас ожидается абсолютный горизонт
# (is_relative=False)
fh = ForecastingHorizon(y_test.index, is_relative=False)
fh

ForecastingHorizon(['2025-01-31', '2025-02-28', '2025-03-31', '2025-04-30',
               '2025-05-31', '2025-06-30', '2025-07-31', '2025-08-31',
               '2025-09-30'],
              dtype='datetime64[ns]', freq=None, is_relative=False)

Здесь мы уже видим абсолютные индексы – конкретные метки времени. Абсолютный
горизонт, созданный с помощью класса ForecastingHorizon, можно преобразовать в
относительный и наоборот с помощью методов .to_relative() и .to_absolute(). Оба этих
преобразования требуют передачи cutoff в соответствующий метод. Сutoff – это
последняя временная точка обучающей выборки, отсечка, разделяющая обучающую и
тестовую выборки.

Переходим непосредственно к прогнозированию. 

Библиотека sktime включает целый ряд встроенных прогнозных моделей,
многие из которых связаны с современными пакетами по прогнозированию.
Все прогнозные модели поддерживают единый интерфейс sktime.
Основные классы, которые в настоящее время стабильно поддерживаются:
-классы ExponentialSmoothing, ARIMA, SARIMAX, ThetaForecaster, autoETS,
VAR, VARMAX, VECM из пакета statsmodels;
-класс autoARIMA из пакета pmdarima;
-класс StatsForecastAutoARIMA из пакета statsforecast;
-классы BATS и TBATS из пакета tbats;
-класс PolynomialTrend для прогнозирования трендов;
-класс Prophet, который позволяет использовать библиотеку prophet от
Facebook в формате интерфейса sktime.

Первая модель, которую мы будем использовать, это модель экспоненциального
сглаживания. Библиотека sktime предлагает свой интерфейс для класса
ExponentialSmoothing библиотеки statsmodels, выполняющего экспоненциальное
сглаживание. К нашему набору мы применим экспоненциальное сглаживание с
мультипликативными трендом и  сезонностью. Поскольку у нас месячные данные, 
указываем сезонный период (sp) равный 12.

# создаем экземпляр класса ExponentialSmoothing, задаем
# мультипликативную тренд и сезонность, sp=12

forecaster = ExponentialSmoothing(trend='mul', seasonal='mul', sp=12)

# задаем абсолютный горизонт прогнозирования
fh = ForecastingHorizon(y_test.index, is_relative=False)

# обучаем модель
forecaster.fit(y_train)

# получаем прогнозы
y_pred = forecaster.predict(fh)

# визуализируем прогнозы
plot_series(y_train, y_test, y_pred, labels=['y_train', 'y_test', 'y_pred'])

# вычисляем MAPE
er_ex=round(mean_absolute_percentage_error(y_pred, y_test),3)
print(er_ex)

0.091



#Класс StatsForecastAutoARIMA – это автоматически настраиваемый
вариант ARIMA.
# создаем экземпляр класса StatsForecastAutoARIMA,
# в котором реализована модель StatsForecastAutoARIMA
forecaster = StatsForecastAutoARIMA(sp=12)

# обучаем модель
forecaster.fit(y_train)

# получаем прогнозы
y_pred = forecaster.predict(fh)

# визуализируем прогнозы
plot_series(y_train, y_test, y_pred,labels=['y_train', 'y_test', 'y_pred'])

# вычисляем MAPE
er_ar=round(mean_absolute_percentage_error(y_pred, y_test),3)
print(er_ar)

0.105



Классы BATS и TBATS – это еще два алгоритма прогнозирования временных рядов,
которые реализованы в библиотеке sktime в виде оберток над пакетомBATS и TBATS – 
это акронимы основных компонентов моделей: Trigonometric seasonality, Box-Cox transformation, ARMA errors, Trend and Seasonal components (тригонометрическая сезонность, преобразование Бокса–Кокса, ошибки ARMA, трендовая и сезонная компоненты). Эти 
алгоритмы позволяют моделировать временные ряды с несколькими сезонностями. 
Для нашего прогноза используем модель  BATS

# создаем экземпляр класса BATS
forecaster = BATS(sp=12, use_trend=True, use_box_cox=False)

# обучаем модель
forecaster.fit(y_train)

# получаем прогнозы
y_pred = forecaster.predict(fh)

# визуализируем прогнозы
plot_series(y_train, y_test, y_pred,labels=['y_train', 'y_test', 'y_pred'])

# вычисляем MAPE
bats_ar=round(mean_absolute_percentage_error(y_pred, y_test),3)
print(bats_ar)
0.078


Теперь проиллюстрируем работу с классом Prophet, который позволяет использовать
библиотеку prophet от Facebook в формате интерфейса sktime.

forecaster = Prophet(
# задаем тип сезонности
seasonality_mode='multiplicative',
# задаем количество точек изменения тренда
n_changepoints=4,
# задаем факт наличия/отсутствия годовой сезонности
yearly_seasonality=True,
# задаем факт наличия/отсутствия недельной сезонности
weekly_seasonality=False,
# задаем факт наличия/отсутствия дневной сезонности
daily_seasonality=False)


# обучаем модель
forecaster.fit(y_train)

# получаем прогнозы
y_pred = forecaster.predict(fh)

# визуализируем прогнозы
plot_series(y_train, y_test, y_pred,labels=['y_train', 'y_test', 'y_pred'])

# вычисляем MAPE
er_ph=round(mean_absolute_percentage_error(y_pred, y_test),3)
print(er_ph)

0.077


Выведем ошибки моделей в одной таблице

data = {'Model':['ExponentialSmoothing','StatsForecastAutoARIMA','BATS','Prophet'],
        'MAPE':[er_ex,er_ar,er_bats,er_ph]}
 
df_er=pd.DataFrame(data)
df_er

ModelMAPE
0ExponentialSmoothing0.091
1StatsForecastAutoARIMA0.105
2BATS0.078
3Prophet0.077

Выполнение первого этапа дало представление об ошибке, которую выдают каждая
из рассмотренных моделей. Теперь переходим ко второму этапу - прогнозированию
по всем магазинам.
Для того, чтобы с одной стороны получить прогноз по февраль 2027 года, а с другой
иметь возможность выбрать этот прогноз из четырех прогнозов опираясь на сравнение
ошибок этих прогнозов на фактических данных, задаем горизонт с января 2025 по
февраль 2027 года. 

period=pd.date_range('2025-01','2027-03',freq='ME')
fh = ForecastingHorizon(period, is_relative=False)
fh

ForecastingHorizon(['2025-01-31', '2025-02-28', '2025-03-31', '2025-04-30',
               '2025-05-31', '2025-06-30', '2025-07-31', '2025-08-31',
               '2025-09-30', '2025-10-31', '2025-11-30', '2025-12-31',
               '2026-01-31', '2026-02-28', '2026-03-31', '2026-04-30',
               '2026-05-31', '2026-06-30', '2026-07-31', '2026-08-31',
               '2026-09-30', '2026-10-31', '2026-11-30', '2026-12-31',
               '2027-01-31', '2027-02-28'],
              dtype='datetime64[ns]', freq='ME', is_relative=False)


Каждый прогноз по четырем моделям будем записывать в файл excel.

#ExponentialSmoothing
forecaster = ExponentialSmoothing(trend='mul', seasonal='mul', sp=12)
forecaster.fit(df)
y_pred = forecaster.predict(fh)
y_pred.to_excel("fc_ex.xlsx")

#StatsForecastAutoARIMA
forecaster = StatsForecastAutoARIMA(sp=12)
forecaster.fit(df)
y_pred = forecaster.predict(fh)
y_pred.to_excel("fc_ar.xlsx")

#Prophet
forecaster = Prophet(seasonality_mode='multiplicative',n_changepoints=4,
yearly_seasonality=True,weekly_seasonality=False,daily_seasonality=False)
forecaster.fit(df)
y_pred = forecaster.predict(fh)
y_pred.to_excel("fc_ph.xlsx")

#BATS
forecaster = BATS(sp=12, use_trend=True, use_box_cox=False)
forecaster.fit(df)
y_pred = forecaster.predict(fh)
y_pred.to_excel("fc_bats.xlsx")

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

   ModelShops
0ExponentialSmoothing2
1StatsForecastAutoARIMA0
2BATS14
3Prophet13
4BATS+Prophet15
5BATS+ExponentialSmoothing6


Как видно, модель BATS оказалась самая востребованная, как сама по себе, так и как
среднее с другими моделями. Средняя MAPE по периоду январь-сентябрь 2025
составила 7,6% минимальная 2,6% максимальная 16,4%. И что еще хотелось бы
отметить : MAPE между прогнозом по 51-му магазину (суммарная выручка) и
сумма отдельно взятых прогнозов составила всего лишь 3,3%.
Можно считать такие результаты удовлетворительные и описанная методика с
с использованием библиотеки SKTIME может быть применена при планировании
выручек в магазинах, при том, что в качестве базы для прогноза взяты данные за
достаточно короткий период, всего 45 месяцев.
В этом материале описаны далеко не все возможности библиотеки SKTIME, в
следующих статьях разберем скользящее обновление моделей и прогнозов,
прогнозирование с использованием экзогенных показателей, перекрестная проверка
с расширяющимся или скользящим окном и многое другое.
При работе над данной статьей были использованы материалы из книги
Груздева А.В. "Прогнозирование временных рядов ..." ДМК Пресс, 2023.













вторник, 18 марта 2025 г.

понедельник, 24 февраля 2025 г.

ETNA : модель наивного прогноза

 Начинаю серию статей о сравнительно молодой библиотеки ETNA для прогнозирования временных рядов от команды Тинькофф Банк. В работе над статьями использую очень полезную книгу Груздев А. В. "Прогнозирование временных рядов с помощью Facebook Prophet, ETNA,sktime и LinkedIn Greykite".Начинаю с самой простой модели :  модель наивного прогноза