作为最佳实践的问题,我试图确定在矩阵中创建函数和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
循环很好。它返回一个与传递给它的类相同的对象。
答案 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)
如果需要,您可以在矢量上使用lapply
或sapply
。但是,为什么不在作业中使用适当的工具,在这种情况下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