【问题标题】:What is the most efficient way to put together a data.frame in R with data generated by a while loop?将 R 中的 data.frame 与 while 循环生成的数据放在一起的最有效方法是什么?
【发布时间】: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 后跟rbindrbind-ing 行顺序要好,最后一个方法要快几个数量级。

但是,对于我的实际情况,有两个问题。

  1. 我的代码中有一个while 循环,其中i 以复杂的方式进行操作,即我不知道i 将采用哪个或多少值。因此我不能使用sapply
  2. 最后一种方法不起作用,因为我需要输出的数据不仅包含单个数值,还包含值列表(在前 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 更快,但是逗号分隔字符列转回向量列的位不起作用。它抱怨替换的大小不同。
我怀疑原因是rbindsapply 输出转换为矩阵。我想这是之前讨论过的。我看看能不能解决。

编辑 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)

out5out1 的身份失败,如上所述:

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 &lt;- matrix(0, 6, 10000); for(i in 1:10000) {m[,i] &lt;- c(i, i*i, (i-3):i)}
  • 不,我不能这样做,因为最终的结构不是我需要的。我需要一个data.frameout5 作为输出。此外,在这个简单的示例中,list_i 的所有元素都具有相同数量的整数,但在我的实际应用中它们没有,所以matrix 会抱怨。但我现在发现out3 可以适应我的需要。我现在就贴出来。

标签: r dataframe optimization


【解决方案1】:

如果您知道数据中将有 1000 行,请使用该长度初始化一个列表,这将显着提高性能。

使用你的方法我得到的时间是:

cat("\ntime_1 =", time_1)

time_1 = 8.42 1.158 9.628 0 0

初始化我得到的列表的长度:

i <- 1
result <- vector('list', 1000L)
time_1 <- system.time({
  
  while (i < 10000) {
    i <- i + 1
    result[[i]] <- data.frame("i" = i, 
                              "i_squared" = i*i, 
                              "list_i" = I(list((i-3):i)), stringsAsFactors = F)
  }
  out1 <- do.call(rbind, result)
})

cat("\ntime_1 =", time_1)

time_1 = 2.243 0.011 2.262 0 0

【讨论】:

  • 谢谢;即使最初的元素数量大于我最终得到的数量,它会起作用吗?意思是,空元素会被忽略吗?顺便说一句,一定有什么不正确的;当我运行此代码时,我得到一个空的 out1。
  • 是的,这甚至适用于大量元素。先运行i &lt;- 1,再运行上面的,得到out1的结果。
  • 好的,谢谢!在我的系统中,这与 sapply 版本所用的时间大致相同(示例 2)。如果它不能进一步改进,好的,那我就用这个。遗憾的是最后一种方法无法适应,因为那样会非常快。也许我可以使用粘贴将列表转换为逗号分隔的字符,然后在输出上应用 strsplit。但不确定它是否有效。
  • 其实可行:如果我使用方法 3 结合您的建议预定义一个空列表,它的速度和 sapply 一样快。是的,然后我有一个字符矩阵,我需要将其重新格式化为 data.frame 并拆分,但是,嘿,这是值得的。再一次非常感谢你!我会将解决方案作为编辑发布在我自己的帖子中,以供将来参考。
  • @user6376297 很高兴得到帮助!随时点击左侧的复选标记accept the answer。每个帖子只能接受一个答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-01-12
  • 2012-03-14
  • 2020-07-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多