使用sapply与for有效地写入预分配的数据结构

时间:2012-10-19 12:06:24

标签: performance r

假设我有一个预先分配的数据结构,为了性能而不是随着时间的推移而增加数据结构。首先,我尝试使用sapply:

set.seed(1)
count <- 5
pre <- numeric(count)

sapply(1:count, function(i) {
  pre[i] <- rnorm(1)
})
pre
# [1] 0 0 0 0 0


for(i in 1:count) {
  pre[i] <- rnorm(1)
}
pre
# [1] -0.8204684  0.4874291  0.7383247  0.5757814 -0.3053884

我认为这是因为sapply中的匿名函数属于不同的范围(或者是R中的环境?),因此pre对象不一样。 for循环存在于相同的范围/环境中,因此它按预期工作。

我一般都试图采用R机制进行迭代,使用apply函数而不是for,但我在这里看不到解决方法。对于这种类型的操作,我应该做些什么不同或更好的习惯用法吗?

如上所述,我的例子非常人为,我对正常偏差不感兴趣。相反,我的实际代码是处理4列150万行数据帧。以前我依靠增长和合并来获得最终的数据帧,并决定尝试避免合并并根据基准测试进行预分配。

4 个答案:

答案 0 :(得分:7)

sapply并不意味着像这样使用。它已经预先分配了结果。

无论如何,for循环不太可能是性能缓慢的源头;这可能是因为你反复对data.frame进行子集化。例如:

set.seed(21)
N <- 1e4
d <- data.frame(n=1:N, s=sample(letters, N, TRUE))
l <- as.list(d)
set.seed(21)
system.time(for(i in 1:N) { d$n[i] <- rnorm(1); d$s <- sample(letters,1) })
#   user  system elapsed 
#   6.12    0.00    6.17 
set.seed(21)
system.time(for(i in 1:N) { l$n[i] <- rnorm(1); l$s <- sample(letters,1) })
#   user  system elapsed 
#   0.14    0.00    0.14 
D <- as.data.frame(l, stringsAsFactors=FALSE)
identical(d,D)
# [1] TRUE

所以你应该遍历各个向量,并在循环后将它们组合成一个data.frame

答案 1 :(得分:3)

apply系列不适用于副作用生成任务,例如更改变量的状态。这些函数仅用于返回值,然后将其分配给变量。这与R部分订阅的功能范例一致。如果您按预期使用这些功能,预分配不会出现太多,这是其吸引力的一部分。您无需预先分配即可轻松完成此操作:p <- sapply(1:count, function(i) rnorm(1))。但是这个例子有点人为 - p <- rnorm(5)就是你要用的。

如果您的实际问题与此不同,并且您遇到效率问题,请查看vapply。它就像sapply,但允许您指定结果数据类型,从而为其提供速度优势。如果无法提供帮助,请查看包data.tableff

答案 2 :(得分:2)

是的,你实际上是在改变匿名函数本地的pre,它本身会返回上一次评估的结果(长度为1的向量),因此sapply()会返回正确的解决方案作为向量(因为它累积了单个长度为1的向量)但它并没有改变全局工作区中的pre

您可以使用<<-运算符解决此问题:

set.seed(1)
count <- 5
pre <- numeric(count)

sapply(1:count, function(i) {
  pre[i] <<- rnorm(1)
})
> pre
[1] -0.6264538  0.1836433 -0.8356286  1.5952808  0.3295078

哪个已更改pre,但我会因各种原因避免这样做。

在这种情况下,我不认为在pre案例中预先分配sapply()可以获得很多好处。


此外,对于这个例子,两者都非常低效;只需获取rnorm()即可生成count个随机数。但我想这个例子只是为了说明这一点?

答案 3 :(得分:1)

我不确定你在问什么。在这种情况下传统的传统习语是

pre <- sapply( 1:count, function(x) rnorm(1) )

您根本不必预先分配,但不限制使用预分配变量。

如果你提出你想要改变的实际循环,我猜测事情会更清楚。你说你遇到了性能问题,你可能会在这里得到一个可以真正优化大量事情的答案。有几位回答者喜欢这样的挑战。

听起来你也有很长的功能或循环。 apply系列函数主要用于表达性,并允许您在混合矢量化函数和不可能的函数时更清晰。与向量化函数混合的多个小sapply调用比R中的一个大循环快得多。