Search code examples
rggplot2rlangquosure

ggplot with aesthetics generated from input data


Since I will need to make a lot of different plots in R I'm trying to put some more logic in preparing the data (add column names corresponding to the aesthetics) and less logic in the plot itself.

Consider the following default iris plot:

library(ggplot2)
library(data.table)
scatter <- ggplot(data=iris, aes(x = Sepal.Length, y = Sepal.Width)) 
scatter + geom_point(aes(color=Species, shape=Species))

Now I make a modified iris data with column names matching to the desired aesthetics:

iris2 <- as.data.table(iris)
iris2 <- iris2[,.(x=Sepal.Length, y=Sepal.Width, color=Species,
                  shape=Species)]

That I want to plot in a function in such a way that it basically builds the following command only slightly more dynamic, so you use all the aesthetics supplied in the data.

ggplot(data, aes(x=x, y=y)) + geom_point(aes(color=color, shape=shape))

It has been a long time since I read anything about nonstandard evaluation, expressions and quotation and I noticed that there are quite some developments with rlang and quosures (cheatsheet). [This] question was kind of helpful, but it did not resolve the fact that I want to infer the aesthetics from the data.

In the end I have tried a lot of stuff, and looked inside aes. In there I see:

exprs <- rlang::enquos(x = x, y = y, ...)

and I think this is the reason that all attempts that I made like:

ggplot(iris2, aes(x=x, y=y)) +
    geom_point(aes(rlang::quo(expr(color=color))))

did not work out since aes is trying to 'enquos' my quosure(s).

QUESTION Is there any way to supply arguments to aes in a dynamic way based on the contents of the data (so you do not know in advance which aesthetics you will need?

If my question is not clear enough, in the end I made something that works, only I have a feeling that this totally not necessary because I don't know/understand the right way to do it. So the stuff below works and is what I have in mind, but what I e.g. don't like is that I had to modify aes:

The block below is stand alone and can be executed without the code chunks above.

library(data.table)
library(ggplot2)
library(rlang)
iris2 <- as.data.table(iris)
iris2 <- iris2[,.(x=Sepal.Length, y=Sepal.Width, color=Species, shape=Species)]
myaes <- function (x, y, myquo=NULL, ...) {    
    exprs <- rlang::enquos(x = x, y = y, ...)    
    exprs <- c(exprs, myquo)
    is_missing <- vapply(exprs, rlang::quo_is_missing, logical(1))
    aes <- ggplot2:::new_aes(exprs[!is_missing], env = parent.frame())
    ggplot2:::rename_aes(aes)
}

generalPlot <- function(data, f=geom_point,
                        knownaes=c('color'=expr(color), 'shape'=expr(shape))){
    myquo  <- list()
    for(i in names(knownaes)){
        if(i %in% names(data)){
            l <- list(rlang::quo(!!knownaes[[i]]))
            names(l) <- i
            myquo <- c(myquo, l)
        }
    }    

    ggplot(data, aes(x=x, y=y)) +
        f(myaes(myquo=myquo))   
}

generalPlot(iris2[,.(x, y, color)])
generalPlot(iris2[,.(x, y, color, shape)])

Solution

  • So if your data as a "color" or "shape" column, you just want to map that to the color or shape aesthetic? I think a simpler way to do that would be

    generalPlot <- function(data, f=geom_point, knownaes=c('color', 'shape')) {
      match_aes <- intersect(names(data), knownaes)
      my_aes_list <- purrr::set_names(purrr::map(match_aes, rlang::sym), match_aes)
      my_aes <- rlang::eval_tidy(quo(aes(!!!my_aes_list)))
      ggplot(data, aes(x=x, y=y)) +
            f(mapping=my_aes)
    
    }
    

    Then you can do

    generalPlot(iris2[,.(x, y)])
    generalPlot(iris2[,.(x, y, color)])
    generalPlot(iris2[,.(x, y, color, shape)])
    

    and it doesn't require the additional myaes function.

    I'm kind of surprised I had to use eval_tidy but for some reason you can't seem to use !!! with aes().

    x <- list(color=sym("color"))
    ggplot(iris2, aes(x,y)) + geom_point(aes(!!!x))
    # Error: Can't use `!!!` at top level
    

    (Tested with ggplot2_3.1.0)