rggplot2tidyeval

Issue with ggplot2 facet_wrap labels using tidy evaluation in R


I am trying to label my facet_wrap titles based on an aggregation of data in my data frame.

I am writing a function to plot different data sets, so I am using a function argument to select the column I want to facet by. In this case, I want to create facets for each unique class in broad but I want the facet label to include the sum of all percBackground values for each broad category.

I have tried using the rlang {{}} with little success (or understanding).


data.test <- data.frame(broad = c("A", "A", "A", "B", "B", "C", "C", "C", "C"),
                        detail = c("a", "b", "c", "d", "e", "f", "g", "h", "i"),
                        sr.Summer = c(12, 23, 32, 12, 12, 11, 14, 15, 18),
                        percBackground = c(1.5, 4.3, 3.4, 4.6, 3.4, 4.1, 3.9, 2.9, 1.8))

func.test <- function(data, facet.by){
  
  aggr <- data %>% aggregate(percBackground ~ facet.by, FUN = sum) %>%
    mutate(label = paste0(facet.by, " - ", round(percBackground, digits = 2), "%"))
  
  facet_label <- aggr$label
  names(facet_label) <- unique(facet_label)
  
  ggplot(data = data, aes(x = detail)) +
    geom_point(aes(y = sr.Summer)) +
    facet_wrap(~facet.by, labeller = labeller(facet.by = facet_label))
  
}

func.test(data = data.test, 
          facet.by = broad)

> Error in eval(predvars, data, env) : object 'broad' not found

     13. eval(predvars, data, env)
     12. eval(predvars, data, env)
     11. model.frame.default(formula = by, data = x)
     10. stats::model.frame(formula = by, data = x)
     9. eval(m, parent.frame())
     8. eval(m, parent.frame())
     7. aggregate.formula(x = by, data = x, FUN = FUN, ...)
     6. aggregate.data.frame(., percBackground ~ facet.by, FUN = sum)
     5. aggregate(., percBackground ~ facet.by, FUN = sum)
     4. aggregate(., percBackground ~ facet.by, FUN = sum)
     3. mutate(., label = paste0(facet.by, " - ", round(percBackground,
        digits = 2), "%"))
     2. data %>% aggregate(percBackground ~ facet.by, FUN = sum) %>%
        mutate(label = paste0(facet.by, " - ", round(percBackground,
        digits = 2), "%"))
     1. func.test(data.test, broad)



Solution

  • One issue with using {{ is that it will not work with aggregate, i.e. switch to dplyr::summarise. Second, when using curly-curly {{ in facet_wrap you have to wrap in vars(). Third, to get the named vector of facet labels I use tibble::deframe and finally, to pass assign the labels via labeller I pass it to the .cols argument (.rows will also work fine for facet_wrap:

    library(ggplot2)
    library(dplyr, warn = FALSE)
    
    func.test <- function(data, facet.by) {
      aggr <- data %>%
        summarise(percBackground = sum(percBackground), .by = {{ facet.by }}) %>%
        mutate(label = paste0({{ facet.by }}, " - ", round(percBackground, digits = 2), "%"))
    
      facet_label <- aggr |> 
        select({{ facet.by }}, label) |> 
        tibble::deframe()
    
      ggplot(data = data, aes(x = broad)) +
        stat_summary(fun = "mean", geom = "point", aes(y = sr.Summer)) +
        facet_wrap(vars({{ facet.by }}), labeller = labeller(.cols = facet_label))
    }
    
    func.test(
      data = data.test,
      facet.by = broad
    )
    

    enter image description here

    A second option which allows to stick with standard evaluation for most of the code would be add a new column to your dataset, which you could name e.g. facet or whatever. After doing so you use this name for the rest of your code without the of using {{, vars() or .rows:

    func.test <- function(data, facet.by) {
      data <- data |> 
        mutate(facet = {{facet.by}})
      
      aggr <- data %>%
        summarise(percBackground = sum(percBackground), .by = facet) %>%
        mutate(label = paste0(facet, " - ", round(percBackground, digits = 2), "%"))
      
      facet_label <- aggr |> 
        select(facet, label) |> 
        tibble::deframe()
      
      ggplot(data = data, aes(x = broad)) +
        stat_summary(fun = "mean", geom = "point", aes(y = sr.Summer)) +
        facet_wrap(~facet, labeller = labeller(facet = facet_label))
    }
    
    func.test(
      data = data.test,
      facet.by = broad
    )