Тренд временного ряда представляет собой постоянное долгосрочное изменение среднего значения ряда. Чтобы увидеть, какой тренд может иметь временной ряд, мы можем использовать график скользящего среднего. Чтобы вычислить скользящее среднее временного ряда, мы вычисляем среднее значение значений в скользящем окне определенной ширины. Каждая точка на графике представляет собой среднее значение всех значений ряда, попадающих в окно с обеих сторон. Идея состоит в том, чтобы сгладить любые краткосрочные колебания ряда, чтобы остались только долгосрочные изменения.
Для примера рассмотрим набор данных, представляющий месячную выручку восьми магазинов с января 2015 по декабрь 2021.
"shops8_rev.xlsx",
parse_dates=['month'])
df = df.set_index("month").to_period()
pd.concat([df.head(), df.tail()])
month | Sh01 | Sh02 | Sh03 | Sh04 | Sh05 | Sh06 | Sh07 | Sh08 |
---|---|---|---|---|---|---|---|---|
2015-01 | 157408 | 82190 | 174300 | 77120 | 87860 | 51830 | 81790 | 80500 |
2015-02 | 127692 | 83010 | 124520 | 83330 | 79210 | 42700 | 62320 | 76530 |
2015-03 | 140148 | 91590 | 182450 | 105470 | 98780 | 54490 | 75460 | 110610 |
2015-04 | 167924 | 82680 | 183340 | 111520 | 121480 | 53850 | 88780 | 96930 |
2015-05 | 184196 | 98930 | 204770 | 123600 | 128200 | 63730 | 92800 | 116680 |
2021-08 | 161308 | 48320 | 100380 | 87310 | 73110 | 83550 | 62500 | 39640 |
2021-09 | 165948 | 61810 | 136720 | 105370 | 91580 | 109460 | 83650 | 55120 |
2021-10 | 216604 | 66430 | 181200 | 105550 | 92360 | 133850 | 85850 | 63220 |
2021-11 | 276204 | 70140 | 197760 | 119400 | 98500 | 154980 | 101530 | 78540 |
2021-12 | 236616 | 78480 | 191440 | 150360 | 126040 | 183610 | 124600 | 93280 |
Сделаем новый набор с одним магазином
df1.head()
month | Sh01 |
---|---|
2015-01 | 157408 |
2015-02 | 127692 |
2015-03 | 140148 |
2015-04 | 167924 |
2015-05 | 184196 |
Для выделения тренда этого временного ряда построим график скользящей средней. Так как этот ряд имеет ежемесячные наблюдения, выберем окно в 12 месяцев, чтобы сгладить сезонные изменения в течение года.
Для создания скользящего окна используем объект Rolling, создаваемый методом pandas.DataFrame.rolling(), указав ширину окна. В данном случае мы хотим создать скользящее окно шириной 12. Объект Rolling задает ширину окна, но при этом он не выполняет фактических вычислений. Для скользящего среднего используем метод mean(). Как мы видим, тенденция нашего временного ряда явно отличается от линейной.
window=12,
center=True
).mean()
ax = df1.plot(style=".", color="0.5")
moving_average.plot(
ax=ax, linewidth=3, title="Revenue - 12-month Moving Average", legend=False,
);
Для получения тренда с помощью линейной регрессии необходимо подготовить независимые переменные, для этого используем функцию DeterministicProcess из модуля statsmodels. Задаем наличие константы в регрессионном уравнении, порядок кривой и контроль за коллениарностью.
X = dp.in_sample()
X.head()
month | const | trend |
---|---|---|
2015-01 | 1.0 | 1.0 |
2015-02 | 1.0 | 2.0 |
2015-03 | 1.0 | 3.0 |
2015-04 | 1.0 | 4.0 |
2015-05 | 1.0 | 5.0 |
Для создания модели линейной регрессии используем класс LinearRegression из библиотеки Scikit-Learn. Так как раньше задали наличие константы в регрессионном уравнении, для исключения дублирования задаем fit_intercept=False
from sklearn.linear_model import LinearRegression
y = df1["Sh01"] # целевая функция
model.fit(X, y)
y_pred = pd.Series(model.predict(X), index=X.index)
Выведем коэффициенты регрессии
print(pd.DataFrame({'Predictor': X.columns,
'coefficient Name':coef_names,
'coefficient Value': model.coef_}))
Predictor coefficient Name coefficient Value
0 const b0 223413.977051 1 trend b1 -71.003382
Выведем график тренда
_ = y_pred.plot(ax=ax, linewidth=3, label="Trend")
Линейный тренд отразил итоговою тенденцию, получился немного падающим. Попробуем нелинейный тренд второго порядка (проще говоря параболу)
index=df.index,
constant=True,
order=2,
drop=True,
)
X.head()
month | const | trend | trend_squared |
---|---|---|---|
2015-01 | 1.0 | 1.0 | 1.0 |
2015-02 | 1.0 | 2.0 | 4.0 |
2015-03 | 1.0 | 3.0 | 9.0 |
2015-04 | 1.0 | 4.0 | 16.0 |
2015-05 | 1.0 | 5.0 | 25.0 |
Заново определяем модель линейной регрессии
model.fit(X, y)
y_pred = pd.Series(model.predict(X), index=X.index)
Выводим коэффициенты и график
print(pd.DataFrame({'Predictor': X.columns,
'coefficient Name':coef_names,
'coefficient Value': model.coef_}))
Predictor coefficient Name coefficient Value 0 const b0 202185.663490 1 trend b1 342.342275 2 trend_squared b2 -16.504886
С точки зрения графика получилось красиво, с точки зрения практики бесполезно.
Если вернуться к графику скользящего среднего, то очевидна есть точка перелома и
может быть даже их две. Первая - между ростом и началом падения, вторая - замедление падения. Такой тренд можно было представить в виде линейного сплайна.
Вернемся к подготовке фиктивных осей времени
dp = DeterministicProcess( index=df1.index, constant=True, order=1, drop=True, )X = dp.in_sample() X.head()
month | const | trend |
---|---|---|
2015-01 | 1.0 | 1.0 |
2015-02 | 1.0 | 2.0 |
2015-03 | 1.0 | 3.0 |
2015-04 | 1.0 | 4.0 |
2015-05 | 1.0 | 5.0 |
Предположим, что первая точка перелома на 24-м месяце, а вторая - на 48-м. Добавим еще две оси времени
trend1=[0 if x<=24 else x-24 for x in range(len(X))]
trend2=[0 if x<=48 else x-48 for x in range(len(X))]
X['trend1']=trend1
X['trend2']=trend2
X.head()
const | trend | trend1 | trend2 | |
---|---|---|---|---|
month | ||||
2015-01 | 1.0 | 1.0 | 0 | 0 |
2015-02 | 1.0 | 2.0 | 0 | 0 |
2015-03 | 1.0 | 3.0 | 0 | 0 |
2015-04 | 1.0 | 4.0 | 0 | 0 |
2015-05 | 1.0 | 5.0 | 0 | 0 |
Определим новую модель
model = LinearRegression(fit_intercept=False)
model.fit(X, y)
y_pred = pd.Series(model.predict(X), index=X.index)Выведем коэффициенты регрессии
coef_names = ['b0','b1','b2','b3']
print(pd.DataFrame({'Predictor': X.columns,
'coefficient Name':coef_names,
'coefficient Value': model.coef_}))
Predictor coefficient Name coefficient Value 0 const b0 156776.149339 1 trend b1 3899.664307 2 trend1 b2 -4425.803496 3 trend2 b3 -1181.006243
Выведем график тренда
Чтобы сделать прогноз, мы применяем нашу модель к функциям «out_of_sample или вне выборки». «Вне выборки» относится ко времени вне периода наблюдения обучающих данных. Вот как мы можем сделать 12-ти месячный прогноз:
X.head()
month | const | trend |
---|---|---|
2022-01 | 1.0 | 85.0 |
2022-02 | 1.0 | 86.0 |
2022-03 | 1.0 | 87.0 |
2022-04 | 1.0 | 88.0 |
2022-05 | 1.0 | 89.0 |
trend11=list(range(trend1[-1]+1,trend1[-1]+13))
trend22=list(range(trend2[-1]+1,trend2[-1]+13))
print(trend11)
print(trend22)
[60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71] [36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47]
X['trend2']=trend22
X.head(12)
const | trend | trend1 | trend2 | |
---|---|---|---|---|
2022-01 | 1.0 | 85.0 | 60 | 36 |
2022-02 | 1.0 | 86.0 | 61 | 37 |
2022-03 | 1.0 | 87.0 | 62 | 38 |
2022-04 | 1.0 | 88.0 | 63 | 39 |
2022-05 | 1.0 | 89.0 | 64 | 40 |
2022-06 | 1.0 | 90.0 | 65 | 41 |
2022-07 | 1.0 | 91.0 | 66 | 42 |
2022-08 | 1.0 | 92.0 | 67 | 43 |
2022-09 | 1.0 | 93.0 | 68 | 44 |
2022-10 | 1.0 | 94.0 | 69 | 45 |
2022-11 | 1.0 | 95.0 | 70 | 46 |
2022-12 | 1.0 | 96.0 | 71 | 47 |
Делаем прогноз на 12 месяцев вперед
y_fore.head()
2022-01 180183.180947 2022-02 178476.035516 2022-03 176768.890084 2022-04 175061.744653 2022-05 173354.599221
Выводим график вместе с прогнозом
ax = df1["2015-01":].plot(title="Revenue - splain trend Forecast", **plot_params)
ax = y_pred["2015-01":].plot(ax=ax, linewidth=3, label="Trend")
ax = y_fore.plot(ax=ax, linewidth=3, label="Trend Forecast", color="C3")
_ = ax.legend()
Комментариев нет:
Отправить комментарий