在 R 中使用 for 循环和 ggplot 处理承诺 (rlang)

Handling promises (rlang) with for loops and ggplot in R

此脚本的目的是复制如下图所示的内容: 发现于:https://robjhyndman.com/hyndsight/tscv/

我遇到的问题与(我认为)R 如何处理我在 ggplot 中的承诺有关。

下面是一个重现我的问题的例子。

library(tidyverse)
process_starting_row  <- 600
per_validation_period <- 30
number_of_validations <- 5

graphical_data <- data.frame(x= 1:(process_starting_row + 1 + (number_of_validations)*per_validation_period))

for (it in 1:number_of_validations) {

  # For this graph there is always a line and then a colour component explaining each one...
  graphical_data[,paste0("iteration",it,"line")]   <- c(it)

  # First make the whole row grey and then "dolly up" the colours.
  graphical_data[,paste0("iteration",it,"colour")] <- "grey"
  graphical_data[1:(process_starting_row + (it-1)*per_validation_period), paste0("iteration",it,"colour")] <- "blue"
  graphical_data[(process_starting_row + 1 + (it)*per_validation_period), paste0("iteration",it,"colour")] <- "red"

}
#graphical_data

以上代码创建了一个数据框对象,可用于创建所需的图形。对于每次迭代(在原始图中不同的行),它会创建一个对应于轴上方迭代 "height" 的向量(该列名称始终为 iteration#line 和相应的字符向量 iteration#colour , 每个点的颜色代码。

下一步是创建基本 ggplot 对象。

ggbase <- ggplot(data = graphical_data, aes(x=x)) +
  coord_cartesian(xlim = c(process_starting_row-1*per_validation_period, nrow(graphical_data))) +
  theme_bw()

我希望在这个基础对象上进行迭代。

我写了一个函数,它会添加每个迭代 gg_adding(),然后是另一个 ggaddfor(),其中 运行 是 for 循环。

gg_adding <- function(data, iteration_sub, color_sub){
  iteration_promise <- enquo(iteration_sub)
  colour_promise <- enquo(color_sub)
  gg  <- geom_point(data = data, aes(x= x, y= !! iteration_promise, color = !! colour_promise))
  return(gg)
}

ggaddfor <- function(data, gg){
  ggout <- gg
for (it in 1:number_of_validations) {
  #print(it)
  iterationsub <- paste0("iteration",it,"line")
  coloursub <- paste0("iteration",it,"colour")

  ggout <- ggout + gg_adding(data, iterationsub, coloursub)

  }
  return(ggout)
}

当我 运行 这个函数时,我得到以下信息:

# Not working
ggaddfor(graphical_data, ggbase)

产生如下所示的输出:

显然这不是我所希望的... 为了测试我明确规定了每次迭代。

    # Working...
ggadd <- ggbase
ggadd <- ggadd + gg_adding(graphical_data, iteration1line, iteration1colour)
ggadd <- ggadd + gg_adding(graphical_data, iteration2line, iteration2colour)
ggadd <- ggadd + gg_adding(graphical_data, iteration3line, iteration3colour)
ggadd <- ggadd + gg_adding(graphical_data, iteration4line, iteration4colour)
ggadd <- ggadd + gg_adding(graphical_data, iteration5line, iteration5colour)

这会产生所需的输出:

我想将这些功能放入我目前正在编写的包中,因此明确规定添加(就像我在上面直接做的那样)是行不通的...

我不确定为什么我之前的代码没有产生相同的结果。我对使用 rlang 包处理承诺有些陌生,我怀疑我的错误可能在那里......

对我有用的是用 as.symbol() 替换 gg_adding() 函数中的 enquo() 调用,这样新函数将如下所示:

gg_adding <- function(data, iteration_sub, color_sub){
  iteration_promise <- as.symbol(iteration_sub)
  colour_promise <- as.symbol(color_sub)
  gg  <- geom_point(data = data, aes(x= x, y= !! iteration_promise, color = !! colour_promise))
  return(gg)
}

但是,为了避免每次迭代都重复您的数据,我建议您将此作为您的 geom_point() 调用。

gg  <- geom_point(aes(y= !! iteration_promise, color = !! colour_promise))

我对 tidy 的评估和引用略有了解,但并不完全。我理解的是,无论你在 aes() 中输入什么,都将始终在 data 列名的上下文中进行评估,首先在层的数据中,然后在全局数据中,除非用户在他的电话(例如 aes(fill = "black") 之类的)。因为 xdata 的值已经在您的 ggbase 构造中指定,所以我们在您的 geom_point() 调用中不需要它。

我知道这可能是一个未经请求的提示,我深表歉意,但 ggplot 似乎更喜欢处理长数据而不是宽数据。我对 'wide' 数据的意思是你的迭代有点像 cbind()-ed 在一起。因此,如果您首先计算每个迭代,然后 rbind() 它们一起计算,您可以大大缩短脚本并完全绕过(准)引用内容以产生类似的情节:

new_gr_dat <- lapply(seq_len(number_of_validations), function(it){
  df <- data.frame(x= 1:(process_starting_row + 1 + (number_of_validations)*per_validation_period),
                   line = it, # doubles as y-value and iteration tracker
                   colour = "grey")
  df[1:(process_starting_row + (it-1)*per_validation_period), "colour"] <- "blue"
  df[(process_starting_row + 1 + (it)*per_validation_period), "colour"] <- "red"
  return(df)
})
new_gr_dat <- do.call(rbind, new_gr_dat)

ggplot(new_gr_dat, aes(x = x, y = line, colour = colour)) +
  geom_point() +
  coord_cartesian(xlim = c(process_starting_row-1*per_validation_period, max(new_gr_dat$x)))