How to use the rlang `!!!` operator to define a function that wraps around a ggplot call? (Error: Can't use `!!!` at top level)

How to use the rlang `!!!` operator to define a function that wraps around a ggplot call? (Error: Can't use `!!!` at top level)

上下文

阅读插图 Programming with dplyr 我尝试使用 ...!!! 运算符来实现一个函数,该函数将环绕 ggplot 函数并接受任意数量的参数定义数据框中的哪些变量要映射到每种美学。

我的目标

我想定义一个函数 plot_points2() 使得

  1. plot_points2(df, x = x, y = y, color = z) 等同于 df %>% ggplot( mapping = aes(x = x, y = y, color = z) ) + geom_point(alpha = 0.1)
  2. plot_points2(df, x = x, y = z, color = y) 等价于 df %>% ggplot( mapping = aes(x = x, y = z, color = y) ) + geom_point(alpha = 0.1)
  3. plot_points2(df, x = x, y = z) 等同于 df %>% ggplot( mapping = aes(x = x, y = z) ) + geom_point(alpha = 0.1)

什么失败了

require(tidyverse)
require(rlang)

减少示例数据集

df <- tibble(g1= sample(x = c(1,2,3), replace = T, size = 10000),
             g2= sample(x = c("a","b","c"), replace = T, size = 10000),
             x = rnorm(10000, 50, 10),
             y = rnorm(10000, 0, 20) + x*2,
             z = rnorm(10000, 10, 5))
df

我的尝试

plot_points2 <- function(d, ...){
  args <- quos(...)
  print(args)
  ggplot(data = d, mapping = aes(!!!args)) + geom_point(alpha = 0.1)
}
plot_points2(df, x = x, y = y, color = z)

错误

Error: Can't use `!!!` at top level
Call `rlang::last_error()` to see a backtrace 

为什么我认为它应该有效

我认为我想要完成的与 vignette 中的示例没有太大区别,该示例使用这些运算符来创建环绕 mutate() 的函数,并传递定义的多个参数分组变量(事实上,我能够实现一个函数来对上面的示例数据集执行此操作,我将其作为示例发布),但不知何故后者有效而前者无效:

这有效

add_dif_to_group_mean <- function(df, ...) {
  groups <- quos(...)
  df %>% group_by(!!!groups) %>% mutate(x_dif = x-mean(x), 
                                        y_dif = y-mean(y), 
                                        z_dif = z-mean(z))
}

df %>% add_dif_to_group_mean(g1)
df %>% add_dif_to_group_mean(g1, g2)

这不是

plot_points2 <- function(d, ...){
  args <- quos(...)
  print(args)
  ggplot(data = d, mapping = aes(!!!args)) + geom_point(alpha = 0.1)
}
plot_points2(df, x = x, y = y, color = z)

我还读到这个问题可能与 aes() 仅在打印图时才被评估有关,但在那种情况下我认为使用 !! 手动解包应该引发相同的错误,但它不会:

plot_points2b <- function(d, ...){
  args <- quos(...)
  print(args)
  ggplot(data = d, mapping = aes(x = !!args[[1]], 
                                 y = !!args[[2]],
                                 color = !!args[[3]])) + 
    geom_point(alpha = 0.1)
}
plot_points2b(df, x = x, y = y, color = z)

事实上,如果您绘制 3 个变量,最后一个示例可以正常工作,但它不允许您绘制不同于 3

的多个变量

例如:plot_points2b(df, x = x, y = z) 不等同于

df %>% ggplot( mapping = aes(x = x, y = z) ) + geom_point(alpha = 0.1)

相反,它会引发错误:

Error in args[[3]] : subscript out of bounds 

有人知道我在这里缺少什么概念吗?提前致谢!

正如我在评论中提到的,我认为您的环境中可能已经有 x 和 y,这就是您的某些代码可以正常工作的原因。我不完全确定你想要实现什么,但我认为你为了让你的代码没有错误地 运行 做了太多的 rlang。

例如:

plot_points <- function(d, ...){    
    ggplot(data = d, mapping = aes(x = x, y = y)) + 
     geom_point(alpha = 0.1)
}
plot_points (df, x, y)

将使您的情节毫无理由地增加 !!!enquo() 的开销和复杂性。

你在这里也走在这条路上,这个更简单的代码工作得很好:

add_dif_to_group_mean <- function(., ...) {

    df %>% group_by(g1) %>% mutate(x_dif = x-mean(x), 
                                          y_dif = y-mean(y), 
                                          z_dif = z-mean(z))
}

df %>% add_dif_to_group_mean(g1)

同样:

plot_points2 <- function(d, ...){
    ggplot(data = d, mapping = aes(x=x, y=y, color=z)) + 
    geom_point(alpha = 0.1)
}
plot_points2(df, x = x, y = y, color = z)

据我所知工作正常。

所以我了解到,也许您正在研究书中的示例,这很棒。但我认为某处遗漏了一个问题,所以你必须在现实世界的功能中做所有额外的事情。例如,也许您想传递 "x""y" 之类的字符串,而不是 xy?

您的具体用例是 ?aes 中的示例。 aes 自动引用其参数。一个人可以简单地直接通过点。尝试:

plot_points3 <- function(d, ...){
  print(aes(...))
  ggplot(d, aes(...)) + geom_point(alpha = 0.1)
}
plot_points3(df, x = x, y = y, color = z)

这很好地打印:

Aesthetic mapping: 
* `x`      -> `x`
* `y`      -> `y`
* `colour` -> `z`

并生成所需的图。