“在顶层无法使用 `!!!`”是什么意思,如何解决? (注意:这是一个提问标题,不需要回答问题。)

12
我正在尝试创建一个使用ggplot2创建棒棒糖图的函数。我想将所有参数传递到geom_point()中的aes()中,但是我想从传递到geom_segment()中的aes()中排除size参数(如果您查看下面a()的输出,很明显为什么)。因此,我使用rlang::enquos()捕获...,而不是直接传递它。在我将dots传递给ggplot()中的aes()的函数a()中,这个方法没有问题。但是在函数b()中,我得到了错误信息Can't use '!!!' at top level.

我被卡住了,希望能得到任何输入来解决这个问题。

library(ggplot2)
data("mtcars")

d <- dplyr::count(mtcars, cyl, am)

a <- function(data, x, y, ...) {
  x <- rlang::enquo(x)
  y <- rlang::enquo(y)
  dots <- rlang::enquos(...)

  ggplot(data, aes(!!x, !!y, !!!dots)) +
    geom_segment(aes(y = 0, xend = !!x, yend = !!y)) +
    geom_point()
}

b <- function(data, x, y, ...) {
  x <- rlang::enquo(x)
  y <- rlang::enquo(y)

  dots <- rlang::enquos(...)
  segment_args <- dots[names(dots) != "size"]

  ggplot(data, aes(!!x, !!y)) +
    geom_segment(aes(y = 0, xend = !!x, yend = !!y, !!!segment_args)) +
    geom_point(aes(!!!dots))
}

a(d, cyl, n, color = factor(am), size = am)


b(d, cyl, n, color = factor(am), size = am)
#> Error: Can't use `!!!` at top level.

这是我的sessionInfo()信息:
R version 3.5.2 (2018-12-20)
Platform: x86_64-apple-darwin16.7.0 (64-bit)
Running under: macOS Sierra 10.12.1

Matrix products: default
BLAS: /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib
LAPACK: /usr/local/Cellar/openblas/0.3.5/lib/libopenblasp-r0.3.5.dylib

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods  
[7] base     

other attached packages:
[1] ggplot2_3.2.1

loaded via a namespace (and not attached):
 [1] Rcpp_1.0.3       digest_0.6.18    withr_2.1.2     
 [4] assertthat_0.2.0 crayon_1.3.4     dplyr_0.8.3     
 [7] grid_3.5.2       R6_2.3.0         gtable_0.2.0    
[10] magrittr_1.5     scales_1.0.0     pillar_1.4.2    
[13] rlang_0.4.2      lazyeval_0.2.1   rstudioapi_0.10 
[16] labeling_0.3     tools_3.5.2      glue_1.3.0      
[19] purrr_0.3.3      munsell_0.5.0    compiler_3.5.2  
[22] pkgconfig_2.0.2  colorspace_1.4-0 tidyselect_0.2.5
[25] tibble_2.1.3

我的SessionInfo和你的只有两个关键差异。我正在使用R 3.6.1(不应该有影响),以及lazyeval 0.2.2(最有可能的原因),尽管这个问题表明,lazyeval已被"放弃",转而使用tidy eval。 - NelsonGon
我按照你的代码运行了一下,对我来说完全正常。我的版本是3.5.3。 - MCP_infiltrator
1
@NelsonGon 更新到 lazyeval 版本0.2.2并没有解决问题。我将尝试更新我的 R 版本。 - Thomas Neitmann
1
这并不是一个解释,但似乎在ggplot()中调用!!!算作引用环境,而在geom_point()中调用则不算。 - Romain
1
@NelsonGon 你在哪个操作系统上运行 R? - Thomas Neitmann
显示剩余10条评论
4个回答

10

显然这是aes()的已知问题,可以在这里验证。一个解决方法是:

b <- function(data, x, y, ...) {
  x <- rlang::enquo(x)
  y <- rlang::enquo(y)

  dots <- rlang::enquos(...)
  segment_args <- dots[names(dots) != "size"]

  ggplot(data, aes(!!x, !!y)) +
    geom_segment(aes(, y = 0, xend = !!x, yend = !!y, !!!segment_args)) +
    geom_point(aes(, , !!!dots))
}

请注意 geom_segment() 中只有一个逗号,而在 geom_point() 中有两个逗号。


我认为你可以将此标记为被接受的答案,因为它以最通用的方式回答了这个问题! - Romain

4

如果您按照 rlang 的指示操作,您将获得一些进一步的细节:

> rlang::last_error()
<error>
message: Can't use `!!!` at top level.
class:   `rlang_error`
backtrace:
 1. global::b(d, cyl, n, color = factor(am), size = am)
 4. ggplot2::aes(y = 0, xend = !!x, yend = !!y, !!!segment_args)
 5. rlang::enquos(x = x, y = y, ..., .ignore_empty = "all")
 6. rlang:::endots(...)
 7. rlang:::map(...)
 8. base::lapply(.x, .f, ...)
 9. rlang:::FUN(X[[i]], ...)
Call `rlang::last_trace()` to see the full backtrace

那么

> rlang::last_trace()
    █
 1. └─global::b(d, cyl, n, color = factor(am), size = am)
 2.   ├─ggplot2::geom_segment(aes(y = 0, xend = !!x, yend = !!y, !!!segment_args))
 3.   │ └─ggplot2::layer(...)
 4.   └─ggplot2::aes(y = 0, xend = !!x, yend = !!y, !!!segment_args)
 5.     └─rlang::enquos(x = x, y = y, ..., .ignore_empty = "all")
 6.       └─rlang:::endots(...)
 7.         └─rlang:::map(...)
 8.           └─base::lapply(.x, .f, ...)
 9.             └─rlang:::FUN(X[[i]], ...)

看起来问题出在 !!!segment_args

编辑1:我只是随便试了试,但由于segment_args目前是单个值,我尝试了以下操作,错误确实消失了:

b <- function(data, x, y, ...) {
  x <- rlang::enquo(x)
  y <- rlang::enquo(y)

  dots <- rlang::enquos(...)
  print(dots)
  segment_args <- dots[[setdiff(names(dots), "size")]]
  print(names(dots))

  print(segment_args)

  ggplot(data, aes(!!x, !!y)) +
    geom_segment(aes(y = 0, xend = !!x, yend = !!y, !!segment_args)) +
    geom_point(aes(!!!dots))
}

这只证实了问题出在使用 !!! 上,因为上面的代码现在会在 aes(!!!dots) 处产生错误,这取决于在示例中 segment_args 中只有一个元素,但它可能为进一步调查提供了线索。

2

我认为你不再需要使用引号/取消引号。相反,你可以使用双括号{{ x }},并将点保留为点...

以下内容可行且更易理解:

b <- function(data, x, y, ...) {
  ggplot(data, aes( {{x}} , {{y}} )) +
    geom_segment(aes(y = 0, xend = {{x}}, yend = {{y}}, ...)) +
    geom_point(aes(...))
}

谢谢你的建议,Ismail。我没有保留这些点的原因是因为我不想将 size 参数传递给 geom_segment()。你提出的建议本质上与我的函数 a() 所做的相同,但那不是我想要的。 - Thomas Neitmann

1

编辑2:

您可以覆盖geom_segmentsize值,这样您就不必在引号点之前操纵它们:

b <- function(data, x, y, ...) {
  x <- enquo(x)
  y <- enquo(y)
  dots <- enquos(...)

  ggplot(data, aes(!!x, !!y, !!!dots)) +
    geom_segment(aes(y = 0, xend = !!x, yend = !!y), size = 1) +
    geom_point(aes())
}

b(d, cyl, n)
b(d, cyl, n, color = factor(am))
b(d, cyl, n, color = factor(am), size = am)

编辑:考虑到提供明确的参数,我尝试了这个方法,似乎有效。

b <- function(data, x, y, color, size) {
  x <- enquo(x)
  y <- enquo(y)
  color <- enquo(color)
  size <- enquo(size)

  ggplot(data, aes(!!x, !!y, color = !!color)) +
    geom_segment(aes(y = 0, xend = !!x, yend = !!y)) +
    geom_point(aes(size=!!size))
}

鉴于您的示例,我建议采用以下解决方案,其中所需变量在函数内部创建,而不是从“...”传递,这样您就不必在调用“geom_xxx”时取消引号。
library(dplyr)
library(rlang)
library(ggplot2)

data("mtcars")
d <- dplyr::count(mtcars, cyl, am)

b <- function(data, x, y, aspect) {
  x <- enquo(x)
  y <- enquo(y)
  aspect <- enquo(aspect)

  data <- data %>% mutate(
    color = factor(!!aspect),
    size = !!aspect
  )

  ggplot(data, aes(!!x, !!y, color = color)) +
    geom_segment(aes(y = 0, xend = !!x, yend = !!y)) +
    geom_point(aes(size=size))
}

b(d, cyl, n, am)

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