3

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!

RobJan
  • 1,351
  • 1
  • 14
  • 18
  • See related questions with answers, e.g., [here](https://stackoverflow.com/questions/31993704/storing-ggplot-objects-in-a-list-from-within-loop-in-r?noredirect=1&lq=1) and [here](https://stackoverflow.com/questions/41666078/ggplot-for-loop-outputs-all-the-same-graph). I think using `aes_string()` is easiest but the most current version of ggplot2 is moving towards `tidyeval`. – aosmith Jul 20 '18 at 20:48

2 Answers2

1

Try this:

library(ggplot2)
library(grid)
library(gridExtra)
set.seed(1234)
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)) {
  # Define here the dataset for the i-th plot
  df <- data.frame(x=test$var_16, y=test[, i])
  plotlist[[i]] = ggplot(data=df, aes(x=x, y=y)) + geom_point()
}
grid.arrange(grobs=plotlist, nrow=3)

enter image description here

Marco Sandri
  • 23,289
  • 7
  • 54
  • 58
1

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).

camille
  • 16,432
  • 18
  • 38
  • 60