rggplot2optional-parametersfacet-grid

Writing custom ggplot function with optional facet plot arguments


I am trying to create a customized function that uses ggplot facet_wrap, in which the user can optionally provide labels. I've wrapped the facet_grid argument in an if/else clause depending on whether or not the user provides labels, and the function runs successfully. However, it only works if this clause is the final call in the ggplot sequence; if I add anything else after it (e.g. specifying the theme), it breaks. Obviously I can reorder the function to leave the facet_wrap call at the end, but I suspect this means I'm doing something incorrectly.

Here is a reproducible example:

library(dplyr)
library(ggplot2)

# sample data
data(msleep)
filtered<- msleep %>% filter(conservation %in% c('en', 'vu', 'domesticated'))

# readable labels
fancy_labels_cons <- c('domesticated' = 'Domesticated', 'en' = 'Endangered', 'vu' = 'Vulnerable')
fancy_labels_vore <- c('carni' = 'Carnivore', 'herbi'='Herbivore', 'omni'='Omnivore', 'insecti'='Insectivore')

# function: works as long as if/else clause is the final argument
myfunc_workingversion <- function(df, x, y, facetx, facety, labs=NULL) {
  ggplot(df, aes({{x}}, {{y}})) + 
    geom_point() +
    if(!is.null(labs)) {
        facet_grid(vars({{facetx}}), vars({{facety}}),
                  labeller = as_labeller(labs))
    } else {
        facet_grid(vars({{facetx}}), vars({{facety}}))
    } 
}

#function: no longer works when an additional ggplot argument is added at the end
myfunc2 <- function(df, x, y, facetx, facety, labs=NULL) {
  ggplot(df, aes({{x}}, {{y}})) + 
    geom_point() +
    if(!is.null(labs)) {
        facet_grid(vars({{facetx}}), vars({{facety}}),
                  labeller = as_labeller(labs))
    } else {
        facet_grid(vars({{facetx}}), vars({{facety}}))
    } +
    theme_minimal()
}

#run function - this works
myfunc2(
  filtered, 
  x=sleep_total, 
  y=awake, 
  facetx=conservation, 
  facety=vore, 
  labs=c('domesticated' = 'Domesticated', 'en' = 'Endangered', 
         'vu' = 'Vulnerable', 'carni' = 'Carnivore', 'herbi'='Herbivore',
         'omni'='Omnivore', 'insecti'='Insectivore'))

#run function - this doesn't work
myfunc2(
  filtered, 
  x=sleep_total, 
  y=awake, 
  facetx=conservation, 
  facety=vore
)

Note that both versions of the functions work if labs are provided; however, the second version breaks if labs are not provided. I suspect I'm doing something wrong with the "+". I'm also interested in learning if there's a more optimized or readable way of setting up the optional input here.


Solution

  • You have a parsing problem. The if/else syntax expect expressions for the iftrue/ifelse block. The {} are not really required and do not determine the expands of the blocks. What you have is being interpreted as

    myfunc2 <- function(df, x, y, facetx, facety, labs=NULL) {
      ggplot(df, aes({{x}}, {{y}})) + 
        geom_point() +
        if(!is.null(labs)) {
            facet_grid(vars({{facetx}}), vars({{facety}}),
                      labeller = as_labeller(labs))
        } else {{ facet_grid(vars({{facetx}}), vars({{facety}}))} + theme_minimal()}
    }
    

    or

    myfunc2 <- function(df, x, y, facetx, facety, labs=NULL) {
      ggplot(df, aes({{x}}, {{y}})) + 
        geom_point() +
        if(!is.null(labs)) 
          facet_grid(vars({{facetx}}), vars({{facety}}), labeller = as_labeller(labs))
        else 
          facet_grid(vars({{facetx}}), vars({{facety}})) + theme_minimal()
    }
    

    And the problem is facet_grid(vars({{facetx}}), vars({{facety}})) + theme_minimal() isn't a valid expression on it's own.

    To get what you want, wrap the if block in braces

    myfunc2 <- function(df, x, y, facetx, facety, labs=NULL) {
      ggplot(df, aes({{x}}, {{y}})) + 
        geom_point() +
        {if(!is.null(labs)) {
            facet_grid(vars({{facetx}}), vars({{facety}}),
                      labeller = as_labeller(labs))
        } else { 
         facet_grid(vars({{facetx}}), vars({{facety}}))
        }} + 
        theme_minimal()
    }