Search code examples
rfunctionrlang

unlist ellipsis list into variables (when unquoted) and arguments


I have the following function which works:

library(cowplot)
library(tidyverse)

hist_fun <- function(data, ...) {
  
  g <- map(
    list(data %>% 
           select(...) %>% 
           map2(., names(.), 
                ~{ggplot(data.frame(var = .x), aes(var)) + 
                    geom_histogram(aes(y = after_stat(density))) + 
                    labs(x = .y) 
                })),
    ~cowplot::plot_grid(plotlist = .x, ncol = 1))
  g
}

#works
hist_fun(mtcars, mpg, cyl, disp)

However, I would like to be able to pass it geom_histogram arguments too so something like:

hist_fun(mtcars, mpg, cyl, disp, bins = 20, color = "white")

I'm not sure of the best way to approach this. Should I collect everything in the ellipsis in a list and then try to separate out the list into a) variables and b) geom_histogram arguments, something like:

hist_fun <- function(data, ...) {
  everything <- list(...)
  #unlist and extract variables/geom_histogram arguments?
  #just_vars
  #just_geom_histogram
  
  g <- map(
    list(data %>%
           select(just_vars) %>%
           map2(., names(.),
                ~{ggplot(data.frame(var = .x), aes(var)) +
                    geom_histogram(aes(y = after_stat(density)), just_geom_histogram) +
                    labs(x = .y)
                })),
    ~cowplot::plot_grid(plotlist = .x, ncol = 1))
  g
}

That seems a bit messy so would it better to separate the variables at the start and keep geom_histogram arguments to the ellipsis, something like:

hist_fun(mtcars, just_vars = list(), ...)

Any suggestions and the best way to achieve it, particularly when I want to keep the variables unquoted?

Thanks


Solution

  • There are only a small number of arguments that a user might pass to geom_histogram, so I would just explicitly name these in the function definition. Ensure they go after the ellipsis though.

    library(cowplot)
    library(tidyverse)
    
    hist_fun <- function(data, ..., color = NA, bins = 30) {
      
      g <- map(
        list(data %>% 
               select(...) %>% 
               map2(., names(.), 
                    ~{ggplot(data.frame(var = .x), aes(var)) + 
                        geom_histogram(aes(y = after_stat(density)),
                                       bins = bins, color = color) + 
                        labs(x = .y) 
                    })),
        ~cowplot::plot_grid(plotlist = .x, ncol = 1))
      g
    }
    

    Allowing

    hist_fun(mtcars, mpg, cyl, disp, bins = 10, color = "red")
    

    enter image description here

    If you didn't want to limit the user to the named arguments you decided upon, you could indeed do it by sorting through the ellipsis arguments, but it's a bit more complex:

    hist_fun <- function(data, ...) {
      args <- as.list(match.call())[-c(1:2)]
      named_args <- args[which(nzchar(names(args)))]
      unnamed_args <- args[which(!nzchar(names(args)))]
      hist_args <- c(list(mapping = aes(y = after_stat(density))), named_args)
      select_args <- c(list(quote(data)), unnamed_args)
      g <- map(
        list(
               map2(do.call('select', select_args), 
                    names(do.call('select', select_args)), 
                    ~{ggplot(data.frame(var = .x), aes(var)) + 
                        do.call('geom_histogram', hist_args) + 
                        labs(x = .y) 
                    })),
        ~cowplot::plot_grid(plotlist = .x, ncol = 1))
      g
    }
    

    For example

    hist_fun(mtcars, mpg, cyl, disp, bins = 15, fill = 'red')
    

    enter image description here