Image by Monoar Rahman Rony from Pixabay

Методология, лежащая в основе пакета Prophet, предполагает, что моделируемый временной ряд можно разложить на следующие компоненты (Taylor & Letham 2017):

\[y(t) = g(t) + s(t) + h(t) + \epsilon_t,\]

где \(g(t)\) - функция, аппроксимирующая тренд ряда, \(s(t)\) - функция, аппроксимирующая сезонные колебания (например, годовые, недельные и т.п.), \(h(t)\) - функция, отражающая эффекты праздников и других важных событий, а \(\epsilon_t\) - нормально распределенные случайные возмущения. В такой формулировке эта модель представляет собой частный случай обобщенных аддитивных моделей (Generalized Additive Models, GAM).
Как было отмечено ранее, сезонные компоненты аппроксимируются в Prophet с помощью частичных сумм ряда Фурье, число членов которого (порядок) определяет гладкость соответствующей функции (см., например, аргумент yearly.seasonality функции prophet()). В этом сообщении мы рассмотрим дополнительные способы спецификации сезонных составляющих.

Код для воспроизведения примеров

Все примеры, приведенные в сообщениях из этой серии, можно воспроизвести с помощью кода, который хранится в Github-репозитории ranalytics/intro_to_prophet. Описание используемых в примерах данных представлено в первом сообщении.

Функция add_seasonality()

Если анализируемый временной ряд охватывает как минимум два года, то функция prophet() автоматически добавит в модель компоненты годовой и недельной сезонности. Если гранулярность данных превышает дневную (например, если имеются почасовые наблюдения зависимой переменной), то в модель автоматически будет добавлена также дневная сезонность. Функция add_seasonality() позволяет исследователю добавить и любые другие сезонные составляющие (например, часовую, месячную, квартальную и т.п.). Эта функция имеет следующие аргументы:
  • m - модельный объект;
  • name - название сезонной состявляющей;
  • period - число (необязательно целое), соответствующее количеству дней в одном сезонном цикле;
  • fourier.order - порядок (количество членов) ряда Фурье (по умолчанию равен 3 для недельной сезонности и 10 для годовой);
  • mode - тип модели; принимает два возможных значения - "additive" (аддитивная; выбирается по умолчанию) и "multiplicative" (мультипликативная);
  • condition.name - название сторонней переменной, которая задает разные режимы моделируемой сезонности.
Рассмотрим примеры использования функции add_seasonality() и ее аргументов.

Сезонные компоненты, заданные пользователем

В приведенном ниже коде мы сначала отключаем автоматически добавляемую в модель недельную сезонность и вместо нее добавляем месячную сезонность (исходя из того, что один месячный период составляет 30.5 дней). На рис. 1 представлены все компоненты полученной модели (тренд, годовая сезонность и месячная сезонность).

M10 <- prophet(weekly.seasonality = FALSE) # отключаем недельную сезонность
M10 <- add_seasonality(m = M10,            # добавляем месячную сезонность
                       name = "monthly",
                       period = 30.5,
                       fourier.order = 5)
M10 <- fit.prophet(M10, train_df)
forecast_M10 <- predict(M10, future_df)
prophet_plot_components(M10, forecast_M10)

Рис. 1

Аналогичным образом вместо месячной сезонной составляющей мы могли бы добавить, например, квартальную (задав длину периода в 365.25/4 дней):

M11 <- prophet(weekly.seasonality = FALSE) # отключаем недельную сезонность
M11 <- add_seasonality(m = M11,            # добавляем квартальную сезонность
                       name = "quarter",
                       period = 365.25/4,
                       fourier.order = 2)
M11 <- fit.prophet(M11, train_df)
forecast_M11 <- predict(M11, future_df)
prophet_plot_components(M11, forecast_M11)

Рис. 2

Условные режимы сезонности

В ряде случаев функция, аппроксимирующая ту или иную сезонную составляющую, может изменять свои свойства в зависимости от каких-то сторонних факторов. Например, колебания в течение рабочих дней могут иметь характер, сильно отличающийся от такового  в выходные дни. Пакет Prophet позволяет моделировать такие условные режимы сезонности (т.е. режимы, которые зависят от сторонних факторов) с помощью аргумента condition.name функции add_seasonality(). Как следует из названия, на этот аргумент подается имя (булевой) переменной, которая определяет соответствующий режим. Такие переменные должны хранится в той же таблице, что и основные данные по временному ряду.

Исключительно в качестве примера предположим, что недельные колебания стоимости биткоина в летние месяцы отличаются от таковых в другие месяцы. Чтобы смоделировать такое различие, добавим в таблицу с данными train_df две новые индикаторные переменные - summer (принимает значение TRUE в летние месяцы и FALSE в другие месяцы) и not_summer (TRUE в нелетние месяцы и FALSE летом). Важно помнить, что такие же переменные нужно добавить и в таблицу с будущими датами future_df - иначе прогнозные значения расчитать не получится:


# Функция для удобного добавления переключателей режимов в данные:
is_summer <- function(ds) {
    month <- as.numeric(format(ds, '%m'))
    return(month > 5 & month < 9)
}

# Добавляем переключатели в обучающие данные и в таблицу с будущими датами:
train_df$summer <- is_summer(train_df$ds)
train_df$not_summer <- !train_df$summer
future_df$summer <- is_summer(future_df$ds)
future_df$not_summer <- !future_df$summer

# Подгоняем модель:
M12 <- prophet(weekly.seasonality = FALSE) # отключаем автоматическую подгонку
                                           # недельной сезонности
M12 <- add_seasonality(M12, name = 'weekly_summer', 
                       period = 7,
                       fourier.order = 3,
                       condition.name = 'summer') # добавляем летний режим
M12 <- add_seasonality(M12, name = "weekly_not_summer",
                       period = 7, 
                       fourier.order = 3,
                       condition.name = "not_summer") # добавляем нелетний режим
M12 <- fit.prophet(M12, train_df)

forecast_M12 <- predict(M12, future_df)
prophet_plot_components(M12, forecast_M12)

Рис. 3

Согласно полученной модели, в нелетние месяцы стоимость биткоина в течение недели обычно достигает максимума по средам, тогда как в летние месяцы по средам обычно наблюдается противоположная картина.

Регуляризация сезонных составляющих

Подобно тому, как это было с эффектами праздников и других важных событий, мы можем контролировать уровень вклада сезонных составляющих. Глобальный контроль выполняется с помощью аргумента seasonality.prior.scale функции prophet(). Контроль же на уровне отдельных сезонных составляющих возможен с помощью аргумента prior.scale функции add_seasonality(). По умолчанию prior.scale = 10. Уменьшение этого значения приведет к подавлению вклада соответствующего компонента модели.

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

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

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

\[y(t) = g(t) \times s(t) + h(t) + \epsilon_t\]

В приведенном уравнении предполагается, что амплитуда всех сезонных составляющих существенно изменяется во времени. Для подгонки соответствующих моделей необходимо воспользоваться аргументом seasonality.mode функции prophet():

M14 <- prophet(train_df, seasonality.mode = "multiplicative")
forecast_M14 <- predict(M14, future_df)
plot(M14, forecast_M14)

Рис. 4

Однако в Prophet имеется возможность и более гранулярного контроля над аддитивностью сезонных составляющих. Так, например, можно построить модели, в которых недельная составляющая представлена в аддитивном виде, а годовая - в мультипликативном. Вероятно, вы уже догадались, что для этого применяется функция add_seasonality():

M15 <- prophet(yearly.seasonality = FALSE)
M15 <- add_seasonality(M15, name = 'yearly', 
                       period = 365.25,
                       fourier.order = 10, 
                       mode = "multiplicative")
M15 <- fit.prophet(M15, train_df)

forecast_M15 <- predict(M15, future_df)
prophet_plot_components(M15, forecast_M15)

Рис. 6

Обратите внимание: на рис. 6 внизу вклад годовой сезонной компоненты представлен в процентах от уровня тренда.

***

В следующем сообщении мы рассмотрим, как в модель временного ряд можно добавить дополнительные предикторы.

Другие статьи из этой серии:

Послать комментарий

Новые Старые