Search code examples
ggplot2r-packagerlang

NULL params in ggplot function with `.data`


I'm working on replacing all aes_string in my package with more modern approaches. I've been following the vignette ggplot2-in-packages.

However, I'm facing a specific issue: I want to allow the params argument to be set as NULL for flexibility.
Could someone please guide me on how to properly modify these .data calls in my package to accommodate setting params as NULL?

This is a minimal example :

Thank you in advance for your help!

library(tidyverse)
col_summary <- function(df, x, col = NULL) {
  ggplot(df) + 
    geom_bar(aes(x = .data[[x]], fill = .data[[col]])) + 
    coord_flip()
}

col_summary(mpg, "drv", "fl")

col_summary(mpg, "drv")
#> Error in `geom_bar()`:
#> ! Problem while computing aesthetics.
#> ℹ Error occurred in the 1st layer.
#> Caused by error in `.data[[NULL]]`:
#> ! Must subset the data pronoun with a string, not `NULL`.
#> 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("alive-lark_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. │       ├─base::withCallingHandlers(...)
#>  18. │       ├─knitr:::process_group(group)
#>  19. │       └─knitr:::process_group.block(group)
#>  20. │         └─knitr:::call_block(x)
#>  21. │           └─knitr:::block_exec(params)
#>  22. │             └─knitr:::eng_r(options)
#>  23. │               ├─knitr:::in_input_dir(...)
#>  24. │               │ └─knitr:::in_dir(input_dir(), expr)
#>  25. │               └─knitr (local) evaluate(...)
#>  26. │                 └─evaluate::evaluate(...)
#>  27. │                   └─evaluate:::evaluate_call(...)
#>  28. │                     ├─evaluate (local) handle(...)
#>  29. │                     │ └─base::try(f, silent = TRUE)
#>  30. │                     │   └─base::tryCatch(...)
#>  31. │                     │     └─base (local) tryCatchList(expr, classes, parentenv, handlers)
#>  32. │                     │       └─base (local) tryCatchOne(expr, names, parentenv, handlers[[1L]])
#>  33. │                     │         └─base (local) doTryCatch(return(expr), name, parentenv, handler)
#>  34. │                     ├─base::withCallingHandlers(...)
#>  35. │                     ├─base::withVisible(value_fun(ev$value, ev$visible))
#>  36. │                     └─knitr (local) value_fun(ev$value, ev$visible)
#>  37. │                       └─knitr (local) fun(x, options = options)
#>  38. │                         ├─base::withVisible(knit_print(x, ...))
#>  39. │                         ├─knitr::knit_print(x, ...)
#>  40. │                         └─knitr:::knit_print.default(x, ...)
#>  41. │                           └─evaluate (local) normal_print(x)
#>  42. │                             ├─base::print(x)
#>  43. │                             └─ggplot2:::print.ggplot(x)
#>  44. │                               ├─ggplot2::ggplot_build(x)
#>  45. │                               └─ggplot2:::ggplot_build.ggplot(x)
#>  46. │                                 └─ggplot2:::by_layer(...)
#>  47. │                                   ├─rlang::try_fetch(...)
#>  48. │                                   │ ├─base::tryCatch(...)
#>  49. │                                   │ │ └─base (local) tryCatchList(expr, classes, parentenv, handlers)
#>  50. │                                   │ │   └─base (local) tryCatchOne(expr, names, parentenv, handlers[[1L]])
#>  51. │                                   │ │     └─base (local) doTryCatch(return(expr), name, parentenv, handler)
#>  52. │                                   │ └─base::withCallingHandlers(...)
#>  53. │                                   └─ggplot2 (local) f(l = layers[[i]], d = data[[i]])
#>  54. │                                     └─l$compute_aesthetics(d, plot)
#>  55. │                                       └─ggplot2 (local) compute_aesthetics(..., self = self)
#>  56. │                                         └─ggplot2:::scales_add_defaults(...)
#>  57. │                                           └─base::lapply(aesthetics[new_aesthetics], eval_tidy, data = data)
#>  58. │                                             └─rlang (local) FUN(X[[i]], ...)
#>  59. ├─<unknown>
#>  60. └─rlang:::`[[.rlang_data_pronoun`(.data, NULL)
#>  61.   └─rlang:::data_pronoun_get(...)
#>  62.     └─rlang::abort(...)

Created on 2023-09-01 with reprex v2.0.2


Solution

  • You can use the Tidy eval helpers sym and !! to convert string's into <tidy-select> arguments that aes() requires:

    library(tidyverse)
    col_summary <- function(df, x, col = "") {
      ggplot(df) + 
        geom_bar(aes(x = !!sym(x), fill = !!sym(col))) + 
        coord_flip()
    }
    
    col_summary(mpg, "drv", "fl")
    col_summary(mpg, "drv")
    

    In this case, the default value of the function parameter col would be "", and if you want to keep it as NULL, you can use function %||% from package rlang:

    library(tidyverse)
    library(rlang)
    col_summary <- function(df, x, col = NULL) {
      ggplot(df) + 
        geom_bar(aes(x = !!sym(x), fill = !!sym(col %||% ""))) + 
        coord_flip()
    }
    
    col_summary(mpg, "drv", "fl")
    col_summary(mpg, "drv")