【问题标题】:Large loops hang in R?大循环挂在R?
【发布时间】:2010-11-13 06:20:30
【问题描述】:

假设我想使用以下function 执行模拟:

fn1 <- function(N) {
  res <- c()
  for (i in 1:N) {
    x <- rnorm(2)
    res <- c(res, x[2]-x[1])
  }
  res
}

对于非常大的N,计算似乎挂起。有更好的方法吗?

(灵感来自:https://stat.ethz.ch/pipermail/r-help/2008-February/155591.html

【问题讨论】:

    标签: r for-loop


    【解决方案1】:

    也许对您的功能最有效的替代品就是:

    fn <- function(n) rnorm(N,0,sqrt(2))
    

    这比 iid 正态变量的差异快两倍。更一般地说,如果您的目标是运行简单的模拟,则向量/数组预分配和对本机函数的调用会大大加快该过程。

    如果您想为统计估计执行蒙特卡罗模拟(例如 MCMC),R 有许多本机包。对于一般随机模拟,我不知道 R 包,但您可能想尝试 Simpy (http://simpy.sourceforge.net/),它非常棒。

    【讨论】:

      【解决方案2】:

      扩展我对 chris_dubois 回答的评论,这里有一些时间信息:

      > system.time(res <- rnorm(50000) - rnorm(50000))
      user  system elapsed
      0.06    0.00    0.06
      

      将此与来自同一答案的 fn3 进行对比:

      > system.time(res3 <- fn3(50000))
      user  system elapsed
      1.33    0.01    1.36
      

      首先要注意的是我的笔记本电脑比 chris_dubois 的机器慢。 :)

      第二点,也是更重要的一点是,这里非常适用的向量方法要快一个数量级。 (Richie Cotton 在对同一答案的评论中也指出)。

      这让我想到了最后一点:apply 和它的朋友在 R 中的循环比 for 快得多是一个神话。在大多数测量中它们的顺序相同我见过。因为它们只是 for 在幕后循环。另见这篇文章:

      http://yusung.blogspot.com/2008/04/speed-issue-in-r-computing-apply-vs.html

      根据 Brian Ripley 教授的说法,“apply() 只是一个循环的包装器。”使用 apply() 的唯一好处是它可以让你的代码更整洁!

      没错。如果apply表现力,你应该使用它,特别是如果你以函数式风格进行编程。不是因为它更快。

      【讨论】:

      • 好点。我提出这个问题的初衷是强调预分配可能是一件好事。正如您所指出的,这个特定的例子可以很容易地只用向量操作来完成。很高兴有一些其他示例,其中人们展示了优化 R 代码的替代方案(有点像wiki.r-project.org/rwiki/…)。想法?
      • 嘿,这是个好主意——可悲的是,我对那个维基一无所知。我知道我通过阅读其他人编写的代码遇到了向量优化的几个循环——最近查看了 Heagerty 的一些代码来构建变异函数。我倾向于认为这是常识,对其他人来说并不值得注意,但最好是在记录它方面犯错。我将浏览我的文件并找到一些特定的内容以添加到 wiki,希望在本周末之前。您对如何构建它有任何想法吗?我们是否应该创建一个“矢量化提示”页面并在必要时将其拆分?
      • apply 可能和 for 循环一样快,但 lapply 和(尤其是)vapply 通常更快,因为它们是用 C 实现的,并且优化了函数 @ 的调用987654332@。但这仅在FUN 花费的实际时间很短时才有意义。
      【解决方案3】:

      R 中的 For 循环是出了名的慢,但这里还有另一个问题。预先分配结果向量 res 比在每次迭代时附加到 res 上要快得多。

      下面我们可以将上述版本的速度与仅以长度为 N 的向量 res 开始并在循环期间更改第 i 个元素的版本的速度进行比较。

      fn1 <- function(N) {
        res <- c()
        for (i in 1:N) {
           x <- rnorm(2)
           res <- c(res,x[2]-x[1])
        }
        res
      }
      fn2 <- function(N) {
        res <- rep(0,N)
        for (i in 1:N) {
           x <- rnorm(2)
           res[i] <- x[2]-x[1]
        }
        res
      }
      > N <- 50000
      > system.time(res1 <- fn1(N))
         user  system elapsed 
        6.568   0.256   6.826 
      > system.time(res2 <- fn2(N))
         user  system elapsed 
        0.452   0.004   0.496 
      

      另外,作为Sharpie points out,我们可以通过使用像apply(或其亲属,sapplylapply)这样的R 函数来稍微加快速度。

      fn3 <- function(N) {
        sapply( 1:N, function( i ){ x <- rnorm(2); return( x[2] - x[1] ) } )
      }
      > system.time(res3 <- fn3(N))
         user  system elapsed 
        0.397   0.004   0.397 
      

      【讨论】:

      • R 列表线程中的第二个答案有什么问题:res
      • @ars:你是绝对正确的——这给出了最快的解决方案(一个数量级)。最好的建议是 1. 使用自然适用于向量的函数(就像 rnorm 一样); 2. 做不到这一点,使用 *apply 函数; 3. 做不到这一点,使用带有预分配的 for 循环。
      【解决方案4】:

      有时不需要循环。由于 rnorm 提供 iid 样本(理论上),您将获得相同的结果(采样 X-Y 其中 X 和 Y 是 N(0,1)) 通过这样做:

      res <- rnorm(N)-rnorm(N)
      

      【讨论】:

        【解决方案5】:

        在 R 中,通过使用 apply 函数可以极大地提高循环的效率,这些函数本质上是一次处理整个数据向量,而不是遍历它们。对于上面显示的循环,每次迭代期间都会发生两个基本操作:

        # A vector of two random numbers is generated
        x <- rnorm( 2 )
        
        # The difference between those numbers is calculated
        x[2] - x[1]
        

        在这种情况下,适当的函数是sapply()sapply() 对对象列表进行操作,例如循环语句1:N 生成的向量,并返回结果向量:

        sapply( 1:N, function( i ){ x <- rnorm(2); return( x[2] - x[1] ) } )
        

        请注意,索引值i在函数调用期间可用,并依次采用1N之间的值,但在这种情况下不需要。

        养成识别apply 可以在for 上使用的习惯是一项非常有价值的技能——许多用于并行计算的R 库通过apply 函数提供即插即用并行化。使用apply 通常可以通过代码重构在多核系统上获得显着的性能提升。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-05-20
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多