> fibo <- function(n) {
+ if ( n < 2 ) n
+ else fibo(n-1) + fibo(n-2)
+ }
> system.time(for(i in 0:26) fibo(i))
user system elapsed
7.48 0.00 7.52
> system.time(sapply(0:26, fibo))
user system elapsed
7.50 0.00 7.54
> system.time(lapply(0:26, fibo))
user system elapsed
7.48 0.04 7.54
> library(plyr)
> system.time(ldply(0:26, fibo))
user system elapsed
7.52 0.00 7.58
有时候加速可能会很大,比如当您必须嵌套 for 循环以根据多个因子的分组获得平均值时。这里有两种方法可以得到完全相同的结果:
set.seed(1) #for reproducability of the results
# The data
X <- rnorm(100000)
Y <- as.factor(sample(letters[1:5],100000,replace=T))
Z <- as.factor(sample(letters[1:10],100000,replace=T))
# the function forloop that averages X over every combination of Y and Z
forloop <- function(x,y,z){
# These ones are for optimization, so the functions
#levels() and length() don't have to be called more than once.
ylev <- levels(y)
zlev <- levels(z)
n <- length(ylev)
p <- length(zlev)
out <- matrix(NA,ncol=p,nrow=n)
for(i in 1:n){
for(j in 1:p){
out[i,j] <- (mean(x[y==ylev[i] & z==zlev[j]]))
}
}
rownames(out) <- ylev
colnames(out) <- zlev
return(out)
}
# Used on the generated data
forloop(X,Y,Z)
# The same using tapply
tapply(X,list(Y,Z),mean)
两者给出的结果完全相同,都是一个5 x 10的矩阵,包含平均值和命名的行和列。但是:
> system.time(forloop(X,Y,Z))
user system elapsed
0.94 0.02 0.95
> system.time(tapply(X,list(Y,Z),mean))
user system elapsed
0.06 0.00 0.06
我在其他地方写过,像 Shane 这样的例子并没有真正强调各种循环语法之间的性能差异,因为所有的时间都花在函数中,而不是实际强调循环。此外,代码不公平地将没有内存的 for 循环与返回值的 application 族函数进行比较。这里有一个稍微不同的例子,它强调了这一点。
foo <- function(x) {
x <- x+1
}
y <- numeric(1e6)
system.time({z <- numeric(1e6); for(i in y) z[i] <- foo(i)})
# user system elapsed
# 4.967 0.049 7.293
system.time(z <- sapply(y, foo))
# user system elapsed
# 5.256 0.134 7.965
system.time(z <- lapply(y, foo))
# user system elapsed
# 2.179 0.126 3.301
如果你计划保存结果,那么应用家庭功能可以 很多多于句法糖。
(z 的简单 unlist 只有0.2 s,所以 lapplication 要快得多。在 for 循环中初始化 z 非常快,因为我给出了6次运行中最后5次的平均值,所以在 system. time 外移动几乎不会影响到任何东西)
不过,还有一点需要注意的是,使用应用家族函数还有另一个原因,与它们的性能、清晰度或缺乏副作用无关。for循环通常促进尽可能多地放入循环中。这是因为每个循环都需要设置变量来存储信息(以及其他可能的操作)。应用语句倾向于偏向于相反的方向。通常需要对数据执行多个操作,其中有几个可以向量化,但有些可能无法向量化。在 R 语言中,与其他语言不同的是,最好将这些操作分离出来,运行那些在 application 语句(或函数的向量化版本)中没有向量化的操作和那些作为真向量操作向量化的操作。这通常会极大地提高性能。
以 Joris Meys 为例,他用一个方便的 R 函数替换了传统的 for 循环,我们可以使用它来展示以更加 R 友好的方式编写代码的效率,以便在没有专用函数的情况下实现类似的加速。
set.seed(1) #for reproducability of the results
# The data - copied from Joris Meys answer
X <- rnorm(100000)
Y <- as.factor(sample(letters[1:5],100000,replace=T))
Z <- as.factor(sample(letters[1:10],100000,replace=T))
# an R way to generate tapply functionality that is fast and
# shows more general principles about fast R coding
YZ <- interaction(Y, Z)
XS <- split(X, YZ)
m <- vapply(XS, mean, numeric(1))
m <- matrix(m, nrow = length(levels(Y)))
rownames(m) <- levels(Y)
colnames(m) <- levels(Z)
m