最佳答案
因此,我们习惯于对每个 R 新用户说“ apply
不是矢量化的,看看帕特里克 · 伯恩斯(Patrick Burns)的《地狱之火》第四卷”,它说(我引用) :
一个常见的反射是在 application 家族中使用一个函数 向量化,它是循环隐藏 Lapplication 函数隐藏循环,但是执行 时间趋向于大致等于显式 for 循环。
事实上,快速浏览一下 apply
源代码就会发现这个循环:
grep("for", capture.output(getAnywhere("apply")), value = TRUE)
## [1] " for (i in 1L:d2) {" " else for (i in 1L:d2) {"
到目前为止还好,但是看看 lapply
或者 vapply
实际上揭示了一幅完全不同的画面:
lapply
## function (X, FUN, ...)
## {
## FUN <- match.fun(FUN)
## if (!is.vector(X) || is.object(X))
## X <- as.list(X)
## .Internal(lapply(X, FUN))
## }
## <bytecode: 0x000000000284b618>
## <environment: namespace:base>
所以很明显,这里没有隐藏 Rfor
循环,而是调用了内部 C 编写的函数。
此外,让我们以 colMeans
函数为例,它从来没有因为没有向量化而受到指责
colMeans
# function (x, na.rm = FALSE, dims = 1L)
# {
# if (is.data.frame(x))
# x <- as.matrix(x)
# if (!is.array(x) || length(dn <- dim(x)) < 2L)
# stop("'x' must be an array of at least two dimensions")
# if (dims < 1L || dims > length(dn) - 1L)
# stop("invalid 'dims'")
# n <- prod(dn[1L:dims])
# dn <- dn[-(1L:dims)]
# z <- if (is.complex(x))
# .Internal(colMeans(Re(x), n, prod(dn), na.rm)) + (0+1i) *
# .Internal(colMeans(Im(x), n, prod(dn), na.rm))
# else .Internal(colMeans(x, n, prod(dn), na.rm))
# if (length(dn) > 1L) {
# dim(z) <- dn
# dimnames(z) <- dimnames(x)[-(1L:dims)]
# }
# else names(z) <- dimnames(x)[[dims + 1]]
# z
# }
# <bytecode: 0x0000000008f89d20>
# <environment: namespace:base>
啊?它也只是调用 .Internal(colMeans(...
,我们也可以在 兔子洞中找到它。那么这和 .Internal(lapply(..
有什么不同呢?
实际上,一个快速的基准测试表明,对于大数据集,sapply
的性能并不比 colMeans
差,而且比 for
循环好得多
m <- as.data.frame(matrix(1:1e7, ncol = 1e5))
system.time(colMeans(m))
# user system elapsed
# 1.69 0.03 1.73
system.time(sapply(m, mean))
# user system elapsed
# 1.50 0.03 1.60
system.time(apply(m, 2, mean))
# user system elapsed
# 3.84 0.03 3.90
system.time(for(i in 1:ncol(m)) mean(m[, i]))
# user system elapsed
# 13.78 0.01 13.93
换句话说,说 lapply
和 vapply
实际上是矢量化的(与 apply
相比,apply
是一个 for
循环,也调用 lapply
)是否正确? 帕特里克 · 伯恩斯到底想说什么?