英文:
What does 'Can't use `!!!` at top level.' mean and how to resolve it?
问题
我正在尝试创建一个用于创建棒棒糖图的函数,使用ggplot2
。我想要将所有参数传递给geom_point()
中的aes()
,但是,我想要排除size
参数不传递给geom_segment()
中的aes()
(如果您查看下面的a()
的输出,就会明显为什么)。因此,我使用rlang::enquos()
来捕获...
,而不是直接传递它。在函数a()
中,我将dots
传递给ggplot()
中的aes()
,这没有问题。但在函数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
英文:
I am trying to create a function for creating lollipop plots using ggplot2
. I would like to pass all argument within ...
to aes()
within geom_point()
. However, I'd like to exclude the size
argument from passing onto aes()
within geom_segment()
(for obvious reasons if you look at the ouput of a()
below). Therefore I capture ...
using rlang::enquos()
instead of just passing it on as is. In function a()
where I pass the dots
to aes()
within ggplot()
this works without a problem. But in function b()
I get the error Can't use '!!!' at top level.
I am stuck at this point and would appreciate any input to solve this issue.
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.
Here is my 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
答案1
得分: 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()
中有两个逗号。
英文:
Apparently this is a known issue of aes()
as you can verify here. A workaround is this:
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))
}
Notice the single comma in geom_segment()
and the double comma in geom_point()
.
答案2
得分: 4
如果你按照rlang的指示操作,你会得到一些进一步的细节:
> rlang::last_error()
<error>
message: 无法在顶层使用 `!!!`。
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]], ...)
调用 `rlang::last_trace()` 以查看完整的回溯
然后
> 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
只有一个元素,但这可能为进一步的调查提供了一些线索。
英文:
If you follow the instructions of rlang, you get some further details:
> 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
Then
> 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]], ...)
So it appears the issue is with !!!segment_args
EDIT 1: just playing around but since segment_args is currently a single value, I tried the following and the error indeed disappears:
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))
}
This only confirms that the issue is with the usage of !!! since the above gives now an error for aes(!!!dots)
instead and it depends on the fact that in the example there is only one element in segment_args, but it may give a handhold for further investigation
答案3
得分: 2
我认为你不需要再使用引号了。相反,你可以使用双大括号 {{ x }}
并保留点作为点 ...
以下内容可以正常工作,而且更容易理解:
b <- function(data, x, y, ...) {
ggplot(data, aes( {{x}} , {{y}} )) +
geom_segment(aes(y = 0, xend = {{x}}, yend = {{y}}, ...)) +
geom_point(aes(...))
}
英文:
I don't think you need to quote / unquote anymore. Instead, you can use the double bracket {{ x }}
and leave the dots as dots ...
The following works and is much easier to understand:
b <- function(data, x, y, ...) {
ggplot(data, aes( {{x}} , {{y}} )) +
geom_segment(aes(y = 0, xend = {{x}}, yend = {{y}}, ...)) +
geom_point(aes(...))
}
答案4
得分: 1
EDIT 2:
你可以重写 geom_segment
中的 size
值,这样你就不必在之前处理带引号的点了:
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)
EDIT:考虑到我关于提供显式参数的评论,我尝试了这个方法,似乎可以工作:
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)
英文:
EDIT 2 :
You could override the size
value for geom_segment
, so that you don't have to manipulate the quoted dots before :
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)
EDIT : given my commentary about providing explicit argument, I tried this and it seems to work
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))
}
Given your example, I'd suggest the following workaround where the needed variables are created within the function rather than being passed from ...
, so that you don't have to unquote within calls to 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)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论