【问题标题】:how to speed up an R for loop?如何加快 R for 循环?
【发布时间】:2015-08-22 14:53:00
【问题描述】:

我正在为 R 中 GWmodel 包中的 gwr.basic 函数运行以下 for 循环。我需要做的是收集任何给定带宽的估计参数的平均值。

代码如下:

library(GWmodel)
data("DubVoter")
#Dub.voter


LARentMean = list()
for (i in 20:21)
{
gwr.res <- gwr.basic(GenEl2004 ~ DiffAdd + LARent + SC1 + Unempl + LowEduc + Age18_24 + Age25_44 + Age45_64, data = Dub.voter, bw = i,  kernel = "bisquare", adaptive = TRUE, F123.test = TRUE)
a <- mean(gwr.res$SDF$LARent)
LARentMean[i] <- a
}
outcome = unlist(LARentMean)

> outcome
[1] -0.1117668 -0.1099969

但是返回结果非常慢。我需要更大的范围,例如 20:200。有没有办法加快这个过程?如果没有,如何有一个步进范围,比如 20 到 200 步,步长为 5 以减少操作次数?

我是 R 的新 Python 用户。我在 SO 上读到 R 以 for 循环速度慢而闻名,并且有更有效的替代方案。欢迎更清楚地说明这一点。

【问题讨论】:

  • 您的问题似乎表明for 循环的每次迭代 非常昂贵,而不是for 循环本身。在这种情况下,for 循环替代方案(*applydplyr)可能没有多大帮助。或者您正在寻找并行化代码的解决方案?
  • 我只有两个核心。我不知道并行计算是否会有所改进
  • GWR 模型需要一段时间才能连接到计算机。其中 200 个需要更长的时间。您的瓶颈不在代码设计中。
  • 你能定义“非常慢”吗?我的 MacBook Pro 上的一次迭代 (bw=20) 的 microbenchmark 说它需要大约 9 秒才能运行。我不知道更改bw 值是否会导致模型变慢。如果您有更多的迭代,2 个内核可能会有所帮助,因为内存可能不是一个因素,因为 DubVoter 只有 1MB(它将为每个并行任务复制),但对于这少数它可能只会让你25-33% 的加速(可能更多,我不知道你的架构)。您可以使用 seq(20,200,5) 来提前 5 分钟,我建议安装 beep pkg 以在任务完成时发出声音通知。
  • 顺便说一句,在我的 Mac Pro 上迭代需要大约 7 秒。即使有 Matt 的回答,您也可能不喜欢它在系统上花费的时间。在强大的 EC2 实例上并行运行模型几乎不需要时间,并且花费您大约 5.00 美元。

标签: r for-loop gwmodel


【解决方案1】:

我得到了和@musically_ut 一样的印象。 for 循环和传统的for-vs.apply 辩论在这里不太可能对您有所帮助。如果您有多个内核,请尝试进行并行化。有几个包,如 parallelsnowfall。最终哪个软件包最好和最快取决于您的机器和操作系统。

这里最好并不总是等于最快。一个跨平台工作的代码,它的价值不仅仅是一点额外的性能。透明度和易用性也可以超过最大速度。话虽如此,我非常喜欢标准解决方案,并建议使用 R 附带的 parallel,它可以在 Windows、OSX 和 Linux 上运行。

编辑:这是使用 OP 示例的完全可重现的示例。

library(GWmodel)
data("DubVoter")

library(parallel)

bwlist <- list(bw1 = 20, bw2 = 21)


cl <- makeCluster(detectCores())

# load 'GWmodel' for each node
clusterEvalQ(cl, library(GWmodel))

# export data to each node
clusterExport(cl, varlist = c("bwlist","Dub.voter"))

out <- parLapply(cl, bwlist, function(e){
 try(gwr.basic(GenEl2004 ~ DiffAdd + LARent + SC1 +
 Unempl + LowEduc + Age18_24 + Age25_44 +
 Age45_64, data = Dub.voter,
 bw = e,  kernel = "bisquare",
 adaptive = TRUE, F123.test = TRUE  ))

} )


LArent_l <- lapply(lapply(out,"[[","SDF"),"[[","LARent")
unlist(lapply(LArent_l,"mean"))

# finally, stop the cluster
stopCluster(cl)

【讨论】:

  • 你将如何在那里构建 for 循环?
  • 我什至没有意识到您正在循环带宽,因为它在右边太远了,但是您可以使用像 list(bw1 = 20, bw2 = 21) 这样的列表和 parLapply 该列表,因为数据集似乎是在所有迭代中保持不变。另外我会在循环之外提取手段。
  • 这听起来很棒。您介意将它们全部包装在一个答案中吗?由于这是一个常见的话题(循环的慢 R),很高兴看到一个完整的例子
  • 等一下,我要建立一个例子。由于您没有提供可重复的示例,因此很难做出有用的东西。
  • 我写的那几行代码就是你需要的所有代码。数据已经在包中。你只需要安装GWmodel
【解决方案2】:

除了按照 Matt Bannert 的建议使用并行化之外,您还应该预先分配向量 LARentMean。通常,不是for 循环本身很慢,而是for 诱使您做一些缓慢的事情,例如创建不断增长的向量。

考虑以下示例,以了解与预分配内存相比,不断增长的向量的影响:

library(microbenchmark)

growing <- function(x) {
  mylist <- list()
  for (i in 1:x) {
    mylist[[i]] <- i
  }
}

allocate <- function(x) {
  mylist <- vector(mode = "list", length = x)
  for (i in 1:x) {
    mylist[[i]] <- i
  }
}

microbenchmark(growing(1000), allocate(1000), times = 1000)
# Unit: microseconds
#          expr      min       lq      mean   median       uq       max neval
# growing(1000) 3055.134 4284.202 4743.4874 4433.024 4655.616 47977.236  1000
# allocate(1000)  867.703  917.738  998.0719  956.441  995.143  2564.192  1000

不断增长的列表比预分配内存的版本慢大约 5 倍。

【讨论】:

  • 是的,但在这种情况下,模型运行是瓶颈而不是 for / list 操作,并且 OP 只会将数字列表增加 180。
  • 是的。因此,我写道并行化是 OP 实际需要的。但是,他还要求澄清 R 中的 for 循环有什么问题,以及为什么它们可能(不)很慢。由于他使用的列表越来越多,我试图详细说明这方面。
  • Microbenchmark 是一个好主意,可以在这里最终查看哪种解决方案最有帮助。我想我们在这里看到了一个最小的例子,因为@john red 的代码在我的机器上并没有真正花费很长时间。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-06-10
  • 2013-11-13
  • 1970-01-01
相关资源
最近更新 更多