2

I am working on updating some older R code to make it use ggplot2's aes() now that aes_string() is deprecated. I admit I don't understand all of the details of tidy evaluation, but from what I do understand it seems like the replacement for aes_string(shape=shapevar) would be aes(shape=.data[[shapevar]]), or possibly aes(shape=!!sym(shapevar)).

The problem with these replacements is that they do not work when shapevar is NULL, as shown in the example below:

library('ggplot2')

set.seed(1)
dat <- data.frame(x=rnorm(25),y=rnorm(25),othervar=factor(sample(1:3,25,replace=T)))

make_scatter_string <- function(plotdat,outfile,xvar='x',yvar='y',colorvar=NULL,shapevar=NULL) {
  plt <- ggplot(plotdat,aes_string(x=xvar,y=yvar,color=colorvar,shape=shapevar)) + geom_point()
  ggsave(outfile,plt)
}

make_scatter_data <- function(plotdat,outfile,xvar='x',yvar='y',colorvar=NULL,shapevar=NULL) {
  plt <- ggplot(plotdat,aes(x=.data[[xvar]],y=.data[[yvar]],color=.data[[colorvar]],shape=.data[[shapevar]])) + geom_point()
  ggsave(outfile,plt)
}

make_scatter_sym <- function(plotdat,outfile,xvar='x',yvar='y',colorvar=NULL,shapevar=NULL) {
  plt <- ggplot(plotdat,aes(x=!!sym(xvar),y=!!sym(yvar),color=!!sym(colorvar),shape=!!sym(shapevar))) + geom_point()
  ggsave(outfile,plt)
}

make_scatter_string(dat,'test1.png',colorvar='othervar') # Works, aes_string can handle shapevar being NULL
make_scatter_data(dat,'test2.png',colorvar='othervar') # Gives error message "Error in `.data[[NULL]]`: ! Must subset the data pronoun with a string, not NULL."
make_scatter_sym(dat,'test3.png',colorvar='othervar') # Gives error message "Error in `sym()`: ! Can't convert NULL to a symbol."

What is the correct way to give aes() a variable like shapevar that may contain the name of a variable in the data or may be NULL? Or is there some other value that should be used to say "don't use this aesthetic" instead of NULL?

bluemouse
  • 360
  • 1
  • 11

3 Answers3

1

I think you can add individual aes(.) directly based on the value of is.null(.).

make_scatter_data <- function(plotdat, xvar='x', yvar='y', colorvar=NULL, shapevar=NULL) {
  plt <- ggplot(plotdat, aes(x=.data[[xvar]],y =.data[[yvar]])) + geom_point()
  if (!is.null(colorvar)) plt <- plt + aes(color=.data[[colorvar]])
  if (!is.null(shapevar)) plt <- plt + aes(shape=.data[[shapevar]])
  plt
}
make_scatter_data(dat, colorvar='othervar')

ggplot by dot-data, colorvar

Similarly,

make_scatter_sym <- function(plotdat, xvar='x', yvar='y', colorvar=NULL, shapevar=NULL) {
  plt <- ggplot(plotdat, aes(x=!!sym(xvar), y=!!sym(yvar))) + geom_point()
  if (!is.null(colorvar)) plt <- plt + aes(color = !!sym(colorvar))
  if (!is.null(shapevar)) plt <- plt + aes(shape = !!sym(shapevar))
  plt
}
make_scatter_sym(dat, shapevar='othervar')

ggplot by symbol, shapevar

r2evans
  • 141,215
  • 6
  • 77
  • 149
  • 1
    Thanks!! This works, but it seems like a convoluted way to replace something that used to only take one line with aes_string(). It also doesn't seem like it would adapt well when using aes() inside of a geom like geom_point() instead of plot-wide - in that case it would take separate handling for every possible combination of null/non-null. If the point of tidyeval is to make code cleaner and more streamlined, it seems like it would have a shorter way to address this situation (but if it does, I haven't been able to find it). – bluemouse May 26 '23 at 03:16
  • 1
    I agree with all of your points, I don't (yet) have a more flexible option. – r2evans May 26 '23 at 03:37
1

How about:

make_scatter_data <- function(plotdat, outfile, xvar = x, yvar = y,
                              colorvar = NULL, shapevar = NULL) {
  plt <- ggplot(plotdat,
           aes(x={{xvar}}, y={{yvar}}, color={{colorvar}}, shape={{shapevar}})) + 
    geom_point()
  ggsave(outfile,plt)
}


make_scatter_data(dat, "myfile.png")

enter image description here

make_scatter_data(dat, "myfile.png", colorvar = othervar)

enter image description here

make_scatter_data(dat, "myfile.png", shapevar = othervar)

enter image description here

make_scatter_data(dat, "myfile.png", xvar = y, yvar = x, 
                  colorvar = othervar, shapevar = othervar)

enter image description here


Or if you want to feed in quoted names:

make_scatter_data <- function(plotdat, outfile, xvar = x, yvar = y,
                              colorvar = NULL, shapevar = NULL) {
  xvar = sym(xvar)
  yvar = sym(yvar)
  if(!is.null(colorvar)) colorvar = sym(colorvar)
  if(!is.null(shapevar)) shapevar = sym(shapevar)
  
  plt <- ggplot(plotdat,
                aes(x={{xvar}}, y={{yvar}}, color={{colorvar}}, shape={{shapevar}})) + 
    geom_point()
  plt
  # ggsave(outfile,plt)
}

make_scatter_data(dat, "myfile.png", xvar = "y", yvar = "x", 
                  colorvar = "othervar")
Jon Spring
  • 55,165
  • 4
  • 35
  • 53
  • This is a very nice and clean solution. The only major drawback I see with this approach is that all existing code that uses the function would have to be updated to not quote the variable names given as arguments. – bluemouse May 26 '23 at 21:29
  • You could use `sym` to convert from quoted names to symbols, but similar to @r2evans' solution, I think you'd need special handling to toggle some aesthetics between NULL and a variable, which can be avoided if you're not using strings. – Jon Spring May 26 '23 at 21:42
1

Adapting my answer on How to replace the deprecated ggplot2 function aes_string: accepting an arbitrary number of named strings to specify aesthetic mappings?, a flexible and generalized approach of the one by @r2evans may look like so.

Basically I renamed the functions arguments to the names of the aesthetics and used ... to allow to pass additional aesthetics and which also works with NULL:

library(ggplot2)

make_scatter_sym <- function(plotdat, outfile, x = "x", y = "y", ...) {
  args <- lapply(list(x = x, y = y, ...), function(x) if (!is.null(x)) sym(x))

  plt <- ggplot(plotdat, aes(!!!args)) +
    geom_point()

  ggsave(outfile, plt)

  plt
}

make_scatter_sym(dat, "test1.png", color = "othervar")
#> Saving 7 x 5 in image


make_scatter_sym(dat, "test1.png", color = NULL, shape = NULL)
#> Saving 7 x 5 in image

stefan
  • 90,330
  • 6
  • 25
  • 51
  • Nice!!! I expanded on this a little further to make a function aes_s() that uses the same approach to serve as a drop-in replacement for aes_string(), so the parent function like make_scatter_sym() could reserve its use of ... for something else if it needed to: aes_s <- function(...) { orig_args <- list(...) orig_args <- orig_args[!sapply(orig_args,is.null)] return(aes(!!!lapply(orig_args,sym))) } – bluemouse May 26 '23 at 22:33