【问题标题】:Looping in R to create many plots when you have one extra variable当您有一个额外的变量时,在 R 中循环以创建许多图
【发布时间】:2015-12-09 23:58:29
【问题描述】:

我经常遇到包含太多分类变量的数据,无法令人满意地绘制在一个图上。当出现这种情况时,我会写一些东西来循环一个变量并保存几个特定于该变量的图。

下面的例子说明了这个过程:

library(tidyr)
library(dplyr)
library(ggplot2)

mtcars <- add_rownames(mtcars, "car")

param<-unique(mtcars$cyl)
for (i in param)
{
mcplt <- mtcars %>% filter(cyl==i) %>% ggplot(aes(x=mpg, y=hp)) +
    geom_point() +
    facet_wrap(~car) +
    ggtitle(paste("Cylinder Type: ",i,sep=""))
  ggsave(mcplt, file=paste("Type",i,".jpeg",sep=""))
}

每当我在网上看到关于循环的引用时,每个人似乎总是表示循环在 R 中通常不是一个好的策略。如果是这种情况,谁能推荐一种更好的方法来实现与上述相同的结果?我会对更快的东西特别感兴趣,因为循环太慢了。但也许解决方案是这是最好的解决方案。我只是好奇是否有人可以对此进行改进。

提前致谢。

【问题讨论】:

  • 我认为你的循环写得很棒。循环在 R 中得到了很多非常不公平的负面宣传。
  • 同意循环没问题。代码加载 tidyr 但不使用它;循环缩进可以改进,paste(..., sep = "") 最好写成paste0(...) 或使用sprintf
  • R 中经常不鼓励使用循环,因为许多常见的循环样式函数是作为基本 R 函数内置的,应该按原样使用,但因为这些在低级语言中不存在,具有其他语言背景的程序员可能会倾向于使用循环,即使在简单的情况下,例如计算向量/数组的元素之和。当然,这可以扩展到更复杂的情况。但我同意使用for 循环来生成几个(图形上)相同的不同变量的图是一个非常好的用途,就像我自己做的那样。
  • 我确实相信这个问题更多地是关于为什么循环在 R 中是(据说是)一个坏主意,而不是关于情节的具体案例。
  • 我认为在这种情况下循环可以正常工作。你也可以使用lapply。在我看来,一个更常见的循环(正确地)得到坏名声的情况是使用循环而不是矢量化。

标签: r loops ggplot2


【解决方案1】:

这是一个经过深思熟虑的 R 主题,请参阅 SO 帖子 herehereAnswers to this question 强调 *apply() 替代 for() 提高了清晰度,使并行化更容易,并在某些情况下加速问题。但是,大概您的真正问题是“我如何更快地做到这一点”,因为它花费的时间足够长,以至于您不满意。在您的循环中,您正在执行 3 个不同的任务。

  1. 使用filter() 拆分数据帧的一部分
  2. 画一个情节。
  3. 将绘图保存为 jpeg。

有多种方法可以完成所有这三个步骤,因此让我们尝试评估所有这些步骤。我将使用来自 ggplot2 的菱形数据,因为它比汽车数据大。我希望通过这种方式,方法之间的性能差异会很明显。我从this chapter of Hadley Wickham's book on measuring performance学到了很多东西。

为了可以使用分析,我将以下代码块放入函数中,并将其保存在名为 for_solution.r 的单独 R 文件中。

f <- function(){
  param <- unique(diamonds$cut)
  for (i in param){
    mcplt <- diamonds %>% filter(cut==i) %>% ggplot(aes(x=carat, y=price)) +
      geom_point() +
      facet_wrap(~color) +
      ggtitle(paste("Cut: ",i,sep=""))
    ggsave(mcplt, file=paste("Cut",i,".jpeg",sep=""))
  }
}

然后我做:

library(dplyr)
library(ggplot2)
source("for_solution.r",keep.source=TRUE)
Rprof(line=TRUE)
f()
Rprof(NULL)
summaryRprof(lines="show")

检查该输出,我发现代码块花费了 97.25% 的时间只是保存文件。检查ggsave() 的源代码我可以看到该函数正在执行大量防御性编程来识别输出类型,然后打开图形设备,打印,然后关闭设备。所以我想知道手动执行该步骤是否会有所帮助。我还将利用 jpeg 设备会自动为每个页面生成新文件,只打开和关闭设备一次这一事实。

f1 <- function(){
  param <- unique(diamonds$cut)
  jpeg("cut%03d.jpg",width=par("din")[1],height=par("din")[2],units="in",res=300) # open the jpeg device, change defaults to match ggsave()
  for (i in param){
    mcplt <- diamonds %>% filter(cut==i) %>% ggplot(aes(x=carat, y=price)) +
      geom_point() +
      facet_wrap(~color) +
      ggtitle(paste("Cut: ",i,sep=""))
    print(mcplt)
  }
  dev.off()
}

现在再次进行分析

Rprof(line=TRUE)
f1()
Rprof(NULL)
summaryRprof(lines="show")

f1() 仍然将大部分时间花在print(mcplt) 上,并且比以前稍快(1.96 秒比 2.18 秒)。加快速度的一种可能方法是使用更小的设备(分辨率更低或图像更小);当我使用jpeg() 的默认值时,差异更大,快了 25%。我还尝试将设备更改为png(),但这并没有什么不同。

根据分析,我不希望这会有所帮助,但为了完整起见,我将尝试取消 for 循环并使用 do() 在 dplyr 中运行所有内容。我发现 this questionthis one 在这里很有帮助。

jpeg("cut%03d.jpg",width=par("din")[1],height=par("din")[2],units="in",res=300) # open the jpeg device, change defaults to match ggsave()
plots = diamonds %>% group_by(cut) %>% 
  do({plot=ggplot(aes(x=carat, y=price),data=.) +
      geom_point() +
      facet_wrap(~color) +
      ggtitle(paste("Cut: ",.$cut,sep="")) 
    print(plot)})

dev.off()

运行该代码给出

错误:结果不是位置的数据帧:1、2、3

但它似乎工作。我相信当 do() 返回时会出现错误,因为 print() 方法没有返回 data.frame。分析它似乎表明它运行得更快一些,总共 1.78 秒。但我不喜欢产生错误的解决方案,即使它们不会引起问题。

我不得不在这里停下来,但我已经了解了很多关于将注意力集中在哪里的知识。其他可以尝试的方法包括:

  1. 使用parallel 或类似的东西在单独的进程中运行数据帧的每个块。如果问题是保存文件,我不确定这会有所帮助,但如果渲染图像是由 CPU 完成的,我认为。
  2. 尝试使用 data.table 代替 dplyr,但同样是打印部分很慢。
  3. 尝试使用基本图形和点阵图形和 plotly 代替 ggplot2。我不知道相对速度,但它可能会有所不同。
  4. 购买更快的硬盘!我只是比较了我的家用电脑上的 f() 和普通硬盘驱动器的速度和我的工作机器上的 SSD ——它比上面的时间慢了大约 3 倍。

【讨论】:

  • 哇。这非常有帮助。谢谢你。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-01-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多