“应用”功能有哪些优点?它们什么时候比“for”循环更好用,什么时候不用?

时间:2011-06-23 21:28:59

标签: parsing r for-loop compilation lapply

  

可能重复:
  Is R's apply family more than syntactic sugar

正如标题所说的那样。也许是个愚蠢的问题,但我的理解是,当使用“apply”函数时,迭代是在编译代码中而不是在R解析器中执行的。例如,如果存在大量迭代并且每个操作相对简单,那么这似乎意味着lapply仅比“for”循环更快。例如,如果对lapply中包含的函数的单个调用需要10秒,而且只有12次迭代,我会想到使用“for”和“lapply”之间几乎没有任何区别。

现在我想到了,如果必须解析“lapply”中的函数,为什么使用“lapply”而不是“for”会有任何性能上的好处,除非你做的事情是编译函数(如求和或乘法等)?

提前致谢!

约什

2 个答案:

答案 0 :(得分:14)

为什么人们可能更喜欢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 -fold或bootstrap交叉验证。由于每个CV迭代需要多次操作,访问当前帧中的大量对象,并填充包含迭代输出的多个输出对象,我可能永远不会接受apply系列之一的操作。 / p>

关于最后一点,关于为什么lapply()可能比for()apply()更快,你需要意识到“循环”可以在解释的R代码中执行或在编译的代码中。是的,两者仍然会调用需要解释的R函数,但是如果你正在进行循环并直接从编译的C代码调用(例如lapply())那么性能增益可以来自{{{ 1}}说实际R代码中的apply()循环。查看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是R调用R本身使用的编译C代码的方法之一。除了操作和.Internal()上的健全性检查之外,整个计算在C中完成,调用R函数FUN。将其与FUN的来源进行比较。

答案 1 :(得分:3)

来自Burns'R Inferno(pdf),第25页:

  

每个都使用明确的for循环   迭代是一项非常重要的任务。但是一个   简单循环可以更清楚   使用apply紧凑表达   功能。至少有一个   这个规则的例外......如果结果会   是一个列表和一些组件   可以是NULL,然后是for循环   麻烦(大麻烦)和lapply给出   预期的答案。