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

时间:2011-04-03 23:31:27

标签: r benchmarking

作为最佳实践的问题,我试图确定在矩阵中创建函数和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

loop_million结果:

user  system elapsed 
0.468   0.008   0.483 

apply_million结果:

user  system elapsed 
1.401   0.021   1.423 

如果性能下降,在apply()循环上使用for有什么好处?我的测试中有缺陷吗?我比较了两个结果对象的线索并找到了:

> class(apply_million)
[1] "numeric"
> class(loop_million)
[1] "matrix"

这只会加深神秘感。 apply()函数无法接受简单的数字向量,这就是我在开头使用as.matrix()进行投射的原因。但随后它返回一个数字。使用简单的数字向量,for循环很好。它返回一个与传递给它的类相同的对象。

5 个答案:

答案 0 :(得分:39)

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

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

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

答案 1 :(得分:12)

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

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

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

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

现在,当你使用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

答案 2 :(得分: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

为了比较,这里是使用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 

答案 3 :(得分:4)

在这种情况下,基于索引的替换要比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

非常值得拥有所有这些工具。您可以使用对您最有意义的那个(因为您需要在几个月或几年后理解代码),然后在计算时间变得过高时开始转向更优化的解决方案。

答案 4 :(得分:3)

for loop的速度优势的更好例子。

for_loop <- function(x){
    out <- vector(mode="numeric",length=NROW(x))
    for(i in seq(length(out)))
        out[i] <- max(x[i,])
    return(out)
    }

apply_loop <- function(x){
    apply(x,1,max)
}

million  <- matrix(rnorm(1000000),ncol=10)
> system.time(apply_loop(million))
  user  system elapsed 
  0.57    0.00    0.56 
> system.time(for_loop(million))
  user  system elapsed 
  0.32    0.00    0.33 

编辑

Eduardo建议的版本。

max_col <- function(x){
    x[cbind(seq(NROW(x)),max.col(x))]
}

按行

> system.time(for_loop(million))
   user  system elapsed 
   0.99    0.00    1.11 
> system.time(apply_loop(million))
  user  system elapsed 
   1.40    0.00    1.44 
> system.time(max_col(million))
  user  system elapsed 
  0.06    0.00    0.06 

按栏目

> system.time(for_loop(t(million)))
  user  system elapsed 
  0.05    0.00    0.05 
> system.time(apply_loop(t(million)))
  user  system elapsed 
  0.07    0.00    0.07 
> system.time(max_col(t(million)))
  user  system elapsed 
  0.04    0.00    0.06