当你有一个额外的变量时,在R中循环以创建许多图

bos*_*hek 5 loops r ggplot2

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

以下示例说明了此过程:

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=""))
}
Run Code Online (Sandbox Code Playgroud)

每当我看到在线参考循环时,每个人似乎总是表明循环通常不是R中的好策略.如果是这种情况,任何人都可以推荐一种更好的方法来实现与上面相同的结果吗?我特别感兴趣的是像SOOOO这样的循环更快的东西.但也许解决方案是这是最好的解决方案.如果有人能改进这一点,我只是很好奇.

提前致谢.

ati*_*too 4

这是一个经过深思熟虑的 R 主题,请参阅此处此处的 SO 帖子。这个问题的答案强调了提高清晰度、使并行化更容易以及在某些情况下加速解决问题的*apply()替代方案。for()然而,想必您真正的问题是“我怎样才能更快地完成这件事”,因为它花费的时间足够长,以至于您不高兴。在循环中,您正在执行 3 项不同的任务。

  1. 使用以下命令分解数据帧的一大块filter()
  2. 做一个情节。
  3. 将绘图保存为 jpeg。

有多种方法可以完成所有这三个步骤,因此让我们尝试并评估所有这些步骤。我将使用 ggplot2 中的钻石数据,因为它比汽车数据大。我希望通过这种方式,方法之间的性能差异会很明显。我从哈德利·威克姆(Hadley Wickham)关于衡量绩效的书中的这一章学到了很多东西。

为了能够使用分析,我将以下代码块放入函数中,并将其保存在名为 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=""))
  }
}
Run Code Online (Sandbox Code Playgroud)

然后我这样做:

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

检查该输出,我发现代码块花费 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()
}
Run Code Online (Sandbox Code Playgroud)

现在再次进行分析

Rprof(line=TRUE)
f1()
Rprof(NULL)
summaryRprof(lines="show")
Run Code Online (Sandbox Code Playgroud)

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

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

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()
Run Code Online (Sandbox Code Playgroud)

运行该代码给出

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

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

我必须在这里停下来,但我已经学到了很多关于将注意力集中在哪里的知识。其他可以尝试的事情包括:

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