为什么有些人更喜欢使用apply
类函数而不是for
循环?或者反之亦然,这其中有几个原因。
首先,如果正确执行,for()
以及apply()
, sapply()
的执行速度通常相当。相比之下,lapply()
在R内部的编译代码中完成了更多的操作,所以它可以比其他函数更快。这种速度优势似乎最明显的情况是当“循环”数据本身占用了较大的计算时间时;在一般的日常应用中,你可能难以从本质上更快的lapply()
中获得多少收益。总的来说,这些函数都会调用R函数,因此需要解释并运行。
for()
循环通常更容易实现,尤其是如果您来自于一个循环程序背景。使用循环可能比将迭代计算强制转化为apply
家族函数更自然。然而,为了正确使用for()
循环,您需要进行一些额外的工作来设置存储和管理将循环的输出重新组合的过程。apply
函数可以自动地为您完成这些操作,例如:
IN <- runif(10)
OUT <- logical(length = length(IN))
for(i in IN) {
OUT[i] <- IN > 0.5
}
这只是一个愚蠢的例子,因为>
是一个向量化运算符,但我想表达的是,你必须管理输出。主要问题在于,对于for()
循环,您始终需要分配足够的存储空间来容纳输出,然后才能开始循环。如果您不知道需要多少存储空间,那么就分配一个合理的块状存储空间,然后在循环中检查是否已用尽该存储空间,并添加另一个大块的存储空间。
在我看来,使用apply
函数族的主要原因是使代码更优雅、更易读。我们可以让R处理输出存储和设置循环(如上所示),并简洁地要求R在数据子集上运行函数。对我来说,速度通常不是决定因素。我使用最适合情况的函数,并且会生成简单易懂的代码,因为如果我无法记住代码一天、一周或更长时间,我浪费的时间可能比我节省的时间还要多!
apply
函数族适用于标量或向量操作。一个for()
循环通常适用于使用相同索引 i 执行多个迭代操作。例如,我编写的代码使用for()
循环在对象上进行 k 重或自举交叉验证。我可能永远不会考虑使用apply
函数族中的一个来完成这个任务,因为每个CV迭代需要多个操作,需要访问当前帧中的大量对象,并填充几个输出对象以容纳迭代的输出。
至于最后一点,即lapply()
为什么可能比for()
或apply()
更快,您需要意识到“循环”可以在解释的R代码或编译的代码中执行。是的,两者仍然会调用需要解释的R函数,但是如果您正在从编译的C代码(例如lapply()
)直接进行循环和调用,则性能提升可能就来自于此,而apply()
则相当于实际的R代码中的for()
循环。请查看apply()
的源代码,看看它是一个for()
循环的包装器,然后查看lapply()
的代码,如下所示:
> lapply
function (X, FUN, ...)
{
FUN <- match.fun(FUN)
if (!is.vector(X) || is.object(X))
X <- as.list(X)
.Internal(lapply(X, FUN))
}
<environment: namespace:base>
你应该能够看出为什么lapply()
和for()
与其他apply
系列函数的速度会有所不同。.Internal()
是R调用编译过的C代码的一种方式。除了对FUN
进行操作和检查外,整个计算都是在C中完成的,同时调用R函数FUN
。将其与apply()
的源代码进行比较。