воскресенье, 29 мая 2022 г.

Тренд во временных рядах : курсы Kaggle

Тренд временного ряда представляет собой постоянное долгосрочное изменение среднего значения ряда. Чтобы увидеть, какой тренд может иметь временной ряд, мы можем использовать график скользящего среднего. Чтобы вычислить скользящее среднее временного ряда, мы вычисляем среднее значение значений в скользящем окне определенной ширины. Каждая точка на графике представляет собой среднее значение всех значений ряда, попадающих в окно с обеих сторон. Идея состоит в том, чтобы сгладить любые краткосрочные колебания ряда, чтобы остались только долгосрочные изменения.

Для примера рассмотрим набор данных, представляющий месячную выручку восьми магазинов с января 2015 по декабрь 2021.

df = pd.read_excel(
    "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=df[['Sh01']]
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(). Как мы видим, тенденция нашего временного ряда явно отличается от линейной.

moving_average = df1.rolling(
    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,
);
Скользящее среднее позволяет быстро оценить, что происходит с выручкой магазина. При необходимости можно вывести такие графики по группе магазинов.

fig, (ax1, ax2) = plt.subplots(nrows=2, ncols=1, figsize=(10, 10))

ax1.set_title("Original data")
df.plot(ax=ax1)

ax2.set_title("Rolling 12-month mean")
df.rolling(12).mean().plot(ax=ax2)


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

Для получения тренда с помощью линейной регрессии необходимо подготовить независимые переменные, для этого используем функцию DeterministicProcess из модуля statsmodels. Задаем наличие константы в регрессионном уравнении, порядок кривой и контроль за коллениарностью.

from statsmodels.tsa.deterministic import DeterministicProcess

dp = DeterministicProcess(
    index=df1.index,  # даты из обучающих данных
    constant=True,       # фиктивная функция для смещения (y_intercept)
    order=1,        # порядок тренда
    drop=True,    # если необходимо, отбросить члены, чтобы избежать коллинеарности             
)

in_sample` создает функции для дат, указанных в аргументе `index`

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 = LinearRegression(fit_intercept=False)
model.fit(X, y)
y_pred = pd.Series(model.predict(X), index=X.index)

Выведем коэффициенты регрессии

coef_names = ['b0','b1']
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

Выведем график тренда

ax = df1.plot(style=".", color="0.5", title="Revenue - Linear Trend")
_ = y_pred.plot(ax=ax, linewidth=3, label="Trend")


Линейный тренд отразил итоговою тенденцию, получился немного падающим. Попробуем нелинейный тренд второго порядка (проще говоря параболу)

dp = DeterministicProcess(
    index=df.index, 
    constant=True,      
    order=2,             
    drop=True,          
)
X = dp.in_sample()
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 = LinearRegression(fit_intercept=False)
model.fit(X, y)
y_pred = pd.Series(model.predict(X), index=X.index)

Выводим коэффициенты и график

coef_names = ['b0','b1','b2']
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 = dp.out_of_sample(steps=12)
X.head()


   
        


monthconsttrend
2022-011.085.0
2022-021.086.0
2022-031.087.0
2022-041.088.0
2022-051.089.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['trend1']=trend11
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 = pd.Series(model.predict(X), index=X.index)
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()





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

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