'Cannot use faceting for ggplot inside a function

I am trying to use ggplot inside a function, but I am unable to produce the plots. Specifically, I would like to determine whether or not the plot will use facet_grid() from the function call. This is my data:

mydf <- data.frame(
  group = rep(c("g1", "g2"), each = 16, times = 1), 
  cluster = rep(c("c1", "c2"), each = 8, times = 2), 
  score1 = c(rnorm(n = 16, mean = 10, sd = 10), rnorm(n = 16, mean = 18, sd = 10)), 
  score2 = c(rnorm(n = 16, mean = 50, sd = 10), rnorm(n = 16, mean = 33, sd = 10))
  )

Here is the function:

myFunc <- function(data, group = NULL, group2, var1, var2) {

  # So we don't need quotation marks in function call
  arguments <- as.list(match.call())
  var1 = eval(arguments$var1, data)
  var2 = eval(arguments$var2, data)
  group2 = eval(arguments$cluster, data)
  grouping = eval(arguments$group, data)

  # Make this graph if no faceting needed
  if (length(grouping) == 0) {

  means <- aggregate(cbind(var1, var2) ~  group2, FUN = mean, data = data)

  ggplot(data, aes(x = var1, y = var2, color = group2, label = group2)) + 
    stat_ellipse(type = "norm", show.legend = FALSE, geom = "polygon", alpha = 0.1) +
    geom_text(alpha = 0.5, show.legend = FALSE) +
    geom_text(data = means, aes(x = var1, y = var2, color = group2)) 


  # Use faceting
  } else if (length(grouping) > 0) {

  means <- aggregate(cbind(var1, var2) ~ grouping + group2, FUN = mean, data = data)

  # Plot 
  ggplot(data, aes(x = var1, y = var2, color = group2, label = group2)) + 
    stat_ellipse(type = "norm", show.legend = FALSE, geom = "polygon", alpha = 0.1) +
    geom_text(alpha = 0.5, show.legend = FALSE) + 
    geom_text(data = means, aes(x = var1, y = var2, color = group2)) +
    facet_grid(. ~ grouping) 

  }

}

I am calling the function like so:

myFunc(data = mydf, group = NULL, group2 = cluster, var1 = score1, var2 = score2)
myFunc(data = mydf, group = group, group2 = cluster, var1 = score1, var2 = score2)

Both calls give the following error respectively:

# Error 1
Error: Aesthetics must be either length 1 or the same as the data (32): x, y, colour, label

# Error 2
Error in combine_vars(data, params$plot_env, cols, drop = params$drop) : 
At least one layer must contain all variables used for facetting

The expected output can be acquired by building the plots manually:

means <- aggregate(cbind(score1, score2) ~ group + cluster, FUN = mean, data = mydf)

# without facet
ggplot(mydf, aes(x = score1, y = score2, color = cluster, label = cluster)) + 
  stat_ellipse(type = "norm", show.legend = FALSE, geom = "polygon", alpha = 0.1) +
  geom_text(alpha = 0.5, show.legend = FALSE) + 
  geom_text(data = means, aes(x = score1, y = score2, color = cluster)) 

# with facet
ggplot(mydf, aes(x = score1, y = score2, color = cluster, label = cluster)) + 
  stat_ellipse(type = "norm", show.legend = FALSE, geom = "polygon", alpha = 0.1) +
  geom_text(alpha = 0.5, show.legend = FALSE) + 
  geom_text(data = means, aes(x = score1, y = score2, color = cluster)) + 
  facet_grid(. ~ group)


Solution 1:[1]

Here's the basic stat_ellipse plots with and without using facet_grid. I will let you add the frills. Here the column names are kept as strings so aes_string is used instead of aes, and formulas are passed to functions using as.formula.

myFunc <- function(df, var1, var2, group2, group1 = NULL) {

  # Make this graph if no faceting needed
  if (is.null(group1)) {

  means_formula <- as.formula(paste(var1, "+", var2, "~", group2))
  means <- aggregate(means_formula, FUN = mean, data = df)

   p <- ggplot(df, 
       aes_string(x = var1, y = var2, color = group2, label = group2)) + 
       stat_ellipse(type = "norm", show.legend = FALSE, 
           geom = "polygon", alpha = 0.1)
    }else{

    means_formula <- as.formula(paste(var1,"+",var2,"~", group2,"+",group1))
    means <- aggregate(means_formula, FUN = mean, data = df)

    p <- ggplot(df, 
        aes_string(x = var1, y = var2, color = group2, label = group2)) + 
        stat_ellipse(type = "norm", show.legend = FALSE, 
            geom = "polygon", alpha = 0.1) + 
        facet_grid(as.formula(paste(".~ ",group1))) 
  }
  print(p)
}

myFunc(df = mydf, var1 = "score1", var2 = "score2", 
    group2 = "cluster", group1 = NULL)

myFunc(df = mydf, var1 = "score1", var2 = "score2", 
    group2 = "cluster", group1 = "group")

stat ellipse plot

Solution 2:[2]

First, you are assigning group2 to a nonexistent variable in scope of function, cluster. Replace: group2 = eval(arguments$cluster, data) with group2 = eval(arguments$group2, data).

Second, you need a dynamic facet_grid formula. Currently you are passing grouping which is not an actual field in dataset. However, since you are going without quotes in functional arguments, you will need to retrieve the string literal of function's argument, group, which can be achieved with deparse(substitute(...)) to return "group".

Consider adding at top near list of other function variables:

grpname = deparse(substitute(group))

Then replace you facet_grid with either a dynamic as.formula string concatenation or with reformulate:

facet_grid(as.formula(paste0(". ~ ", grpname)))

facet_grid(reformulate(grpname))

Of course, all can be dynamically run with quoted function args as shown by @P-robot.

Solution 3:[3]

  1. Use aes_ instead of aes
  2. Use substitute( ) around variable names in aes_( )
  3. Use vars({{ }}) around variable names in facet_grid

Reference: Hadley Wickam's ggplot2 book: https://ggplot2-book.org/programming.html\ Section 19.4.1 "Indirectly referring to variables"

library(ggplot2)
mydf <- data.frame(
  group = rep(c("g1", "g2"), each = 16, times = 1), 
  cluster = rep(c("c1", "c2"), each = 8, times = 2), 
  score1 = c(rnorm(n = 16, mean = 10, sd = 10), rnorm(n = 16, mean = 18, sd = 10)), 
  score2 = c(rnorm(n = 16, mean = 50, sd = 10), rnorm(n = 16, mean = 33, sd = 10))
)

myFunc <- function(data, group = group, group2 = cluster, var1 = score1, var2 = score2) {
    means <- aggregate(cbind(score1, score2) ~ group + cluster, FUN = mean, data = mydf)
    ggplot(data, aes_(x = substitute(var1), y = substitute(var2), 
                      color = substitute(group2), label = substitute(group2))) + 
      stat_ellipse(type = "norm", show.legend = FALSE, geom = "polygon", alpha = 0.1) +
      geom_text(alpha = 0.5, show.legend = FALSE) + 
      geom_text(data = means, aes_(x = substitute(var1), y = substitute(var2), 
                                  color = substitute(group2))) + 
      facet_grid(cols = vars({{group}}))
}
myFunc(data = mydf, group = group, group2 = cluster, var1 = score1, var2 = score2)

A reprex of a ggplot2 function using facet_grid:

library(ggplot2)
func1 <- function(data, var1, var2) {
  ggplot(data, aes_(x = substitute(var1))) + 
    geom_histogram(bins = 30) + 
    facet_grid(rows = vars({{var2}}))
}

func1(mtcars, mpg, am)

Created on 2022-03-26 by the reprex package (v2.0.1)

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 p-robot
Solution 2 Parfait
Solution 3