понедельник, 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.













Комментариев нет:

Отправить комментарий