Extracting confidence intervals and residuals is failing for recursive panel data in modeltime

Viewed 137

In a former question of mine, I did achieve the extraction of confidence intervals for recursive ensembles in modeltime. However, if I change the data input to a time series panel, the resulting confidence interval columns in the forecast (ex ante) object display NA only.

During the workflow process there must be some error, I did not discover so far I guess. Maybe someone of you is able to help.

# Time Series ML
library(tidymodels)
library(modeltime)
library(modeltime.ensemble)

# Core
library(tidyverse)
library(timetk)


FORECAST_HORIZON <- 24

data = m750    # -----------> Single time series input works fine!

# changing the input to panel results in NA for residuals conf_hi and conf_lo...
data <- m4_monthly %>%
  group_by(id) %>%
  future_frame(
    .length_out = FORECAST_HORIZON,
    .bind_data  = TRUE
  ) %>%
  ungroup()

# data def
lag_transformer_grouped <- function(data){
  data %>%
    group_by(id) %>%
    tk_augment_lags(value, .lags = 1:FORECAST_HORIZON) %>%
    ungroup()
}

data_lags <- data %>%
  lag_transformer_grouped()

test_data <- data_lags %>%
  group_by(id) %>%
  slice_tail(n = 12) %>%
  ungroup()

train_data <- data_lags %>%
  drop_na()

future_data <- data_lags %>%
  filter(is.na(value))

# rec
recipe_spec <- recipe(value ~ date, train_data) %>%
  step_timeseries_signature(date) %>%
  step_rm(matches("(.iso$)|(.xts$)")) %>%
  step_normalize(matches("(index.num$)|(_year$)")) %>%
  step_dummy(all_nominal()) %>%
  step_fourier(date, K = 1, period = 12)

recipe_spec %>% prep() %>% juice()

# elnet 
model_fit_glmnet <- linear_reg(penalty = 1) %>%
  set_engine("glmnet") 

wflw_fit_glmnet <- workflow() %>%
  add_model(model_fit_glmnet) %>%
  add_recipe(recipe_spec %>% step_rm(date)) %>%
  fit(train_data)  %>%
  recursive(
    transform  = lag_transformer_grouped,
    train_tail = panel_tail(train_data, id, FORECAST_HORIZON),
    id         = "id"
  )

# xgboost    
model_fit_xgboost <- boost_tree("regression", learn_rate = 0.35) %>%
  set_engine("xgboost")

wflw_fit_xgboost <- workflow() %>%
  add_model(model_fit_xgboost) %>%
  add_recipe(recipe_spec %>% step_rm(date)) %>%
  fit(train_data) %>%
  recursive(
    transform  = lag_transformer_grouped,
    train_tail = panel_tail(train_data, id, FORECAST_HORIZON),
    id         = "id"
  )

# mtbl
data_models <- modeltime_table(
  wflw_fit_xgboost,
  wflw_fit_glmnet
)

# ensemble
ensemble_fit <- data_models %>% 
  ensemble_weighted(loadings = c(4, 6)) 

# doing calibration for panel with id, as indicated in the documentation 
calibration_tbl <- modeltime_table(
 ensemble_fit) %>%
 modeltime_calibrate(test_data, id = "id", quiet = FALSE) 

# but no residuals for me :(
calib_out <- calibration_tbl$.calibration_data[[1]] %>% 
  left_join(test_data %>% select(id, date, value))

# this function is explicitly for residual extraction. But without panel capability
residuals_method2 <- data_models %>%
  modeltime_calibrate(new_data = test_data) %>%
  modeltime_residuals() 

# Forecast ex ante
data_prepared_tbl <- bind_rows(train_data, test_data)

future_tbl <- data_prepared_tbl %>%
  group_by(id) %>%
  future_frame(.length_out = "2 years") %>%
  ungroup()

# documentation indicates, that calculation of conf_lo and conf_hi should work
ex_ante_obj <- 
  calibration_tbl %>%
  modeltime_forecast(
    new_data    = future_tbl,
    actual_data = data,
    keep_data = TRUE,
    conf_interval = 0.95,
    conf_by_id = TRUE
  )
1 Answers

Fortunately I got some help via github. Thanks again to Matt Dancho!

I will demonstrate the correct workflow below. It was simply the wrong definition of the panel data I used, but see for yourself:

# Time Series ML
library(tidymodels)
library(modeltime)
library(modeltime.ensemble)

# Core
library(tidyverse)
library(timetk)


FORECAST_HORIZON <- 24

# changing the input to panel results in NA for conf_hi and conf_lo...
data <- m4_monthly %>%
  group_by(id) %>%
  ungroup()

# data def
lag_transformer_grouped <- function(data){
  data %>%
    group_by(id) %>%
    tk_augment_lags(value, .lags = 1:FORECAST_HORIZON) %>%
    ungroup()
}

data_lags <- data %>%
  lag_transformer_grouped()

test_data <- data_lags %>%
  group_by(id) %>%
  slice_tail(n = FORECAST_HORIZON) %>%
  ungroup()

train_data <- data_lags %>%
  drop_na()


# rec
recipe_spec <- recipe(value ~ date, train_data) %>%
  step_timeseries_signature(date) %>%
  step_rm(matches("(.iso$)|(.xts$)")) %>%
  step_normalize(matches("(index.num$)|(_year$)")) %>%
  step_dummy(all_nominal()) %>%
  step_fourier(date, K = 1, period = 12)

recipe_spec %>% prep() %>% juice()

# elnet 
model_fit_glmnet <- linear_reg(penalty = 1) %>%
  set_engine("glmnet") 

wflw_fit_glmnet <- workflow() %>%
  add_model(model_fit_glmnet) %>%
  add_recipe(recipe_spec %>% step_rm(date)) %>%
  fit(train_data)  %>%
  recursive(
    transform  = lag_transformer_grouped,
    train_tail = panel_tail(train_data, id, FORECAST_HORIZON),
    id         = "id"
  )

# xgboost    
model_fit_xgboost <- boost_tree("regression", learn_rate = 0.35) %>%
  set_engine("xgboost")

wflw_fit_xgboost <- workflow() %>%
  add_model(model_fit_xgboost) %>%
  add_recipe(recipe_spec %>% step_rm(date)) %>%
  fit(train_data) %>%
  recursive(
    transform  = lag_transformer_grouped,
    train_tail = panel_tail(train_data, id, FORECAST_HORIZON),
    id         = "id"
  )

# mtbl
data_models <- modeltime_table(
  wflw_fit_xgboost,
  wflw_fit_glmnet
)

# ensemble
ensemble_fit <- data_models %>% 
  ensemble_weighted(loadings = c(4, 6)) 

calibration_tbl <- modeltime_table(
  ensemble_fit) %>%
  modeltime_calibrate(test_data, id = "id", quiet = FALSE) 

calib_out <- calibration_tbl$.calibration_data[[1]] %>% 
  left_join(test_data %>% select(id, date, value))


# Forecast ex ante
data_prepared_tbl <- bind_rows(train_data, test_data)

future_tbl <- data_prepared_tbl %>%
  group_by(id) %>%
  future_frame(.length_out = "2 years") %>%
  ungroup()

ex_ante_obj <- 
  calibration_tbl %>%
  modeltime_forecast(
    new_data    = future_tbl,
    actual_data = data,
    keep_data = TRUE,
    conf_interval = 0.95,
    conf_by_id = TRUE
  )
Related