【问题标题】:What does 'Can't use `!!!` at top level.' mean and how to resolve it?'Can't use `!!!` at top level.' 是什么意思?是什么意思以及如何解决?
【发布时间】:2020-04-22 00:08:10
【问题描述】:

我正在尝试使用ggplot2 创建一个用于创建棒棒糖图的函数。我想将... 中的所有参数传递给geom_point() 中的aes()。但是,我想排除 size 参数 传递到 geom_segment() 内的 aes() (原因很明显,如果您查看下面的 a() 的输出)。因此,我使用rlang::enquos() 捕获...,而不是按原样传递它。在函数a() 中,我将dots 传递给aes()ggplot() 中,这没有问题。但是在函数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
  • 我的 SessionInfo 和你的只有两个关键区别。我正在使用 R 3.6.1(应该没关系)和 lazyeval 0.2.2(最可能的原因),尽管这 issue 表明 lazyeval 被“放弃”以支持整洁的评估。
  • @NelsonGon 更新到 lazyeval 的 0.2.2 版没有解决问题。我会尝试更新我的R 版本。
  • 这不是解释,但似乎在ggplot() 内调用!!! 确实算作引用环境,而在geom_point() 内调用它则不算
  • @NelsonGon 您在哪个操作系统上运行 R?

标签: r ggplot2 tidyverse rlang non-standard-evaluation


【解决方案1】:

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

以下内容有效并且更容易理解:

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

【讨论】:

  • 感谢伊斯梅尔的建议。我没有保持原样的原因是因为我不想将size 参数传递给geom_segment()。你的提议基本上和我的函数 a() 一样,但这不是我想要的。
【解决方案2】:

如果您按照 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 中只有一个元素这一事实,但它可能会为进一步调查提供帮助

【讨论】:

    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

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

      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() 中的双逗号。

      【讨论】:

      • 我认为您可以将此标记为已接受的答案,因为它以最通用的方式回答了问题!
      猜你喜欢
      • 1970-01-01
      • 2010-11-22
      • 2017-02-27
      • 2021-06-25
      • 2013-08-04
      • 1970-01-01
      • 2021-12-04
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多