使用caret按组训练时间序列模型

7
我有一个类似下面的数据集:

set.seed(503)
foo <- data.table(group = rep(LETTERS[1:6], 150),
                  y  = rnorm(n = 6 * 150, mean = 5, sd = 2),
                  x1 = rnorm(n = 6 * 150, mean = 5, sd = 10),
                  x2 = rnorm(n = 6 * 150, mean = 25, sd = 10),
                  x3 = rnorm(n = 6 * 150, mean = 50, sd = 10),
                  x4 = rnorm(n = 6 * 150, mean = 0.5, sd = 10),
                  x5 = sample(c(1, 0), size = 6 * 150, replace = T))

foo[, period := 1:.N, by = group]

问题:我想要为每个组使用变量 x1,...,x5,预测 y 一步;我想在 caret 中运行几个模型以决定要使用哪一个。目前,我正在使用 timeslice 循环来运行它。
window.length <- 115
timecontrol   <- trainControl(method          = 'timeslice',
                            initialWindow     = window.length,
                            horizon           = 1, 
                            selectionFunction = "best",
                            fixedWindow       = TRUE, 
                            savePredictions   = 'final')

model_list <- list()
for(g in unique(foo$group)){
  for(model in c("xgbTree", "earth", "cubist")){
    dat <- foo[group == g][, c('group', 'period') := NULL]
    model_list[[g]][[model]] <- train(y ~ . - 1,
                                      data = dat,
                                      method = model, 
                                      trControl = timecontrol)

  }
}

然而,我希望同时运行所有组,使用虚拟变量来识别每个组,例如:

dat <- cbind(foo,  model.matrix(~ group- 1, foo))
            y         x1       x2       x3            x4 x5 period groupA groupB groupC groupD groupE groupF
  1: 5.710250 11.9615460 22.62916 31.04790 -4.821331e-04  1      1      1      0      0      0      0      0
  2: 3.442213  8.6558983 32.41881 45.70801  3.255423e-01  1      1      0      1      0      0      0      0
  3: 3.485286  7.7295448 21.99022 56.42133  8.668391e+00  1      1      0      0      1      0      0      0
  4: 9.659601  0.9166456 30.34609 55.72661 -7.666063e+00  1      1      0      0      0      1      0      0
  5: 5.567950  3.0306864 22.07813 52.21099  5.377153e-01  1      1      0      0      0      0      1      0

仍然使用timeslice以正确的时间顺序运行时间序列。

是否有一种方法可以在trainControl中声明time变量,使得我的one step ahead预测在每个回合中使用六个更多观测值,并删除前6个观测值?

我可以通过对数据进行排序并操纵horizon参数(给定n组,按时间变量排序并将horizon = n),但如果组数发生变化,则必须更改此内容。而且initial.window将必须为time * n_groups

timecontrol   <- trainControl(method          = 'timeslice',
                            initialWindow     = window.length * length(unique(foo$group)),
                            horizon           = length(unique(foo$group)), 
                            selectionFunction = "best",
                            fixedWindow       = TRUE, 
                            savePredictions   = 'final')

还有其他的方法吗?

2个回答

3
我认为你要找的答案其实很简单。您可以使用trainControl()中的skip参数,以在每个训练/测试集之后跳过所需数量的观测值。通过这种方式,您只会对每个组-期进行一次预测,同一期不会被分割到训练组和测试组之间,并且没有信息泄漏。
使用您提供的示例,如果您将skip = 6horizon = 6(即组数),initialWindow = 115,那么第一个测试集将包括所有116期的组,下一个测试集将包括所有117期的组,以此类推。
library(caret)
library(tidyverse)

set.seed(503)
foo <- tibble(group = rep(LETTERS[1:6], 150),
                  y  = rnorm(n = 6 * 150, mean = 5, sd = 2),
                  x1 = rnorm(n = 6 * 150, mean = 5, sd = 10),
                  x2 = rnorm(n = 6 * 150, mean = 25, sd = 10),
                  x3 = rnorm(n = 6 * 150, mean = 50, sd = 10),
                  x4 = rnorm(n = 6 * 150, mean = 0.5, sd = 10),
                  x5 = sample(c(1, 0), size = 6 * 150, replace = T)) %>% 
  group_by(group) %>% 
  mutate(period = row_number()) %>% 
  ungroup() 

dat <- cbind(foo,  model.matrix(~ group- 1, foo)) %>% 
  select(-group)

window.length <- 115

timecontrol   <- trainControl(
  method            = 'timeslice',
  initialWindow     = window.length * length(unique(foo$group)),
  horizon           = length(unique(foo$group)),
  skip              = length(unique(foo$group)),
  selectionFunction = "best",
  fixedWindow       = TRUE,
  savePredictions   = 'final'
)

model_names <- c("xgbTree", "earth", "cubist")
fits <- map(model_names,
            ~ train(
              y ~ . - 1,
              data = dat,
              method = .x,
              trControl = timecontrol
            )) %>% 
  set_names(model_names)

0

我会使用tidyr::nest()来嵌套分组,然后使用purrr::map()迭代数据。这种方法更加灵活,因为它可以适应不同的组大小、不同数量的组以及传递给caret::train()的变量模型或其他参数。此外,您可以使用furrr轻松地并行运行所有内容。

加载包并创建数据

我使用tibble而不是data.table。我还减小了数据的大小。

library(caret)
library(tidyverse)

set.seed(503)

foo <- tibble(
  group = rep(LETTERS[1:6], 10),
  y  = rnorm(n = 6 * 10, mean = 5, sd = 2),
  x1 = rnorm(n = 6 * 10, mean = 5, sd = 10),
  x2 = rnorm(n = 6 * 10, mean = 25, sd = 10),
  x3 = rnorm(n = 6 * 10, mean = 50, sd = 10),
  x4 = rnorm(n = 6 * 10, mean = 0.5, sd = 10),
  x5 = sample(c(1, 0), size = 6 * 10, replace = T)
) %>%
  group_by(group) %>%
  mutate(period = row_number()) %>%
  ungroup()

减小initialWindow的大小

window.length <- 9
timecontrol   <- trainControl(
  method          = 'timeslice',
  initialWindow     = window.length,
  horizon           = 1,
  selectionFunction = "best",
  fixedWindow       = TRUE,
  savePredictions   = 'final'
)

创建一个函数,该函数将返回适合模型对象的列表。
# To fit each model in model_list to data and return model fits as a list.
fit_models <- function(data, model_list, timecontrol) {
  map(model_list,
      ~ train(
        y ~ . - 1,
        data = data,
        method = .x,
        trControl = timecontrol
      )) %>%
    set_names(model_list)
}

适配模型

model_list <- c("xgbTree", "earth", "cubist")
mods <- foo %>% 
  nest(-group) 

mods <- mods %>%
  mutate(fits = map(
    data,
    ~ fit_models(
      data = .x,
      model_list = model_list,
      timecontrol = timecontrol
    )
  ))

如果您想查看特定组/模型的结果,可以执行以下操作:

mods[which(mods$group == "A"), ]$fits[[1]]$xgbTree

使用furrr进行并行处理

只需使用plan(multiprocess)初始化工作进程,并将map更改为future_map。请注意,如果您的计算机处理核心少于6个,则可能需要将工作进程数量更改为小于6的某个值。

library(furrr)
plan(multiprocess, workers = 6)

mods <- foo %>% 
  nest(-group) 

mods <- mods %>%
  mutate(fits = future_map(
    data,
    ~ fit_models(
      data = .x,
      model_list = model_list,
      timecontrol = timecontrol
    )
  ))

那你只是想要一种更优雅的方式,使 initialWindowhorizon 取决于组大小吗?你在问题结尾提供的代码是否已经给出了你期望的结果? - Giovanni Colitti
不是以上任何一种。我想要运行一个带有组别虚拟变量的模型,而不是为每个组别分别运行不同的模型。这是两件非常不同的事情。我的代码最终实现了第二个选项,这已经可以做到了。我需要一种方法来实现第一个选项,即为所有组别运行单个模型,并考虑时间依赖性。 - Felipe Alvarenga
如果您想适配使用所有组数据的单个模型,那么为什么不能按照周期对数据进行排序,并将其传递给“train”函数呢?您在最后创建的“trainControl”对象应该可以很好地工作,并根据不同的组数进行调整。由于您只使用较早的期间来预测较晚的期间,因此您正在考虑时间因素。您能帮我理解一下为什么这对您无效吗? - Giovanni Colitti
也许你的意思是:当你使用在你的答案末尾创建的trainControl对象时,存在一些信息泄漏,因为在第一个训练/测试集中,你使用了115 * 6个obs来预测接下来的6个obs,而默认情况下,训练集仅添加下一个观察值。所以现在你正在预测A组的117期和B-F组的116期,其中A组的116期已经被添加到训练集中。所以你在训练和测试集中使用了相同的期间?这就是你的意思吗? - Giovanni Colitti
好的,我认为你要寻找 trainControl() 函数中的 skip 参数。请查看我的最新回答。 - Giovanni Colitti
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接