用R中的for循环和ggplot处理promise(rlang)

时间:2019-04-17 18:31:42

标签: r ggplot2 rlang

此脚本的目的是复制类似下图的内容: Hydman's Rolling Time series cross-validationhttps://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

以上代码创建了一个数据框对象,该对象可用于创建所需的图形。对于每次迭代(在原始图中用不同的线表示),它会创建一个与轴上方的迭代“高度”相对应的向量(该列名称始终为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)

哪个产生的输出看起来像这样: ggaddfor-wrong-output

显然那不是我想要的... 为了测试事物,我明确规定了每次迭代。

    # 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)

这将产生所需的输出: ggadd_output

我想将这些函数放入我目前正在编写的程序包中,因此明确规定要添加的内容(就像我在上面所做的那样)是行不通的...

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

1 个答案:

答案 0 :(得分:3)

对我有用的是将enquo()函数中的gg_adding()调用替换为as.symbol(),以便新函数看起来像这样:

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))

我切切熟悉整洁的评估和报价,但并不完全。我了解的是,无论您在aes()列中输入什么内容,都将始终在data列名的上下文中进行评估,首先在图层数据中,然后在全局数据中,除非用户在他的电话(例如aes(fill = "black")等)。由于您的x结构中已经指定了dataggbase的值,因此在您的geom_point()调用中我们不需要它。

我知道这可能是一个没有说服力的技巧,对不起,但ggplot似乎更喜欢处理长数据而不是处理宽数据。我所说的“宽”数据是指您的迭代是一起cbind()编辑的。因此,如果您首先计算每个迭代,然后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)))