【发布时间】:2021-01-13 21:55:21
【问题描述】:
如果我运行下面的代码:
i <- 0
out1 <- data.frame()
time_1 <- system.time({
while (i < 10000) {
i <- i + 1
out1 <- rbind(out1, data.frame("i" = i, "i_squared" = i*i, "list_i" = I(list((i-3):i)), stringsAsFactors = F))
}
})
cat("\ntime_1 =", time_1)
time_2 <- system.time({
out2 <- sapply(1:10000, function(i) {
data.frame("i" = i, "i_squared" = i*i, "list_i" = I(list((i-3):i)), stringsAsFactors = F)
}, simplify = F)
out2 <- do.call(rbind,out2)
})
cat("\ntime_2 =", time_2)
time_3 <- system.time({
out3 <- sapply(1:10000, function(i) {
c("i" = i, "i_squared" = i*i, "list_i" = I(list((i-3):i)))
}, simplify = F)
out3 <- do.call(rbind,out3)
})
cat("\ntime_3 =", time_3)
我得到以下信息:
time_1 = 9.39 0.01 9.44 NA NA
time_2 = 3.42 0 3.43 NA NA
time_3 = 0.18 0 0.17 NA NA
所以sapply 后跟rbind 比rbind-ing 行顺序要好,最后一个方法要快几个数量级。
但是,对于我的实际情况,有两个问题。
- 我的代码中有一个
while循环,其中i以复杂的方式进行操作,即我不知道i将采用哪个或多少值。因此我不能使用sapply。 - 最后一种方法不起作用,因为我需要输出的数据不仅包含单个数值,还包含值列表(在前 2 个示例中,我是由
I(list())强制执行的。
任何人都可以建议我如何通过比示例 1 中更快的过程获得out1,并且以一种与预先不知道i 的要求兼容的方式吗?
编辑 - 基于 Ronak Shah 的建议并结合上面的示例 3
# Ronak Shah's suggestion
i <- 0
out4 <- vector('list', 10000L)
time_4 <- system.time({
while (i < 10000) {
i <- i + 1
out4[[i]] <- data.frame("i" = i,
"i_squared" = i*i,
"list_i" = I(list((i-3):i)), stringsAsFactors = F)
}
out4 <- do.call(rbind, out4)
})
cat("\ntime_4 =", time_4)
# Combination of 3 and 4
i <- 0
out5 <- vector('list', 10000L)
time_5 <- system.time({
while (i < 10000) {
i <- i + 1
out5[[i]] <- c("i" = i, "i_squared" = i*i, "list_i" = paste((i-3):i,collapse=","))
}
out5 <- do.call(rbind, out5)
out5 <- as.data.frame(out5, stringsAsFactors = F)
out5["i"] <- as.integer(out5$i)
out5["i_squared"] <- as.integer(out5$i_squared)
#out5["list_i"] <- do.call(rbind,sapply(out5$list_i,function(csl) I(list(as.integer(unlist(strsplit(csl,split=","))))),simplify = F))
})
cat("\ntime_5 =", time_5)
正如我在下面的评论中提到的,示例 5 更快,但是逗号分隔字符列转回向量列的位不起作用。它抱怨替换的大小不同。
我怀疑原因是rbind 将sapply 输出转换为矩阵。我想这是之前讨论过的。我看看能不能解决。
编辑 2
好的,我可能有一个解决方案;虽然结果不是identical(out1,out5):
i <- 0
out5 <- vector('list', 10000L)
time_5 <- system.time({
while (i < 10000) {
i <- i + 1
out5[[i]] <- c("i" = i, "i_squared" = i*i, "list_i" = paste((i-3):i,collapse=","))
}
out5 <- do.call(rbind, out5)
out5 <- as.data.frame(out5, stringsAsFactors = F)
out5["i"] <- as.integer(out5$i)
out5["i_squared"] <- as.integer(out5$i_squared)
out5$list_i <- unname(sapply(out5$list_i,function(csl) I(as.integer(unlist(strsplit(csl,split=",")))),simplify = F))
})
cat("\ntime_5 =", time_5)
out5 和 out1 的身份失败,如上所述:
sapply(out1,class)
# i i_squared list_i
#"numeric" "numeric" "AsIs"
sapply(out5,class)
# i i_squared list_i
#"integer" "integer" "list"
我不知道是什么导致了这种差异... :(
一个人应该通过字符矩阵来加快速度,这似乎很奇怪。
但是好的,看起来out5 包含我需要的内容,并且我实现了所需的运行时间大幅减少,所以我称之为“问题已解决”。
再次感谢 Ronak Shah 的帮助!
编辑 3 - 经过进一步的工作
事实上,看起来不需要paste然后strsplit:可以将c(integer, integer, I(list(integers)) )形式的项目添加到Ronak Shah建议的list。
所以这里有两种同样快速的解决方案,一种使用 sapply,当一个确定填满所有 10000 行时,另一个将项目附加到预制的 list。
注意:我将每次出现的list((i-3):i) 替换为ifelse(i/2 == floor(i/2), list((i-3):i), list((i-2):i) ),以模拟每行整数列表长度不同的真实情况。
time_6 <- system.time({
out6 <- sapply(1:10000, function(i) {
c("i" = i, "i_squared" = i*i, "list_i" = I(ifelse(i/2 == floor(i/2), list((i-3):i), list((i-2):i) )))
}, simplify = F)
out6 <- do.call(rbind,out6)
out6 <- as.data.frame(out6, stringsAsFactors = F)
out6["i"] <- unlist(out6$i)
out6["i_squared"] <- unlist(out6$i_squared)
})
cat("\ntime_6 =", time_6)
i <- 0
out7 <- vector('list', 10000L)
time_7 <- system.time({
while (i < 10000) {
i <- i + 1
out7[[i]] <- c("i" = i, "i_squared" = i*i, "list_i" = I(ifelse(i/2 == floor(i/2), list((i-3):i), list((i-2):i) )))
}
out7 <- do.call(rbind,out7)
out7 <- as.data.frame(out7, stringsAsFactors = F)
out7["i"] <- unlist(out7$i)
out7["i_squared"] <- unlist(out7$i_squared)
})
cat("\ntime_7 =", time_7)
运行时间如下:
time_1 = 6.65 0.08 7.16 NA NA
time_2 = 2.28 0.01 2.29 NA NA
time_3 = 0.18 0 0.17 NA NA
time_4 = 2.35 0.02 2.36 NA NA
time_5 = 0.3 0.02 0.32 NA NA
time_6 = 0.15 0.01 0.18 NA NA
time_7 = 0.17 0 0.18 NA NA
方法 7 中特别好的地方是数据不会来回转换,它们是整数开始并始终保持整数。应用as.data.frame 后,unlist 不应该是列表的列就足够了。另一列,作为一个列表,确实是一个列表,并且可以保持这样的状态。
我只是想知道是否/为什么没有更多关于此的文档。我很确定人们经常必须通过附加一些迭代过程生成的行来创建data.frame。每个人都必须每次都重新发明轮子吗?
【问题讨论】:
-
如果您知道尺寸和类型相等,请将其存储在矩阵中:
m <- matrix(0, 6, 10000); for(i in 1:10000) {m[,i] <- c(i, i*i, (i-3):i)} -
不,我不能这样做,因为最终的结构不是我需要的。我需要一个
data.frame像out5作为输出。此外,在这个简单的示例中,list_i的所有元素都具有相同数量的整数,但在我的实际应用中它们没有,所以matrix会抱怨。但我现在发现out3可以适应我的需要。我现在就贴出来。
标签: r dataframe optimization