Search code examples
rmetaprogrammingtidyevalnse

Can you use a parameter in a function name with tidy eval/NSE/metaprogramming?


I am writing a function that makes ggplots with given data, and I am interested in setting a parameter that can call the different scales::label_* functions to change the outputs. For example, I want to be able to call create_pie_chart(starwars, fill = gender, label = "percent") and have the labels appear using scales::label_percent or create_pie_chart(starwars, fill = gender, label = "date") and have the labels use scales::label_date.

I know I could just use some if else statements, but I'm curious if there is a tidy eval or NSE way to do this? Maybe using substitute or {{}}?

Here is an example where I tried to use glue to create the function name -- it does not work:

library(dplyr)

create_pie_chart <- function(data,
                             fill,
                             label = "percent") {

    data_counts <- data %>%
      dplyr::summarize(num = dplyr::n(), .by = {{ fill }}) %>%
      dplyr::mutate(prop = num/sum(num))
    
    label_function <- glue::glue("scales::label_", label)
    
    plot <- data_counts %>%
      ggplot2::ggplot(ggplot2::aes(x="", y = prop, fill = {{ fill }})) +
      ggplot2::geom_bar(width = 1, stat = "identity") +
      ggplot2::coord_polar("y", start = 0) + 
      ggplot2::geom_label(
        # This is the line where I would like to use tidy eval
        # to call `scales::label_*` with my `label` parameter
        ggplot2::aes(label = label_function()(prop)),
        position = ggplot2::position_stack(vjust = 0.5)
      ) +
      ggplot2::theme_void()
    
    plot
}

data("starwars")

create_pie_chart(starwars, fill = gender, label = "percent")

I have also tried match.fun/get(..., mode = function) as described in this question, but I get an error stating:

Error in get(as.character(FUN), mode = "function", envir = envir) :
object 'scales::label_percent' of mode 'function' was not found

Solution

  • No need for tidyeval. You can use getExportedValue to find a name inside a namespace; so to find scales::label_X, you could use getExportedValue("scales", "label_X"):

    label_function <- getExportedValue("scales", glue::glue("label_{label}"))
    

    For non-qualified names (i.e. if you didn’t have scales:: in front of it), you can instead use get(name, mode = "function").