如何替换已弃用的 ggplot2 函数 aes_string:接受任意数量的命名字符串来指定美学映射?

问题描述 投票:0回答:4

aes_string
有一些我在使用 ggplot2 编程时使用的便捷行为。但是
aes_string
已被弃用(我相信从 ggplot2 版本 3.4.0 开始就很明显)。我正在努力思考如何很好地替换它。

具体来说,我之前创建了通过省略号接受任意字符串参数的函数,并通过 do.call 将这些参数传递给 aes_string,如下面的第一个表示所示。

自从注意到弃用警告后,我一直试图避免

aes_string
,并发现自己实际上只是以一种相当“hacky”的方式模仿它。 据推测,
aes_string
中的任何缺陷导致其被弃用,也适用于我的黑客解决方法。请参阅第二个代表。

有更优雅的解决方案吗?我想继续将变量名称作为字符串传递。

我用 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()`

plot 1

# it can accept NULLs, and e.g. intuitively doesn't map size to anything
plotterOld(colour = "cyl", size = NULL)

plot 2

# no arguments also works fine
plotterOld()

plot 3

于 2022 年 11 月 11 日使用 reprex v2.0.2 创建


代表我尝试替换 aes_string 的行为?

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")

plot 4

plotterNew(colour = "cyl", size = NULL, shape = "drv")

plot 5

plotterNew()

plot 6


# 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
#> 
#> ──────────────────────────────────────────────────────────────────────────────
````
r ggplot2 rlang nse non-standard-evaluation
4个回答
6
投票

一种选择是使用

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()


3
投票

您可以使用

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


3
投票

Stefan 的建议

sym()
和 Allan Cameron 的建议
ensyms()
的答案都确实帮助我找到了理解这个问题的正确方向,所以这一切都归功于他们。

这个答案比较了两种方法,并添加了第三种方法

data_sym()

aes_string 的两种行为,如下所示,我能够使用 sym() 进行复制,但不能使用 ensyms()

  1. aes_string 可以接受 NULL 作为参数值,
  2. aes_string 可以使用已经存储在对象中的字符串
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


0
投票

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)
© www.soinside.com 2019 - 2024. All rights reserved.