有几个原因可能会导致人们更喜欢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()
or更快apply()
,您需要意识到“循环”可以在解释的 R 代码或编译代码中执行。是的,两者仍将调用需要解释的 R 函数,但如果您正在执行循环并直接从编译的 C 代码(例如lapply()
)调用,那么这就是性能提升的来源,apply()
而归结为for()
循环在实际的 R 代码中。查看源代码apply()
以查看它是for()
循环的包装器,然后查看代码 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 调用 R 本身使用的已编译 C 代码的方法之一。除了对 的操作和完整性检查之外FUN
,整个计算都是在 C 中完成的,调用 R 函数FUN
。将其与apply()
.