Search code examples
rggplot2visualizationgrid-layout

ggplot on grid with a grobList in R


I'm trying to plot multiple plots on a grid using ggplot2 in a for loop, followed by grid.arrange. But all the plots are identical afterwards.

library(ggplot2)
library(grid)
test = data.frame(matrix(rnorm(320), ncol=16 ))
names(test) = sapply(1:16, function(x) paste0("var_",as.character(x)))
plotlist = list()
for (i in 1:(dim(test)[2]-1)){
    plotlist[[i]] = ggplot(test) + 
        geom_point(aes(get(x=names(test)[dim(test)[2]]), y=get(names(test)[i])))
}
pdf("output.pdf")
do.call(grid.arrange, list(grobs=plotlist, nrow=3))
dev.off(4)

When running this code, it seems like the get() calls are only evaluated at the time of the grid.arrange call, so all of the y vectors in the plot are identical as "var_15". Is there a way to force get evaluation immediately, so that I get 15 different plots?

Thanks!


Solution

  • Here are two ways that use purrr::map functions instead of a for-loop. I find that I have less of a clear sense of what's going on when I try to use loops, and since there are functions like the apply and map families that fit so neatly into R's vector operations paradigm, I generally go with mapping instead.

    The first example makes use of cowplot::plot_grid, which can take a list of plots and arrange them. The second uses the newer patchwork package, which lets you add plots together—like literally saying plot1 + plot2—and add a layout. To do all those additions, I use purrr::reduce with + as the function being applied to all the plots.

    library(tidyverse)
    
    set.seed(722)
    test = data.frame(matrix(rnorm(320), ncol=16 ))
    names(test) = sapply(1:16, function(x) paste0("var_",as.character(x)))
    
    
    # extract all but last column
    xvars <- test[, -ncol(test)]
    

    By using purrr::imap, I can map over all the columns and apply a function with 2 arguments: the column itself, and its name. That way I can set an x-axis label that specifies the column name. I can also easily access the column of data without having to use get or any tidyeval tricks (although for something for complicated, a tidyeval solution might be better).

    plots <- imap(xvars, function(variable, var_name) {
      df <- data_frame(x = variable, y = test[, ncol(test)])
      ggplot(df, aes(x = x, y = y)) +
        geom_point() +
        xlab(var_name)
    })
    
    cowplot::plot_grid(plotlist = plots, nrow = 3)
    

    library(patchwork)
    
    # same as plots[[1]] + plots[[2]] + plots[[3]] + ...
    reduce(plots, `+`) + plot_layout(nrow = 3)
    

    Created on 2018-07-22 by the reprex package (v0.2.0).