Search code examples
rfunctionggplot2optional-parameters

Pass an optional argument to a custom function for use both directly and in an ellipsis construct


Sometimes I write functions that involve renaming/relabeling a lot of variables in different places, using a relabeller. The relabeller is used in multiple contexts, both directly in my code (e.g. for titles) and passed to other functions (e.g. facet_wrap). This is fine for typical uses:

library(ggplot2)
library(dplyr)

plotfcn <- function(df, x, y, facet, fancy_labs){
  # Strings of x & y (for axis titles)
  xStr <- fancy_labs(deparse(substitute(x)))
  yStr <- fancy_labs(deparse(substitute(y)))
  
  #base plot
  p <- ggplot(df, aes({{x}}, {{y}})) + 
    geom_point()
  
  #add facets (with labels) and axis labels
  p + facet_wrap(vars({{facet}}), labeller=fancy_labs) +
    labs(x=xStr, y=yStr)
}

mylabs <- as_labeller(c(
  'compact' = 'Small Family Car',
  'midsize' = 'Medium Family Car',
  'minivan' = 'For the Kids',
  'pickup' = 'For the Stuff',
  'displ' = 'Engine Displacement (L)',
  'hwy' = 'Highway Mileage (mpg)'))

classlist <- c('compact', 'midsize', 'minivan', 'pickup')

mpg_lim <- mpg %>% filter(class %in% classlist) 

mpg_lim %>% plotfcn(displ, hwy, class, fancy_labs = mylabs)

Now I'd like to make the labeller argument optional. Wrapping anything involving labels in an if/else statement is easy:

plotfcn <- function(df, x, y, facet, fancy_labs=NULL){
  # Strings of x & y (for axis titles)
  if(!is.null(fancy_labs)) {
    xStr <- fancy_labs(deparse(substitute(x)))
    yStr <- fancy_labs(deparse(substitute(y)))
  } 
  
  #base plot
  p <- ggplot(df, aes({{x}}, {{y}})) + 
    geom_point() 
  
  #optional: add labels using labeller
  if(!is.null(fancy_labs)) { 
    p + 
      facet_wrap(vars({{facet}}), labeller=fancy_labs) +
      labs(x=xStr, y=yStr)
    } else { 
      p + facet_wrap(vars({{facet}}))  
    }
}

mpg_lim %>% plotfcn(displ, hwy, class, fancy_labs = mylabs)

mpg_lim %>% plotfcn(displ, hwy, class)

However, if possible I'd like to avoid putting the facet_wrap() argument into the if/else statement. (In my real code, it's already in an if/else sequence, so adding this additional if/else will add a lot of branches and repeated copy/pasting, which is cumbersome to work with. ) This answer suggests using an ellipse construct to pass the labeller argument to facet_wrap, which works great - except that I can't figure out how to then call the same labeller function for the axis labels. Would I need to pass the same argument into my code twice under two different names, as shown below, or is there any way to call the labeller argument both through the ellipses construct and independently in my code?

plotfcn <- function(df, x, y, facet, fancy_labs=NULL, ...){
  # Strings of x & y (for axis titles)
  if(!is.null(fancy_labs)) {
    xStr <- fancy_labs(deparse(substitute(x)))
    yStr <- fancy_labs(deparse(substitute(y)))
  } 
  
  #base plot - calls labeller using ...
  p <- ggplot(df, aes({{x}}, {{y}})) + 
    geom_point() +
    facet_wrap(vars({{facet}}), ...)
  
  #optional: add labels using labeller
  if(!is.null(fancy_labs)) { 
    p + labs(x=xStr, y=yStr)
    } else { 
      p
    }
}

#this works, but involves calling the same parameter (mylabs) twice under two different names, which is annoying
mpg_lim %>% plotfcn(displ, hwy, class, fancy_labs = mylabs, labeller=mylabs)
mpg_lim %>% plotfcn(displ, hwy, class)

If I set the function up with one labeller argument function(df, x, y, facet, labeller = NULL, ...), the ellipsis construct doesn't trigger, and my facets are unlabelled. But if I set it up with just the ellipses function(df, x, y, facet, ...), I can't check for its presence using is.null() or missing() to trigger my if statements. Is there any way to get the if statements to recognize the presence/absence of the labeller argument?


Solution

  • You can use list(...) to pull out and work with specific arguments passed to ...:

    library(ggplot2)
    library(dplyr)
    
    plotfcn <- function(df, x, y, facet, ...){
      # get `labeller` arg if present
      fancy_labs <- list(...)$labeller
    
      # Strings of x & y (for axis titles)
      if(!is.null(fancy_labs)) {
        xStr <- fancy_labs(deparse(substitute(x)))
        yStr <- fancy_labs(deparse(substitute(y)))
      } 
      
      p <- ggplot(df, aes({{x}}, {{y}})) + 
        geom_point() +
        facet_wrap(vars({{facet}}), ...)
      
      if(!is.null(fancy_labs)) { 
        p + labs(x=xStr, y=yStr)
        } else { 
          p
        }
    }
    
    mpg_lim %>% plotfcn(displ, hwy, class, labeller=mylabs)
    

    mpg_lim %>% plotfcn(displ, hwy, class)