最大的问题和无效性的根源是数据框的索引,我的意思是你使用 temp[,]
的所有这些行。
尽可能避免使用这种方式。我采用了你的函数,更改了索引,这里是version_A
dayloop2_A <- function(temp){
res <- numeric(nrow(temp))
for (i in 1:nrow(temp)){
res[i] <- i
if (i > 1) {
if ((temp[i,6] == temp[i-1,6]) & (temp[i,3] == temp[i-1,3])) {
res[i] <- temp[i,9] + res[i-1]
} else {
res[i] <- temp[i,9]
}
} else {
res[i] <- temp[i,9]
}
}
temp$`Kumm.` <- res
return(temp)
}
正如您所看到的,我创建了向量 res
来收集结果。最后,我将其添加到 data.frame
中,而无需修改名称。
那么这样做有多好呢?
我为每个使用 data.frame
的函数运行测试,并使用 system.time
测量从 1,000 到 10,000 的 nrow
,间隔为 1,000。
X <- as.data.frame(matrix(sample(1:10, n*9, TRUE), n, 9))
system.time(dayloop2(X))
结果是
您可以看到,您的版本与nrow(X)
成指数关系。修改后的版本具有线性关系,并且简单的lm
模型预测,对于850,000行计算需要6分10秒。
向量化的优势
正如Shane和Calimo在他们的答案中所述,向量化是提高性能的关键。
从您的代码中,您可以移出循环:
这导致以下代码:
dayloop2_B <- function(temp){
cond <- c(FALSE, (temp[-nrow(temp),6] == temp[-1,6]) & (temp[-nrow(temp),3] == temp[-1,3]))
res <- temp[,9]
for (i in 1:nrow(temp)) {
if (cond[i]) res[i] <- temp[i,9] + res[i-1]
}
temp$`Kumm.` <- res
return(temp)
}
这些函数的比较结果,这次是对nrow
从10,000到100,000每隔10,000进行一次。
调整已调整
另一个调整是在循环索引temp[i,9]
到res[i]
(它们在第i个循环迭代中完全相同)中进行更改。
这再次是向量索引和data.frame
索引之间的差异。
第二件事:当您查看循环时,可以看到没有必要循环所有i
,而只需循环符合条件的那些。
所以我们来吧
dayloop2_D <- function(temp){
cond <- c(FALSE, (temp[-nrow(temp),6] == temp[-1,6]) & (temp[-nrow(temp),3] == temp[-1,3]))
res <- temp[,9]
for (i in (1:nrow(temp))[cond]) {
res[i] <- res[i] + res[i-1]
}
temp$`Kumm.` <- res
return(temp)
}
性能的提升高度依赖于数据结构。确切地说,它依赖于条件中
TRUE
值所占的百分比。对于我模拟的数据,在 850,000 行以下的计算时间不到一秒钟。
如果你想更进一步,以下是至少两件可以做的事情:
- 编写一个
C
代码来执行条件累加。
如果您知道您的数据中最大序列不是很大,那么您可以将循环改为向量化 while 循环,类似这样:
while (any(cond)) {
indx <- c(FALSE, cond[-1] & !cond[-n])
res[indx] <- res[indx] + res[which(indx)-1]
cond[indx] <- FALSE
}
用于模拟和图表的代码可以在 GitHub 上获取。
if(i%%1000) {print(i)}
的内容,以获取大致的运行时间。 - David