aes_string
有一些我在使用 ggplot2 编程时使用的便捷行为。但是 aes_string
已被弃用(我相信从 ggplot2 版本 3.4.0 开始就很明显)。我正在努力思考如何很好地替换它。
具体来说,我之前创建了通过省略号接受任意字符串参数的函数,并通过 do.call 将这些参数传递给 aes_string,如下面的第一个表示所示。
自从注意到弃用警告后,我一直试图避免
aes_string
,并发现自己实际上只是以一种相当“hacky”的方式模仿它。
据推测,aes_string
中的任何缺陷导致其被弃用,也适用于我的黑客解决方法。请参阅第二个代表。
有更优雅的解决方案吗?我想继续将变量名称作为字符串传递。
library(ggplot2)
plotterOld <- function(...) {
args <- list(...)
pointAes <- do.call(aes_string, args = args)
ggplot(mpg, aes(displ, cty)) +
geom_point(mapping = pointAes)
}
plotterOld(colour = "cyl", size = "year")
#> Warning: `aes_string()` was deprecated in ggplot2 3.0.0.
#> ℹ Please use tidy evaluation ideoms with `aes()`
# it can accept NULLs, and e.g. intuitively doesn't map size to anything
plotterOld(colour = "cyl", size = NULL)
# no arguments also works fine
plotterOld()
于 2022 年 11 月 11 日使用 reprex v2.0.2 创建
library(ggplot2)
# arbitrary aesthetics passed as strings using ellipses, aes, quo and .data
myAesString <- function(...) {
dots <- list(...)
# early exits
stopifnot(rlang::is_named2(dots))
if (length(dots) == 0) {
return(NULL)
}
# initialise empty mapping object and fill it with quosures where appropriate
mapping <- aes()
for (n in names(dots)) {
v <- dots[[n]]
if (!is.null(v)) {
if (!rlang::is_string(v)) stop(n, " must be a string or NULL")
mapping[[n]] <- quo(.data[[v]])
}
}
return(mapping)
}
plotterNew <- function(...) {
pointAes <- myAesString(...)
ggplot(mpg, aes(displ, cty)) +
geom_point(mapping = pointAes)
}
plotterNew(colour = "cyl", size = "year")
plotterNew(colour = "cyl", size = NULL, shape = "drv")
plotterNew()
# seems to work fine
p <- plotterNew(colour = "cyl", size = "year")
p$layers[[1]]$mapping
#> Aesthetic mapping:
#> * `colour` -> `.data[["cyl"]]`
#> * `size` -> `.data[["year"]]`
创建于 2022 年 11 月 11 日,使用 reprex v2.0.2
会议信息sessioninfo::session_info()
#> ─ Session info ───────────────────────────────────────────────────────────────
#> setting value
#> version R version 4.2.1 (2022-06-23)
#> os macOS Big Sur ... 10.16
#> system x86_64, darwin17.0
#> ui X11
#> language (EN)
#> collate en_GB.UTF-8
#> ctype en_GB.UTF-8
#> tz Europe/Amsterdam
#> date 2022-11-11
#> pandoc 2.18 @ /Applications/RStudio.app/Contents/MacOS/quarto/bin/tools/ (via rmarkdown)
#>
#> ─ Packages ───────────────────────────────────────────────────────────────────
#> package * version date (UTC) lib source
#> assertthat 0.2.1 2019-03-21 [1] CRAN (R 4.2.0)
#> cli 3.4.1 2022-09-23 [1] CRAN (R 4.2.0)
#> colorspace 2.0-3 2022-02-21 [1] CRAN (R 4.2.0)
#> curl 4.3.3 2022-10-06 [1] CRAN (R 4.2.0)
#> DBI 1.1.3 2022-06-18 [1] CRAN (R 4.2.0)
#> digest 0.6.30 2022-10-18 [1] CRAN (R 4.2.1)
#> dplyr 1.0.10 2022-09-01 [1] CRAN (R 4.2.0)
#> evaluate 0.18 2022-11-07 [1] CRAN (R 4.2.0)
#> fansi 1.0.3 2022-03-24 [1] CRAN (R 4.2.0)
#> farver 2.1.1 2022-07-06 [1] CRAN (R 4.2.0)
#> fastmap 1.1.0 2021-01-25 [1] RSPM (R 4.2.0)
#> fs 1.5.2 2021-12-08 [1] RSPM (R 4.2.0)
#> generics 0.1.3 2022-07-05 [1] CRAN (R 4.2.0)
#> ggplot2 * 3.4.0 2022-11-04 [1] CRAN (R 4.2.1)
#> glue 1.6.2 2022-02-24 [1] CRAN (R 4.2.0)
#> gtable 0.3.1 2022-09-01 [1] CRAN (R 4.2.0)
#> highr 0.9 2021-04-16 [1] RSPM (R 4.2.0)
#> htmltools 0.5.3 2022-07-18 [1] CRAN (R 4.2.0)
#> httr 1.4.4 2022-08-17 [1] CRAN (R 4.2.0)
#> knitr 1.40 2022-08-24 [1] CRAN (R 4.2.0)
#> labeling 0.4.2 2020-10-20 [1] CRAN (R 4.2.0)
#> lifecycle 1.0.3 2022-10-07 [1] CRAN (R 4.2.0)
#> magrittr 2.0.3 2022-03-30 [1] CRAN (R 4.2.0)
#> mime 0.12 2021-09-28 [1] RSPM (R 4.2.0)
#> munsell 0.5.0 2018-06-12 [1] CRAN (R 4.2.0)
#> pillar 1.8.1 2022-08-19 [1] CRAN (R 4.2.0)
#> pkgconfig 2.0.3 2019-09-22 [1] CRAN (R 4.2.0)
#> purrr 0.3.5 2022-10-06 [1] CRAN (R 4.2.0)
#> R.cache 0.16.0 2022-07-21 [1] CRAN (R 4.2.0)
#> R.methodsS3 1.8.2 2022-06-13 [1] CRAN (R 4.2.0)
#> R.oo 1.25.0 2022-06-12 [1] CRAN (R 4.2.0)
#> R.utils 2.12.1 2022-10-30 [1] CRAN (R 4.2.0)
#> R6 2.5.1 2021-08-19 [1] CRAN (R 4.2.0)
#> reprex 2.0.2 2022-08-17 [1] CRAN (R 4.2.0)
#> rlang 1.0.6 2022-09-24 [1] CRAN (R 4.2.0)
#> rmarkdown 2.18 2022-11-09 [1] CRAN (R 4.2.1)
#> rstudioapi 0.14 2022-08-22 [1] CRAN (R 4.2.0)
#> scales 1.2.1 2022-08-20 [1] CRAN (R 4.2.0)
#> sessioninfo 1.2.2 2021-12-06 [1] RSPM (R 4.2.0)
#> stringi 1.7.8 2022-07-11 [1] CRAN (R 4.2.0)
#> stringr 1.4.1 2022-08-20 [1] CRAN (R 4.2.0)
#> styler 1.8.1 2022-11-07 [1] CRAN (R 4.2.0)
#> tibble 3.1.8 2022-07-22 [1] CRAN (R 4.2.0)
#> tidyselect 1.2.0 2022-10-10 [1] CRAN (R 4.2.0)
#> utf8 1.2.2 2021-07-24 [1] CRAN (R 4.2.0)
#> vctrs 0.5.0 2022-10-22 [1] CRAN (R 4.2.0)
#> withr 2.5.0 2022-03-03 [1] CRAN (R 4.2.0)
#> xfun 0.34 2022-10-18 [1] CRAN (R 4.2.0)
#> xml2 1.3.3 2021-11-30 [1] RSPM (R 4.2.0)
#> yaml 2.3.6 2022-10-18 [1] CRAN (R 4.2.1)
#>
#> [1] /Library/Frameworks/R.framework/Versions/4.2/Resources/library
#>
#> ──────────────────────────────────────────────────────────────────────────────
````
一种选择是使用
sym
: 将带引号的字符串列表转换为符号
library(ggplot2)
plotterOld <- function(...) {
args <- lapply(list(...), function(x) if (!is.null(x)) sym(x))
pointAes <- do.call(aes, args = args)
ggplot(mpg, aes(displ, cty)) +
geom_point(mapping = pointAes)
}
更新 我们可以通过使用
!!!
来摆脱 do.call
来进一步简化:
plotterOld <- function(...) {
args <- lapply(list(...), function(x) if (!is.null(x)) sym(x))
ggplot(mpg, aes(displ, cty)) +
geom_point(mapping = aes(!!!args))
}
plotterOld(colour = "cyl", size = "year")
plotterOld(colour = "cyl", size = NULL)
plotterOld()
您可以使用
ensyms
将命名字符串参数转换为命名符号参数,因此相当于旧的绘图函数可以是
library(ggplot2)
plotterNew <- function(...) {
ggplot(mpg, aes(displ, cty)) +
geom_point(mapping = aes(!!!ensyms(...)))
}
plotterNew(colour = "cyl", size = "year")
创建于 2022 年 11 月 12 日,使用 reprex v2.0.2
Stefan 的建议
sym()
和 Allan Cameron 的建议 ensyms()
的答案都确实帮助我找到了理解这个问题的正确方向,所以这一切都归功于他们。
这个答案比较了两种方法,并添加了第三种方法
data_sym()
aes_string 的两种行为,如下所示,我能够使用 sym() 进行复制,但不能使用 ensyms()
library(rlang)
library(ggplot2)
plotterAesString <- function(...) {
pointAes <- do.call(aes_string, args = list(...))
ggplot(mpg, aes(displ, cty)) +
geom_point(mapping = pointAes)
}
colourObject <- "cyl"
plotterAesString(colour = colourObject, size = NULL, shape = "drv")
#> Warning: `aes_string()` was deprecated in ggplot2 3.0.0.
#> ℹ Please use tidy evaluation ideoms with `aes()`
Stefan 对 sym() 的建议引导我采用了这种方法, 这似乎是使用 aes_string 的一个很好的替代品
plotterSym <- function(...) {
args <- list(...)
args <- lapply(args, function(x) if (is_string(x)) sym(x) else x)
ggplot(mpg, aes(displ, cty)) + geom_point(mapping = aes(!!!args))
}
plotterSym(colour = colourObject, size = NULL, shape = "drv")
我发布这个答案是因为 rlang 中的 data_sym() 函数 防止意外匹配的问题 环境变量而不是数据变量。 (原始的 aes_string 方法也有这种行为!)
driv <- "misspelled variable?"
plotterSym(shape = "driv")
plotterDataSym <- function(...) {
args <- list(...)
args <- lapply(args, function(x) if (is_string(x)) data_sym(x) else x)
ggplot(mpg, aes(displ, cty)) + geom_point(mapping = aes(!!!args))
}
它的工作原理就像 sym 方法
plotterDataSym(colour = colourObject, size = NULL, shape = "drv")
但是拼写错误的名称会出现错误,这对我来说是理想的行为。
plotterDataSym(shape = "driv")
#> Error in `geom_point()`:
#> ! Problem while computing aesthetics.
#> ℹ Error occurred in the 1st layer.
#> Caused by error in `.data$driv`:
#> ! Column `driv` not found in `.data`.
#> Backtrace:
#> ▆
#> 1. ├─base::tryCatch(...)
#> 2. │ └─base (local) tryCatchList(expr, classes, parentenv, handlers)
#> 3. │ ├─base (local) tryCatchOne(...)
#> 4. │ │ └─base (local) doTryCatch(return(expr), name, parentenv, handler)
#> 5. │ └─base (local) tryCatchList(expr, names[-nh], parentenv, handlers[-nh])
#> 6. │ └─base (local) tryCatchOne(expr, names, parentenv, handlers[[1L]])
#> 7. │ └─base (local) doTryCatch(return(expr), name, parentenv, handler)
#> 8. ├─base::withCallingHandlers(...)
#> 9. ├─base::saveRDS(...)
#> 10. ├─base::do.call(...)
#> 11. ├─base (local) `<fn>`(...)
#> 12. ├─global `<fn>`(input = base::quote("pious-rhino_reprex.R"))
#> 13. │ └─rmarkdown::render(input, quiet = TRUE, envir = globalenv(), encoding = "UTF-8")
#> 14. │ └─knitr::knit(knit_input, knit_output, envir = envir, quiet = quiet)
#> 15. │ └─knitr:::process_file(text, output)
#> 16. │ ├─base::withCallingHandlers(...)
#> 17. │ ├─knitr:::process_group(group)
#> 18. │ └─knitr:::process_group.block(group)
#> 19. │ └─knitr:::call_block(x)
#> 20. │ └─knitr:::block_exec(params)
#> 21. │ └─knitr:::eng_r(options)
#> 22. │ ├─knitr:::in_input_dir(...)
#> 23. │ │ └─knitr:::in_dir(input_dir(), expr)
#> 24. │ └─knitr (local) evaluate(...)
#> 25. │ └─evaluate::evaluate(...)
#> 26. │ └─evaluate:::evaluate_call(...)
#> 27. │ ├─evaluate (local) handle(...)
#> 28. │ │ └─base::try(f, silent = TRUE)
#> 29. │ │ └─base::tryCatch(...)
#> 30. │ │ └─base (local) tryCatchList(expr, classes, parentenv, handlers)
#> 31. │ │ └─base (local) tryCatchOne(expr, names, parentenv, handlers[[1L]])
#> 32. │ │ └─base (local) doTryCatch(return(expr), name, parentenv, handler)
#> 33. │ ├─base::withCallingHandlers(...)
#> 34. │ ├─base::withVisible(value_fun(ev$value, ev$visible))
#> 35. │ └─knitr (local) value_fun(ev$value, ev$visible)
#> 36. │ └─knitr (local) fun(x, options = options)
#> 37. │ ├─base::withVisible(knit_print(x, ...))
#> 38. │ ├─knitr::knit_print(x, ...)
#> 39. │ └─knitr:::knit_print.default(x, ...)
#> 40. │ └─evaluate (local) normal_print(x)
#> 41. │ ├─base::print(x)
#> 42. │ └─ggplot2:::print.ggplot(x)
#> 43. │ ├─ggplot2::ggplot_build(x)
#> 44. │ └─ggplot2:::ggplot_build.ggplot(x)
#> 45. │ └─ggplot2:::by_layer(...)
#> 46. │ ├─rlang::try_fetch(...)
#> 47. │ │ ├─base::tryCatch(...)
#> 48. │ │ │ └─base (local) tryCatchList(expr, classes, parentenv, handlers)
#> 49. │ │ │ └─base (local) tryCatchOne(expr, names, parentenv, handlers[[1L]])
#> 50. │ │ │ └─base (local) doTryCatch(return(expr), name, parentenv, handler)
#> 51. │ │ └─base::withCallingHandlers(...)
#> 52. │ └─ggplot2 (local) f(l = layers[[i]], d = data[[i]])
#> 53. │ └─l$compute_aesthetics(d, plot)
#> 54. │ └─ggplot2 (local) compute_aesthetics(..., self = self)
#> 55. │ └─ggplot2:::scales_add_defaults(...)
#> 56. │ └─base::lapply(aesthetics[new_aesthetics], eval_tidy, data = data)
#> 57. │ └─rlang (local) FUN(X[[i]], ...)
#> 58. ├─driv
#> 59. ├─rlang:::`$.rlang_data_pronoun`(.data, driv)
#> 60. │ └─rlang:::data_pronoun_get(...)
#> 61. └─rlang:::abort_data_pronoun(x, call = y)
#> 62. └─rlang::abort(msg, "rlang_error_data_pronoun_not_found", call = call)
关于我对 Allan Cameron 建议的 ensyms 方法进行测试的注释 我发现这种方法不能接受包含字符串或 NULL 参数的对象,并且我找不到使用 ensyms 解决这些问题的方法
plotterEnsyms <- function(...) {
ggplot(mpg, aes(displ, cty)) + geom_point(mapping = aes(!!!ensyms(...)))
}
# 1. uses name of object as symbol, instead of converting the string itself
plotterEnsyms(colour = colourObject)
# 2. can't accept NULLs, (and ensyms directly uses the args, so NULLs can't be removed first, I think...)
plotterEnsyms(size = NULL)
#> Error in `sym()`:
#> ! Can't convert NULL to a symbol.
#> Backtrace:
#> ▆
#> 1. └─global plotterEnsyms(size = NULL)
#> 2. ├─ggplot2::geom_point(mapping = aes(!!!ensyms(...)))
#> 3. │ └─ggplot2::layer(...)
#> 4. ├─ggplot2::aes(!!!ensyms(...))
#> 5. │ └─ggplot2:::arg_enquos("x")
#> 6. │ └─rlang::eval_bare(expr[[2]][[2]][[2]], env)
#> 7. └─rlang::ensyms(...)
#> 8. └─rlang:::map(...)
#> 9. └─base::lapply(.x, .f, ...)
#> 10. └─rlang (local) FUN(X[[i]], ...)
#> 11. └─rlang::sym(expr)
#> 12. └─rlang:::abort_coercion(x, "a symbol")
#> 13. └─rlang::abort(msg, call = call)
创建于 2022 年 11 月 13 日,使用 reprex v2.0.2
“tinycodet”R 包提供了
aes_pro()
,它相当于 ggplot2::aes()
,只不过它使用公式输入而不是非标准评估。所以你可以执行以下操作:
library(ggplot2)
plotterOld <- function(...) {
pointAes <-tinycodet::aes_pro(...)
ggplot(mpg, aes(displ, cty)) +
geom_point(mapping = pointAes)
}
plotterOld(colour = ~cyl, size = ~year)