September 5, 2022

Получение компонент временного ряда

Рассмотрим способ разложения временного ряда на трендовую, сезонную и остаточную составляющие. Под трендом понимаем общую закономерность ряда (изменение среднего значения со временем), под сезонностью — повторяющуюся закономерность в определенных периодах (чтобы говорить о сезонности, в датасете должно быть несколько периодов).
Для демонстрации загрузим набор данных о статистике пассажирских перелетов с 1949 по 1960 из библиотеки pmdarima:

import pmdarima as pm
import pandas as pd
ts = pm.datasets.load_airpassengers(as_series=True)

ts.index = pd.date_range('1949', '1961', freq='M')
ts.head()

Разложение на перечисленные выше составляющие можно произвести с помощью функции seasonal_decompose из модуля statsmodels.tsa.seasonal:

from statsmodels.tsa.seasonal import seasonal_decompose

parts = seasonal_decompose(ts, model="additive")
_ = parts.plot()

В атрибутах возвращаемого seasonal_decompose объекта хранятся нужные нам тренд, сезонность и остатки. Проверим, что разложение в сумме (так как модель аддитивная) дает исходный ряд (для наглядности отобразил ряд с первого заполненного члена, так как в результате подсчета составляющих по умолчанию получаются пропуски):

(parts.trend.loc['1949-07':] + parts.seasonal.loc['1949-07':] + 
 parts.resid.loc['1949-07':]).head(7)
ts.loc['1949-07':].head(7)

Тренд

Теперь подробнее рассмотрим составляющие разложения и попробуем получить их вручную. Тренд извлекается путем подсчета скользящих средних с центрированным окном. Сравните:

parts.trend.plot()

и

ts.rolling(center=True, window=14).mean().plot()

Ручной результат немного отличается ввиду использования в seasonal_decompose незначительно отличающейся функции свертки.

Сезонность

Автоматически полученная сезонность для всех компонентов такая:

parts.seasonal['1960'].plot()

Для получения сезонности вручную достаточно вычесть из исходного ряда тренд и получить среднее по "сезонным" периодам (в данном случае месяцам):

ts_detrend = ts - parts.trend

ts_detrend.to_frame().assign(mon=ts_detrend.index.month).groupby('mon')[0].mean().plot()

Остатки

Остатки получаем как разность между исходным рядом с одной стороны и трендом и сезонностью с другой:

ts_seas = ts_detrend.to_frame().assign(mon=ts_detrend.index.month).groupby('mon')[0].transform('mean')
ts_resid = ts_detrend - ts_seas
ts_resid.plot()

А вот остатки, полученные с seasonal_decompose:

parts.resid.plot()

Можно заключить, что графики почти идентичны.