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
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()
.
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
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(...))
}
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)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With