Search code examples
rloopsggplot2cowplot

R assigning ggplot objects to list in loop


I'm using a for loop to assign ggplots to a list, which is then passed to plot_grid() (package cowplot). plot_grid places multiple ggplots side by side in a single figure. This works fine manually, but when I use a for loop, the last plot generated is repeated in each subframe of the figure (shown below). In other words, all the subframes show the same ggplot.

Here is a toy example:

require(cowplot)

dfrm <- data.frame(A=1:10, B=10:1)

v <- c("A","B")
dfmsize <- nrow(dfrm)
myplots <- vector("list",2)

count = 1
for(i in v){
    myplots[[count]] <- ggplot(dfrm, aes(x=1:dfmsize, y=dfrm[,i])) + geom_point() + labs(y=i)
    count = count +1
}
plot_grid(plotlist=myplots)

Expected Figure:

enter image description here

Figure from for loop:

enter image description here

I tried converting the list elements to grobs, as described in this question, like this:

mygrobs <- lapply(myplots, ggplotGrob)
plot_grid(plotlist=mygrobs)

But I got the same result.

I think the problem lies in the loop assignment, not plot_grid(), but I can't see what I'm doing wrong.


Solution

  • The answers so far are very close, but unsatisfactory in my opinion. The problem is the following - after your for loop:

    myplots[[1]]$mapping
    #* x -> 1:dfmsize
    #* y -> dfrm[, i]
    myplots[[1]]$plot_env
    #<environment: R_GlobalEnv>
    
    myplots[[2]]$mapping
    #* x -> 1:dfmsize
    #* y -> dfrm[, i]
    myplots[[2]]$plot_env
    #<environment: R_GlobalEnv>
    
    i
    #[1] "B"
    

    As the other answers mention, ggplot doesn't actually evaluate those expressions until plotting, and since these are all in the global environment, and the value of i is "B", you get the undesirable results.

    There are several ways of avoiding this issue, the simplest of which in fact simplifies your expressions:

    myplots = lapply(v, function(col)
                ggplot(dfrm, aes(x=1:dfmsize, y=dfrm[,col])) + geom_point() + labs(y=col))
    

    The reason this works, is because the environment is different for each of the values in the lapply loop:

    myplots[[1]]$mapping
    #* x -> 1:dfmsize
    #* y -> dfrm[, col]
    myplots[[1]]$plot_env
    #<environment: 0x000000000bc27b58>
    
    myplots[[2]]$mapping
    #* x -> 1:dfmsize
    #* y -> dfrm[, col]
    myplots[[2]]$plot_env
    #<environment: 0x000000000af2ef40>
    
    eval(quote(dfrm[, col]), env = myplots[[1]]$plot_env)
    #[1]  1  2  3  4  5  6  7  8  9 10
    eval(quote(dfrm[, col]), env = myplots[[2]]$plot_env)
    #[1] 10  9  8  7  6  5  4  3  2  1
    

    So even though the expressions are the same, the results are different.

    And in case you're wondering what exactly is stored/copied to the environment of lapply - unsurprisingly it's just the column name:

    ls(myplots[[1]]$plot_env)
    #[1] "col"