【问题标题】:Fastest way to row bind dataframe within for loop in R?在 R 中的 for 循环中行绑定数据帧的最快方法?
【发布时间】:2021-07-08 15:41:38
【问题描述】:

我正在尝试找到在 R 中使用 for 循环(或 purrrr 中的映射)生成表格的最快和最有效的方法。

我有 15,881 个值要循环,在本例中假设这些值是数字 1 到 15,881 加 1,即这个变量:

values <- c(1:15881)

然后我尝试过滤列与值匹配的现有数据帧,然后执行一些数据清理过程 - 此单个数据帧的输出,为清楚起见,我的过程如下:

假设在这种情况下,我从值对象中选择了一个值,例如value = values[1]

那么对于单个值,我有以下内容:

  df <- df_to_filter %>%
    filter(code == value) %>%
    group_by(code, country) %>%
    group_split() %>%
    purrr::map_dfr(some_other_function) %>%
    filter(!is.na(country))
  

当我为单个值运行上面的代码时,它工作得非常好。输出是所需的数据帧。对于单个值,此过程大约需要 0.7 秒。

但是,我正在尝试将此输出的结果附加到变量 values

中找到的每个单个值的空数据框中

到目前为止,我已经尝试了以下方法:

For 循环方法

# empty dataframe  to append values to 
empty_df <- tibble()

for (value in values){

  df <- df_to_filter %>%
    filter(code == value) %>%
    group_by(code, country) %>%
    group_split() %>%
    purrr::map_dfr(some_other_function) %>%
    filter(!is.na(country))

 empty_df <- bind_rows(empty_df, df)

}

但是上面的速度非常慢 - 我做了一个快速计算,大约需要 186 分钟 ((0.7 seconds per table x 15,881)/60 - seconds in a minute = around 185.7 minutes) - 这对于处理一个数据帧来说是一个巨大的时间。

有没有比 for 循环更快的方法来加快上述过程?我想不出任何方法来改进上述代码的基础,因为它可以很好地完成工作,并且 0.7 秒生成一个表对我来说似乎很快,但 15,881 个表显然需要很长时间。

我尝试将purrr 包与data.table 一起使用,但我得到的最远的是:

combine_dfs <- function(value){

    df <- df_to_filter %>%
    filter(code == value) %>%
    group_by(code, country) %>%
    group_split() %>%
    purrr::map_dfr(some_other_function) %>%
    filter(!is.na(country))

   df <- data.table(df) 

   rbindlist(list(df, empty_df))

}

然后用map_df 运行是这样的:

map_df(values, ~combine_dfs(.))

但是,即使是上述方法也非常缓慢,而且似乎需要大约相同的时间!

感谢任何帮助!

【问题讨论】:

  • 如果您可以共享现有数据帧的可重现部分会更好。
  • 您确实需要首先提供一个可重现的示例。否则我们无法提供完整的解决方案,也无法与您想要的结果进行比较。
  • 你能在你的答案中添加一些数据吗?
  • for 循环中rbind 帧表的最快方法是... 不要。这打破了R Inferno 的第二个圆圈,标题为“Growing Objects”。意识到每次执行此操作时,在创建新对象之前,数据都会在内存中完美复制,而旧对象会被垃圾收集(最终)。这意味着如果您有 1M 行并且想要 rbind 再添加 10 行,那么您将在某个时间点在内存中拥有 2M+ 行。 不要这样做。

标签: r dataframe dplyr data.table tidyverse


【解决方案1】:

无论您使用哪个库,循环中的行绑定数据框都是低效的。

您没有提供任何示例数据,但我认为对于您的情况,这应该是一样的。

library(dplyr)

df_to_filter %>%
  group_split(code, country) %>%
  purrr::map_dfr(some_other_function) %>%
  filter(!is.na(country)) -> result

result

【讨论】:

  • 嗨,你能解释一下这有什么不同吗?我了解您不再使用 for 循环 - 但 -&gt; result 是否将其发送到新数据帧?此外,加速我看不到的代码有什么好处,因为它似乎是相同的代码?
  • 是的,-&gt; result 将其添加到名为 result 的新数据帧中。在您的代码中,empty_df &lt;- bind_rows(empty_df, df)rbindlist(list(df, empty_df)) 将完全减慢我的代码避免的过程。另外,正如大家已经说过的那样,如果您提供一个示例来比较我们的答案,那将非常有用。
【解决方案2】:

您确实需要首先提供一个可重现的示例。否则我们无法提供完整的解决方案,也无法比较结果。

library(data.table)
setDT(df_to_filter)[code %in% values, by = .(code, country)] %>%
group_split(code, country) %>%
purrr::map_dfr(some_other_function) %>%
  filter(!is.na(country))

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-07-06
    相关资源
    最近更新 更多