【问题标题】:Storing ggplot objects in a list from within loop in R从R中的循环内将ggplot对象存储在列表中
【发布时间】:2020-02-17 16:46:21
【问题描述】:

我的问题类似于this one;当我在循环中生成绘图对象(在本例中为直方图)时,似乎所有这些对象都被最新的绘图覆盖了。

为了调试,在循环中,我打印了索引和生成的图,两者都正确显示。但是当我查看存储在列表中的图时,它们都是相同的 except 标签。

(我正在使用 multiplot 来制作合成图像,但是如果您使用 print (myplots[[1]]),您会得到相同的结果 通过print(myplots[[4]]) 一次一个。)

因为我已经有一个附加的数据框(不像类似问题的海报),我不知道如何解决这个问题。

(顺便说一句,列类是我在这里近似的原始数据集中的因素,但如果它们是整数,也会出现同样的问题)

这是一个可重现的例子:

library(ggplot2)
source("http://peterhaschke.com/Code/multiplot.R") #load multiplot function

#make sample data
col1 <- c(2, 4, 1, 2, 5, 1, 2, 0, 1, 4, 4, 3, 5, 2, 4, 3, 3, 6, 5, 3, 6, 4, 3, 4, 4, 3, 4, 
          2, 4, 3, 3, 5, 3, 5, 5, 0, 0, 3, 3, 6, 5, 4, 4, 1, 3, 3, 2, 0, 5, 3, 6, 6, 2, 3, 
          3, 1, 5, 3, 4, 6)
col2 <- c(2, 4, 4, 0, 4, 4, 4, 4, 1, 4, 4, 3, 5, 0, 4, 5, 3, 6, 5, 3, 6, 4, 4, 2, 4, 4, 4, 
          1, 1, 2, 2, 3, 3, 5, 0, 3, 4, 2, 4, 5, 5, 4, 4, 2, 3, 5, 2, 6, 5, 2, 4, 6, 3, 3, 
          3, 1, 4, 3, 5, 4)
col3 <- c(2, 5, 4, 1, 4, 2, 3, 0, 1, 3, 4, 2, 5, 1, 4, 3, 4, 6, 3, 4, 6, 4, 1, 3, 5, 4, 3, 
          2, 1, 3, 2, 2, 2, 4, 0, 1, 4, 4, 3, 5, 3, 2, 5, 2, 3, 3, 4, 2, 4, 2, 4, 5, 1, 3, 
          3, 3, 4, 3, 5, 4)
col4 <- c(2, 5, 2, 1, 4, 1, 3, 4, 1, 3, 5, 2, 4, 3, 5, 3, 4, 6, 3, 4, 6, 4, 3, 2, 5, 5, 4,
          2, 3, 2, 2, 3, 3, 4, 0, 1, 4, 3, 3, 5, 4, 4, 4, 3, 3, 5, 4, 3, 5, 3, 6, 6, 4, 2, 
          3, 3, 4, 4, 4, 6)
data2 <- data.frame(col1,col2,col3,col4)
data2[,1:4] <- lapply(data2[,1:4], as.factor)
colnames(data2)<- c("A","B","C", "D")

#generate plots
myplots <- list()  # new empty list
for (i in 1:4) {
  p1 <- ggplot(data=data.frame(data2),aes(x=data2[ ,i]))+ 
    geom_histogram(fill="lightgreen") +
    xlab(colnames(data2)[ i])
  print(i)
  print(p1)
  myplots[[i]] <- p1  # add each plot into plot list
}
multiplot(plotlist = myplots, cols = 4)

当我在绘图列表中查看绘图对象的摘要时,我看到的是这样的

> summary(myplots[[1]])
data: A, B, C, D [60x4]
mapping:  x = data2[, i]
faceting: facet_null() 
-----------------------------------
geom_histogram: fill = lightgreen 
stat_bin:  
position_stack: (width = NULL, height = NULL)

我认为mapping: x = data2[, i] 是问题所在,但我被难住了!我无法发布图片,因此如果我对问题的解释令人困惑,您需要运行我的示例并查看图表。

谢谢!

【问题讨论】:

  • 多情节链接已失效
  • 该链接对我有用。我添加了一个带有图表的帖子。

标签: r plot ggplot2


【解决方案1】:

由于传递的所有表达式的引用,在循环结束时评估的 i 是当时 i 恰好是它的最终值。您可以通过eval(substitute(在每次迭代期间输入正确的值来解决此问题。

myplots <- list()  # new empty list
for (i in 1:4) {
    p1 <- eval(substitute(
        ggplot(data=data.frame(data2),aes(x=data2[ ,i]))+ 
          geom_histogram(fill="lightgreen") +
          xlab(colnames(data2)[ i])
    ,list(i = i)))
    print(i)
    print(p1)
    myplots[[i]] <- p1  # add each plot into plot list
}
multiplot(plotlist = myplots, cols = 4)

【讨论】:

  • 诊断正确,但解决方案有些复杂。在本地上下文中更容易捕获i。问题是 R 中的 for 循环没有范围,因此您需要使用 local 代替:for (i in 1:4) local({i = i; … rest of the loop … })。自我分配i = i 并非偶然——这实际上是需要的。也可以使用不同的变量名。无论如何,通过使用“正确的”列表函数而不是 for,这一切都是不必要的,坦率地说,这在 R 中是一种糟糕的语言结构。
  • @KonradRudolph local 很好
  • 啊,我忘记了一点:如果使用local,对myplots[[i]]的赋值需要使用&lt;&lt;-操作符而不是本地赋值。
  • @KonradRudolph 任何您想使用apply 函数之一添加解决方案的机会。看来,在那种情况下,还需要替换或本地?另外,localsubstitute 方式更好吗?
  • 我更喜欢local,因为它看起来像是在执行标准评估(尽管当然不是这样)。它隐藏了evals 和substitutes。事实上,如果在美学中使用列名,lapplyfor 都不需要捕获变量 i。我会添加一个答案。
【解决方案2】:

除了其他出色的答案之外,这是一个使用“正常”外观评估而不是 eval 的解决方案。由于for 循环没有单独的变量范围(即它们在当前环境中执行),我们需要使用local 来包装for 块;此外,我们需要将 i 设为局部变量——我们可以通过将其重新分配给它自己的名称来做到这一点1

myplots <- vector('list', ncol(data2))

for (i in seq_along(data2)) {
    message(i)
    myplots[[i]] <- local({
        i <- i
        p1 <- ggplot(data2, aes(x = data2[[i]])) +
            geom_histogram(fill = "lightgreen") +
            xlab(colnames(data2)[i])
        print(p1)
    })
}

但是,一种更简洁的方法是完全放弃 for 循环并使用列表函数来构建结果。这有几种可能的方式。以下是我认为最简单的:

plot_data_column = function (data, column) {
    ggplot(data, aes_string(x = column)) +
        geom_histogram(fill = "lightgreen") +
        xlab(column)
}

myplots <- lapply(colnames(data2), plot_data_column, data = data2)

这有几个优点:它更简单,并且不会使环境混乱(使用循环变量i)。


1 这可能看起来令人困惑:为什么i &lt;- i 有任何影响? — 因为通过执行赋值,我们创建了一个新的 local 变量,它与外部作用域中的变量同名。我们同样可以使用不同的名称,例如local_i &lt;- i.

【讨论】:

  • 这个功能的好主意
  • 非常感谢,尤其是 lapply 版本;我想对其进行功能化但无法弄清楚,并决定做(表面上更容易,实际上很糟糕)for循环。我认为这是一个变量范围问题,我经常在 R 中与他们战斗!
  • 这两种解决方案都很笨拙。出于某种原因,myplots 在我的环境中每次迭代都会增长到 GB。使用本地方法或函数/lapply 方法。
  • @BigTimeStats 好吧,这是有很多非常大的地块的问题,而不是这些解决方案中的任何一个。一个常见的解决方案是对您绘制的数据点数量进行二次抽样(通常,这样的大图无论如何都不会可靠地显示所有单个数据点),或者在绘制之前计算汇总统计数据(并绘制这些而不是原始数据)。但有时两者都不起作用。在这种情况下,唯一的解决方案是避免一次在内存中拥有多个绘图。
  • @BigTimeStats 环境窗格中的估计是出了名的不可靠。很大一部分原因是它单独估计每个对象的大小,但是 R 中的许多对象(特别是数据帧)共享内存:如果您通过修改一列从另一个数据帧创建一个数据帧,那么它们将共享所有剩余的内存列。
【解决方案3】:

使用lapply 也可以工作,因为x 存在于匿名函数环境中(使用mtcars 作为数据):

plot <- lapply(seq_len(ncol(mtcars)), FUN = function(x) {
  ggplot(data = mtcars) + 
    geom_line(aes(x = mpg, y = mtcars[ , x]), size = 1.4, color = "midnightblue", inherit.aes = FALSE) +
    labs(x="Date", y="Value", title = "Revisions 1M", subtitle = colnames(mtcars)[x]) +
    theme_wsj() +
    scale_colour_wsj("colors6")
})

【讨论】:

    【解决方案4】:

    我已经运行了问题和答案中的代码,将geom_histogram 更改为geom_bar 以避免错误:Error: StatBin requires a continuous x variable

    这是带有可视化效果的代码:

    问题

    #generate plots
    myplots <- list()  # new empty list
    for (i in 1:4) {
      p1 <- ggplot(data=data.frame(data2),aes(x=data2[ ,i]))+ 
        geom_bar(fill="lightgreen") +
        xlab(colnames(data2)[ i])
      print(i)
      print(p1)
      myplots[[i]] <- p1  # add each plot into plot list
    }
    
    multiplot(plotlist = myplots, cols = 4)
    #> Loading required package: grid
    

    回答

    myplots <- vector('list', ncol(data2))
    
    for (i in seq_along(data2)) {
        message(i)
        myplots[[i]] <- local({
            i <- i
            p1 <- ggplot(data2, aes(x = data2[[i]])) +
                geom_bar(fill = "lightgreen") +
                xlab(colnames(data2)[i])
            print(p1)
        })
    }
    
    
    multiplot(plotlist = myplots, cols = 4)
    
    

    使用lapply 的结果相同:

    
    plot_data_column = function (data, column) {
        ggplot(data, aes_string(x = column)) +
            geom_bar(fill = "lightgreen") +
            xlab(column)
    }
    
    myplots <- lapply(colnames(data2), plot_data_column, data = data2)
    
    multiplot(plotlist = myplots, cols = 4)
    #> Loading required package: grid
    

    reprex package (v0.3.0) 于 2021-04-09 创建

    【讨论】:

      猜你喜欢
      • 2017-02-09
      • 1970-01-01
      • 1970-01-01
      • 2019-04-30
      • 1970-01-01
      • 2021-11-08
      • 1970-01-01
      相关资源
      最近更新 更多