В этом сообщении мы продолжим начатое ранее знакомство с основными параметрами функции prophet() из одноименного пакета для R. В частности, мы рассмотрим, как в модели можно добавить эффекты "праздников". Употребляемый здесь термин "праздник" есть результат прямого перевода термина "holiday", принятого в Prophet. В действительности под "праздниками" мы будем понимать как "настоящие" официальные праздничные и выходные дни (например, Новый год, Рождество и т.п.), так и другие события, во время которых свойства моделируемой зависимой переменной существенно изменяются (спортивные или культурные мероприятия, природные явления и т.п.). Поэтому термины "праздник" и "событие" будут использованы ниже как синонимы.

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

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

Представление "праздников" в моделях

Как было отмечено ранее, для добавления эффектов "праздников" в Prophet-модель необходимо сначала создать отдельную таблицу, содержащую как минимум два обязательных столбца: holiday (названия "праздников" и других важных для моделирования событий) и ds (даты в стандартном для R формате YYYY-MM-DD). Важно, чтобы эта таблица охватывала как исторический период, на основе которого происходит обучение модели, так и период в будущем, для которого необходимо сделать прогноз. Например, если какое-то важное событие встречается в обучающих данных, то его следует указать и для прогнозного периода (при условии, конечно, что мы ожидаем повторение этого события в будущем, и что дата этого события входит в прогнозный период).

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

key_dates <- dplyr::tibble(
    holiday = c("Bitcoin_Cash_hard_fork",                     # Создание Bitcoin Cash
               "China_ICO_ban",                               # Запрет ICO в Китае
               "South_Korea_announces_regulations",           # Введение регуляций в Ю. Корее
               "CoinMarketCap_removes_South_Korean_prices",   # Удаление южнокорейского рынка 
                                                              # из трекера цен CoinMarketCap
               "Bitcoin_Cash_SV_and_Bitcoing_Cash_ABC_fork"), # Разветвление Bitcoin Cash
    ds = as.Date(c(
        "2017-08-01",
        "2017-09-04",
        "2017-12-28",
        "2018-01-08",
        "2018-11-15"
    ))
)

Теперь добавим эти события в модель (см. аргумент holidays):

M5 <- prophet(train_df, holidays = key_dates,
              changepoint.range = 0.9)
forecast_M5 <- predict(M5, future_df)

# Графическое представление компонентов модели:
prophet_plot_components(M5, forecast_M5)

Рис. 1

На рис. 1 приведены оцененные компоненты модели, включая эффекты событий, которые мы добавили с помощью аргумента holidays (см. второй график сверху). При желании можно воспользоваться функцией plot_forecast_component() и изобразить эффекты отдельных событий (на аргумент name этой функции нужно подать имя соответствующего события; см. рис. 2):

plot_forecast_component(M5, forecast_M5,
                        name = "Bitcoin_Cash_SV_and_Bitcoing_Cash_ABC_fork")

Рис. 2

На аргумент name функции plot_forecast_component() можно также подать значение "holidays", что приведет к изображению на отдельном графике эффектов всех включенных в  модель событий (рис. 3):

plot_forecast_component(M5, forecast_M5, name = "holidays")

Рис. 3

Наступление некоторых событий в истории биткоина было известно заранее, как например разветвление Bitcoin Cash на Bitcoin Cash SV и Bitcoin Cash ABC. Поэтому многие спекулянты начали скупать Bitcoin Cash за несколько дней до наступления "вилки", или "форка", чтобы впоследствии удвоить свое богатство (поскольку при наступлении "форка" владелец старой монеты автоматически становится владельцем и новой монеты). В связи с этим имело бы смысл моделировать эффект "Bitcoin Cash SV and Bitcoing Cash ABC fork" в виде события, которое имело некоторую "предысторию", т.е. эффект этого события начал проявлять себя за несколько дней до главной даты (в данном случае - 15 ноября 2018 г.). Prophet позволяет сделать это путем добавления в таблицу с перечнем моделируемых событий столбцов lower_window (определят длительность "предыстории") и upper_window (определяет длительность периода, в течение которого эффект события все еще имел место после главной даты события). Предположим, что в рассматриваемом примере спекулянты начали скупать Bitcoin Cash за две недели до "вилки" (lower_window = -14) и прекратили делать это сразу после "вилки" (upper_window = 0):

key_dates2 <- dplyr::bind_cols(key_dates, 
                               lower_window = c(0, 0, 0, 0, -14),
                               upper_window = c(0, 0, 0, 0, 0))

M6 <- prophet(train_df,
              holidays = key_dates2,
              changepoint.range = 0.9)
forecast_M6 <- predict(M6, future_df)

# Эффект события с "предысторией":
plot_forecast_component(M6, forecast_M6,
                        name = "Bitcoin_Cash_SV_and_Bitcoing_Cash_ABC_fork")

Рис. 4 

Как видно из рис. 4, теперь эффект "Bitcoin Cash SV and Bitcoing Cash ABC fork" включает несколько дней до главной даты этого события (сравните с рис. 2).

Встроенные даты официальных праздников и выходных дней

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

Конечно, мы могли бы воспользоваться описанным выше способом для включения таких событий в модель, т.е. путем создания таблицы, подобной key_dates или key_dates2. К счастью, в большинстве случаев в этом не будет необходимости - в Prophet уже включены даты официальных праздников и выходных дней для более чем 60 стран (см. список стран здесь, а также здесь). Эти "встроенные праздники" охватывают период с 1995 по 2044 гг. Для их добавления в модель служит функция add_country_holidays(), которая принимает два аргумента: m (модельный объект) и country_name (название страны: например, "Russia" или "RU"). Для примера построим модель, которая включает как рассмотренные выше важные в истории биткоина одноразовые события, так и регулярные официальные праздники США:

# Обратите внимание: здесь мы инициализируем объект M7,
# но пока не подаем на него таблицу с обучающими данными
M7 <- prophet(holidays = key_dates2, changepoint.range = 0.9)

# Добавляем официальные праздничные дни США:
M7 <- add_country_holidays(M7, country_name = 'US')

# Подгонка модели (обратите внимание на использование функции fit.prophet()):
M7 <- fit.prophet(M7, train_df)
forecast_M7 <- predict(M7, future_df)

# Графическое изображение эффектов всех событий:
plot_forecast_component(M7, forecast_M7, name = "holidays")

Рис. 5

На рис. 5 показаны оцененные эффекты всех включенных в модель M7 событий. Просмотреть названия этих событий можно следующим образом:

M7$train.holiday.names

## [1] "Bitcoin_Cash_hard_fork"                      "China_ICO_ban"                             
## [3] "1South_Korea_announces_regulations"          "CoinMarketCap_removes_South_Korean_prices" 
## [5] "Bitcoin_Cash_SV_and_Bitcoing_Cash_ABC_fork"  "New Year's Day"                            
## [7] "Martin Luther King, Jr. Day"                 "Washington's Birthday"                     
## [9] "Memorial Day"                                "Independence Day"                          
## [11] "Labor Day"                                  "Columbus Day"                              
## [13] "Veterans Day"                               "Thanksgiving"                              
## [15] "Christmas Day"                              "Christmas Day (Observed)"                  
## [17] "New Year's Day (Observed)"                  "Veterans Day (Observed)"

Регуляризация эффектов "праздников"

В Prophet имеется возможность подавлять величину эффектов "праздников", что может оказаться полезным при возникновении переобучения модели. Такой контроль (т.е. регуляризация) осуществляется одним из двух способов:
  • глобальный контроль - распространяется на все моделируемые события;
  • индивидуальный контроль на уровне отдельных событий.
Первый из этих способов осуществляется с помощью аргумента holidays.prior.scale функции prophet(). Данный аргумент задает априорное значение стандартного отклонения (нормального) распределения, с помощью которого моделируется эффект того или иного события. По умолчанию "holidays.prior.scale = 10", что соответствует очень незначительной регуляризации (кстати, у функции prophet() есть также похожий аргумент seasonality.prior.scale, который предназначен для глобальной регуляризации сезонных компонентов модели). Уменьшение этого принятого по умолчанию значения приведет к "подавлению" эффектов всех событий, включенных в модель:

# Для глобальной регуляризации эффектов праздников служит аргумент
# holidays.prior.scale:
M8 <- prophet(holidays = key_dates2,
              changepoint.range = 0.9,
              holidays.prior.scale = 0.01)

M8 <- add_country_holidays(M8, country_name = 'US')
M8 <- fit.prophet(M8, train_df)
forecast_M8 <- predict(M8, future_df)

# Эффекты праздников до (модель M7) и после (M8) глобальной регуляризации:
m7_holidays <- plot_forecast_component(M7, forecast_M7, name = "holidays") +
    labs(title = "Модель M7") +
    ylim(c(-0.15, 0.25))
m8_holidays <- plot_forecast_component(M8, forecast_M8, name = "holidays") +
    labs(title = "Модель M8") +
    ylim(c(-0.15, 0.25))
gridExtra::grid.arrange(m7_holidays, m8_holidays, nrow = 1)

Рис. 6

Рис. 6 наглядно демонстрирует снижение влияния большинства событий в модели M8 после применения глобальной регуляризации.

Для управления эффектами отдельных событий в таблицу с перечнем событий необходимо добавить столбец prior_scale. В качестве примера, уменьшим величину эффекта события "Bitcoin Cash SV and Bitcoing Cash ABC fork", оставив остальные уровни как есть (т.е. используя принятое по умолчанию значение параметра prior_scale = 10):

key_dates3 <- dplyr::bind_cols(key_dates2, 
                               prior_scale = c(10, 10, 10, 10, 0.01))

M9 <- prophet(holidays = key_dates3,
              changepoint.range = 0.9)

M9 <- add_country_holidays(M9, country_name = 'US')
M9 <- fit.prophet(M9, train_df)
forecast_M9 <- predict(M9, future_df)

m9_holidays <- plot_forecast_component(M9, forecast_M9, name = "holidays") +
    labs(title = "Модель M9") +
    ylim(c(-0.15, 0.25))
gridExtra::grid.arrange(m7_holidays, m9_holidays, nrow = 1)

Рис. 7

На рис. 7 хорошо виден результат подавления эффекта события "Bitcoin Cash SV and Bitcoing Cash ABC fork" (сравните уровень самого большого пика на графике слева с уровнем пика в той же позиции на графике справа). В то же время эффекты большинства других событий и официальных праздников остались почти неизменными.

***

В следующем сообщении из этой серии мы рассмотрим особенности моделирования сезонных колебаний во временных рядах. Подпишитесь на рассылку, чтобы не пропустить ничего интересного!

3 Комментарии

Анонимный написал(а)…
Подписался, статья супер,спасибо!
Unknown написал(а)…
Спасибо за доходчивое объяснение
Slavapro написал(а)…
Очень интересное детально изложение внутренностей Prophet. Пока я его не встретил приходилось мириться со скудной оригинальной документацией, пестрящей отсылками на не менее сложные статьи или заниматься изучением исходников. Именно из них я узнал про holidays_prior_scale=10 по умолчанию. Большинство "простых" примеров из интернет-статей об этом просто умалчивают. Скажите, а откуда вы брали информацию при написании этой статьи? Я правильно понимаю, что неважен знак показателя "праздника"? Еще интересное наблюдение - в модели, которую я построил на своих данных и которые точно зависят от праздников при добавлении "стандартного" набора наблюдается ситуация, что все метрики при кросс-валидации "с праздниками" выше чем без. Единственный вариант который приходит на ум - это наличие "переносных" дней, которые приклеивают к праздникам. После проверки этой гипотезы, идей у меня больше не останется.
Новые Старые