R将ggplot对象分配给循环列表

R assigning ggplot objects to list in loop

我正在使用 for loop 将 ggplots 分配给 list,然后将其传递给 plot_grid()(包 cowplot)。 plot_grid 将多个 ggplots 并排放置在一个图中。这手动工作正常,但是当我使用 for loop 时,生成的最后一个图在图中的每个子帧中重复(如下所示)。换句话说,所有子帧显示相同的ggplot。

这是一个玩具示例:

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)

预期数字:

图来自for loop

我尝试将列表元素转换为 grobs,如 中所述,如下所示:

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

但我得到了相同的结果。

我认为问题出在循环赋值上,而不是plot_grid(),但我看不出我做错了什么。

我认为 ggplotdfrm 中寻找 xy 变量时感到困惑,即使您实际上是在动态定义它们。如果您稍微更改 for 循环以构建一个新的子 data.frame 作为第一行,它就可以正常工作。

myplots <- list()
count = 1

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

在 [this answer](.

中对 ggplot2 的惰性求值和 for 循环发生了什么有很好的解释

我通常会在这种情况下切换到 aes_stringaes_,这样我就可以在 ggplot2 中将变量用作字符串。

我发现 lapply 循环比 for 循环更容易,因为可以避免初始化列表和使用计数器。

首先,我将 x 变量添加到数据集。

dfrm$index = 1:nrow(dfrm)

现在,lapply 循环,遍历 v 中的列。

myplots = lapply(v, function(x) {
    ggplot(dfrm, aes_string(x = "index", y = x)) + 
        geom_point() +
        labs(y = x)
})

plot_grid(plotlist = myplots)

我认为这里的问题是 aes 方法的非标准评估延迟评估 i 直到实际绘制情节。在绘图时,i 是最后一个值(在玩具示例中 "B"),因此所有绘图的 y 美学映射都指的是最后一个值。同时,labs 调用使用标准评估,因此标签正确引用循环中 i 的每次迭代。

这可以通过简单地使用映射函数的标准评估版本来解决,aes_q

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_q(x=1:dfmsize, y=dfrm[,i])) + geom_point() + labs(y=i)
    count = count +1
}
plot_grid(plotlist=myplots)

到目前为止的答案非常接近,但在我看来并不令人满意。问题如下 - 在 for 循环之后:

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"

正如其他答案所提到的,ggplot 在绘制之前实际上并不评估这些表达式,因为这些都在全局环境中,并且 i 的值是 "B", 你会得到不希望的结果。

有几种方法可以避免这个问题,其中最简单的方法实际上可以简化您的表达式:

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

之所以可行,是因为对于 lapply 循环中的每个值,环境 不同

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

所以即使表达相同,结果也不同。

如果您想知道 stored/copied 对 lapply 的环境到底是什么 - 不出所料,它只是列名称:

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