为什么apply()方法比R中的for循环慢?

Mil*_*der 21 benchmarking r

作为最佳实践的问题,我试图确定apply()在矩阵中创建函数是否更好,或者如果通过函数简单地循环矩阵更好.我尝试了两种方式,并惊讶地发现apply()速度较慢.任务是取一个向量并将其评估为正数或负数,然后如果为正数则返回1,如果为负则返回-1.该mash()函数循环和squish()功能传递给apply()函数.

million  <- as.matrix(rnorm(100000))

mash <- function(x){
  for(i in 1:NROW(x))
    if(x[i] > 0) {
      x[i] <- 1
    } else {
      x[i] <- -1
    }
    return(x)
}

squish <- function(x){
  if(x >0) {
    return(1)
  } else {
    return(-1)
  }
}


ptm <- proc.time()
loop_million <- mash(million)
proc.time() - ptm


ptm <- proc.time()
apply_million <- apply(million,1, squish)
proc.time() - ptm
Run Code Online (Sandbox Code Playgroud)

loop_million 结果:

user  system elapsed 
0.468   0.008   0.483 
Run Code Online (Sandbox Code Playgroud)

apply_million 结果:

user  system elapsed 
1.401   0.021   1.423 
Run Code Online (Sandbox Code Playgroud)

什么是优势,使用apply()for,如果性能下降的循环?我的测试中有缺陷吗?我比较了两个结果对象的线索,发现:

> class(apply_million)
[1] "numeric"
> class(loop_million)
[1] "matrix"
Run Code Online (Sandbox Code Playgroud)

这只会加深神秘感.该apply()函数不能接受一个简单的数字向量,这就是我as.matrix()在开始时使用它的原因.但随后它返回一个数字.该for循环是精细用一个简单的数字矢量.它返回一个与传递给它的类相同的对象.

had*_*ley 39

apply(和plyr)函数族的要点不是速度,而是表达性.它们还倾向于防止错误,因为它们消除了循环所需的簿记代码.

最近,stackoverflow的答案过分强调了速度.随着计算机变得更快并且R-core优化R的内部,您的代码将变得更快.您的代码将永远不会变得更优雅或更容易理解.

在这种情况下,你可以充分利用两个世界:使用矢量化的优雅答案,也非常快(million > 0) * 2 - 1.

  • 这与我在R Inferno by Burns中发现的相反,应用函数族基本上是R循环,它们的好处不是速度.他称之为循环隐藏. (6认同)
  • 很抱歉提出这个问题,虽然我同意你的观点,即表现力和意图很重要,但我不同意等待电脑变得更快的态度。我昨天需要我的结果,我不能等待几天才能得到正确编写的东西,甚至没有优化,不需要超过几分钟 (2认同)

Jor*_*eys 12

正如Chase所说:利用矢量化的力量.你在这里比较两个不好的解决方案.

澄清为什么您的应用解决方案较慢:

在for循环中,实际上使用了矩阵的向量化索引,这意味着没有类型转换.我在这里稍微粗略一点,但基本上内部计算类型忽略了维度.它们只是作为属性保存,并返回表示矩阵的向量.为了显示 :

> x <- 1:10
> attr(x,"dim") <- c(5,2)
> y <- matrix(1:10,ncol=2)
> all.equal(x,y)
[1] TRUE
Run Code Online (Sandbox Code Playgroud)

现在,当您使用apply时,矩阵在内部以100,000个行向量分割,每个行向量(即单个数字)都通过该函数,最后将结果合并为适当的形式.apply函数在这种情况下估计向量是最好的,因此必须连接所有行的结果.这需要时间.

另外,sapply函数首先用于as.vector(unlist(...))将任何东西转换为向量,并最终尝试将答案简化为合适的形式.这也需要时间,因此这里的速度也可能较慢.然而,它不在我的机器上.

如果申请在这里是一个解决方案(它不是),你可以比较:

> system.time(loop_million <- mash(million))
   user  system elapsed 
   0.75    0.00    0.75    
> system.time(sapply_million <- matrix(unlist(sapply(million,squish,simplify=F))))
   user  system elapsed 
   0.25    0.00    0.25 
> system.time(sapply2_million <- matrix(sapply(million,squish)))
   user  system elapsed 
   0.34    0.00    0.34 
> all.equal(loop_million,sapply_million)
[1] TRUE
> all.equal(loop_million,sapply2_million)
[1] TRUE
Run Code Online (Sandbox Code Playgroud)


Cha*_*ase 6

如果需要,可以使用lapplysapply在矢量上.但是,在这种情况下,为什么不使用适当的工具ifelse()呢?

> ptm <- proc.time()
> ifelse_million <- ifelse(million > 0,1,-1)
> proc.time() - ptm
   user  system elapsed 
  0.077   0.007   0.093 

> all.equal(ifelse_million, loop_million)
[1] TRUE
Run Code Online (Sandbox Code Playgroud)

为了比较,这里是使用for循环和sapply的两个可比较的运行:

> ptm <- proc.time()
> apply_million <- sapply(million, squish)
> proc.time() - ptm
   user  system elapsed 
  0.469   0.004   0.474 
> ptm <- proc.time()
> loop_million <- mash(million)
> proc.time() - ptm
   user  system elapsed 
  0.408   0.001   0.417 
Run Code Online (Sandbox Code Playgroud)


Rei*_*son 5

ifelse()在这种情况下,进行基于索引的替换比、*apply()族或循环要快得多:

> million  <- million2 <- as.matrix(rnorm(100000))
> system.time(million3 <- ifelse(million > 0, 1, -1))
   user  system elapsed 
  0.046   0.000   0.044 
> system.time({million2[(want <- million2 > 0)] <- 1; million2[!want] <- -1}) 
   user  system elapsed 
  0.006   0.000   0.007 
> all.equal(million2, million3)
[1] TRUE
Run Code Online (Sandbox Code Playgroud)

所有这些工具都触手可及,非常值得。您可以使用对您最有意义的解决方案(因为您需要在数月或数年后理解代码),然后在计算时间变得令人望而却步时开始转向更优化的解决方案。

  • 或者更简洁、甚至更快,“(百万 &gt; 0) * 2 - 1”。 (4认同)