0

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.

4
  • 1
    If you take ggplot out of it entirely, 1 + if(TRUE) {1} else {1} + 1 gives 2, not 3, which suggests that there is a fundamental syntax issue. Commented Oct 21, 2024 at 18:02
  • 1
    ...though, experimenting with that I guess I've found the answer: 1 + {if(TRUE) {1} else {1}} + 1 does work, so you can probably fix your mid-statement conditional by wrapping the entire thing in brackets, from before the if to after the close of the else{}. Commented Oct 21, 2024 at 18:04
  • 1
    @GregorThomas This kind of use NULL (instead of using missing()) is all over modern packages, including many tidyverse functions (example). Hadley writes that he feels this makes it more clear which arguments are required. Commented Oct 21, 2024 at 23:04
  • @Axeman interesting, thanks for the reference! Commented Oct 22, 2024 at 13:47

2 Answers 2

3

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()
}
Sign up to request clarification or add additional context in comments.

Comments

3

a bit easier to read might be:

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

1 Comment

This is really helpful; I accepted the other answer, because it pointed out my root parsing error, but really like this example of how to set the code up to be more readable.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.