R的应用家庭不仅仅是语法糖吗?

关于执行时间和/或记忆。

如果不是这样,请用代码片段来certificate。 请注意,vector化加速不计算在内。 加速必须来自applytapplysapply ,…)本身。

R中的apply函数不会比其他循环函数(例如for )提供更好的性能。 有一个例外是lapply ,它可以快一点,因为它在C代码中比在R中做了更多的工作(参见这个问题的一个例子 )。

但总体而言,规则是您应该使用apply函数来清晰,而不是performance

我会补充一点, 应用函数没有副作用 ,这在使用R进行函数式编程时是一个重要的区别。这可以通过使用assign<<-来覆盖,但这可能是非常危险的。 由于variables的状态取决于历史,所以副作用也使程序更难理解。

编辑:

只是用recursion计算斐波纳契数列的一个简单例子来强调这一点; 这可以运行多次以获得准确的度量,但重点是这些方法都没有显着不同的性能:

 > 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 

编辑2:

关于R的并行软件包(例如rpvm,rmpi,snow)的使用,通常会提供apply系列函数(即使名称上的foreach软件包本质上是相同的)。 下面是一个简单的例子,在snowsapplyfunction:

 library(snow) cl <- makeSOCKcluster(c("localhost","localhost")) parSapply(cl, 1:20, get("+"), 3) 

这个例子使用一个套接字集群,不需要安装额外的软件。 否则你需要类似PVM或MPI的东西(参见Tierney的集群页面 )。 snow具有以下应用function:

 parLapply(cl, x, fun, ...) parSapply(cl, X, FUN, ..., simplify = TRUE, USE.NAMES = TRUE) parApply(cl, X, MARGIN, FUN, ...) parRapply(cl, x, fun, ...) parCapply(cl, x, fun, ...) 

因为它们没有副作用 ,所以apply函数应该用于并行执行是有意义的。 在for循环中更改variables值时,它是全局设置的。 另一方面,所有apply函数都可以安全地并行使用,因为函数调用是局部的(除非您尝试使用assign<<- ,在这种情况下您可以引入副作用)。 不用说,注意局部variables和全局variables是非常重要的,特别是在处理并行执行时。

编辑:

这里有一个简单的例子来certificate在和副作用有关的情况下,

 > df <- 1:10 > # *apply example > lapply(2:3, function(i) df <- df * i) > df [1] 1 2 3 4 5 6 7 8 9 10 > # for loop example > for(i in 2:3) df <- df * i > df [1] 6 12 18 24 30 36 42 48 54 60 

请注意父级环境中的df是如何被修改的for但不*apply

有时候加速可能是很大的,就像当你为了获得基于一个以上因素的分组的平均值而嵌套循环一样。 在这里你有两种方法给你完全相同的结果:

 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×10matrix。 但是:

 > 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 

你走了 我赢了什么? 😉

…就像我刚才在别处写的那样,vapply是你的朋友! …就像sapply,但是你也指定了返回值types,这使得它更快。

 > system.time({z <- numeric(1e6); for(i in y) z[i] <- foo(i)}) user system elapsed 3.54 0.00 3.53 > system.time(z <- lapply(y, foo)) user system elapsed 2.89 0.00 2.91 > system.time(z <- vapply(y, foo, numeric(1))) user system elapsed 1.35 0.00 1.36 

我在其他地方写过类似Shane的例子,并没有真正强调各种循环语法在性能上的差异,因为时间都花在了函数中,而不是实际上强调循环。 此外,代码不公平地比较了没有内存的for循环和应用返回值的系列函数。 这是一个稍微不同的例子,强调这一点。

 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 

如果您打算保存结果,那么应用家庭function可能不止是语法糖。

(z的简单unlist只有0.2s,所以lapply要快得多。在for循环中初始化z是相当快的,因为我给出了6次运行的最后5次的平均值,以便在system之外移动。几乎不影响东西)

还有一点需要注意的是,还有另外一个原因,使用适用的家庭function独立于其性能,清晰度或缺乏副作用。 for循环通常会促进尽可能多地在循环中放置。 这是因为每个循环都需要设置variables来存储信息(以及其他可能的操作)。 应用语句往往偏向另一种方式。 通常情况下,您希望对数据执行多个操作,其中有些可以是vector化的,但有些可能无法执行。 在R中,与其他语言不同的是,最好将这些操作分离出来,并运行在应用语句(或向量化版本的函数)中未vector化的操作,以及作为vector化操作vector化的操作。 这通常会极大地提高性能。

以Joris Meys为例,他用一个方便的R函数replace传统的for循环,我们可以用它来显示以更友好的方式编写代码的效率,以便在没有专门function的情况下进行类似的加速。

 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 

这比for循环要快得多for而且比内置的优化的tapply函数慢了一点。 这并不是因为vapplyfor更快for而是因为它在循环的每次迭代中只执行一个操作。 在这个代码中,一切都是vector化的。 在Joris Meys传统的for循环中,每次迭代都会发生很多(7?)操作,并且只需要执行一些相关的设置。 还要注意这个版本比这个版本更紧凑。

当在一个向量的子集上应用函数时, tapply可以比for循环快得多。 例:

 df <- data.frame(id = rep(letters[1:10], 100000), value = rnorm(1000000)) f1 <- function(x) tapply(x$value, x$id, sum) f2 <- function(x){ res <- 0 for(i in seq_along(l <- unique(x$id))) res[i] <- sum(x$value[x$id == l[i]]) names(res) <- l res } library(microbenchmark) > microbenchmark(f1(df), f2(df), times=100) Unit: milliseconds expr min lq median uq max neval f1(df) 28.02612 28.28589 28.46822 29.20458 32.54656 100 f2(df) 38.02241 41.42277 41.80008 42.05954 45.94273 100 

但是,在大多数情况下,不会提供任何速度增加,在某些情况下甚至会慢很多:

 mat <- matrix(rnorm(1000000), nrow=1000) f3 <- function(x) apply(x, 2, sum) f4 <- function(x){ res <- 0 for(i in 1:ncol(x)) res[i] <- sum(x[,i]) res } > microbenchmark(f3(mat), f4(mat), times=100) Unit: milliseconds expr min lq median uq max neval f3(mat) 14.87594 15.44183 15.87897 17.93040 19.14975 100 f4(mat) 12.01614 12.19718 12.40003 15.00919 40.59100 100 

但是对于这些情况我们有colSumsrowSums

 f5 <- function(x) colSums(x) > microbenchmark(f5(mat), times=100) Unit: milliseconds expr min lq median uq max neval f5(mat) 1.362388 1.405203 1.413702 1.434388 1.992909 100