当有大量数据[超过一百万行] [重复]时,改进 R 中的循环以提高时间效率

Posted

技术标签:

【中文标题】当有大量数据[超过一百万行] [重复]时,改进 R 中的循环以提高时间效率【英文标题】:Improve loops in R for time efficiency when having a lot of data [over half a million rows] [duplicate] 【发布时间】:2019-07-02 11:04:37 【问题描述】:

我有一些相当简单的 R 代码需要 10 分钟到 20 分钟才能执行,我认为这会耗费不必要的时间。数据由大约 30 列和 500.000 行的数据框组成。循环的目的是查看某个值应该放在什么样的 bin 中。

我试图通过在循环之前添加整个列来改进代码,在阅读了有关该主题的其他一些线程后在循环外进行一些计算,但这些方法都没有显着改进代码。

col_days <- Sys.Date() - as.Date(df$col)
i=1
while (i < length(df$col))
  if (Sys.Date() - as.Date(df$col[i]) <366)
    df$col_bin[i] <- "Less than 1 year"
    i=i+1
  

  else if (between(Sys.Date() - as.Date(df$col[i]), 366, 1095))
    df$col_bin[i] <- "1 year to 3 years"
    i=i+1
  
  else if (between(Sys.Date() - as.Date(df$col[i]), 1096, 1825))
    df$col_bin[i] <- "3 years to 5 years"
    i=i+1
  
  else if (between(Sys.Date() - as.Date(df$col[i]), 1826, 3650))
    df$col_bin[i] <- "5 years to 10 years"
    i=i+1
  
  else
    df$col_bin[i] <- "More than 10 years"  
    i=i+1
  

因此,使用此版本的代码,计算所有行大约需要 15 分钟。我相信有几种方法可以改善这一点。有什么建议吗?

【问题讨论】:

你能分享一个你的数据的小例子吗?和预期的输出?从它的外观来看,函数cut()可能与这个问题有关。 是的:将所有内容替换为:df$col_bin &lt;- cut(df$col, c(0, 366, 1069, 1826, 3651, Inf), labels = c("&lt;1", "1-3", "3-5", "5-10", &gt;10"))(使用您想要的任何标签)。没有循环。 (打错了:你可能想要cut(Sys.Date() - as.Date(df$col), ...)。) 你为什么要为此使用循环?在我看来,这些看起来很容易通过简单的函数来完成,无论是 base r 还是 tidyverse。我什至认为您不一定需要使用 *apply。此外,您绝对应该查看 case_when。另外,为什么不只计算差异并进行除法,舍入,然后将其作为您想要的级别的有序因子?还有闰年呢?为什么不只使用日期数学? 感谢所有反馈! r2evans 提供的示例解决方案非常有效。现在只需一秒钟而不是 15 分钟。真的,真的很有帮助。谢谢大家,我意识到我过于关注使用循环而不是这种想法。太棒了! 【参考方案1】:

这是使用dplyr::case_when() 的解决方案(我发现它比base::cut() 更容易处理):

library(dplyr)
df %>% 
  mutate(
    col_bin = case_when(
      days < 366 ~ "Less than 1 year",
      days < 1095 ~ "1 year to 3 years",
      days < 1825 ~ "3 years to 5 years",
      days < 3650 ~ "5 years to 10 years",
      TRUE ~ "More than 10 years"
    )
  )

          col      days             col_bin
1  2012-02-27 2538 days 5 years to 10 years
2  2014-11-27 1534 days  3 years to 5 years
3  2013-04-06 2134 days 5 years to 10 years
4  2009-08-15 3464 days 5 years to 10 years
5  2017-12-09  426 days   1 year to 3 years
6  2016-01-08 1127 days  3 years to 5 years
7  2015-05-08 1372 days  3 years to 5 years
8  2015-05-20 1360 days  3 years to 5 years
9  2010-09-08 3075 days 5 years to 10 years
10 2013-03-26 2145 days 5 years to 10 years
11 2010-03-15 3252 days 5 years to 10 years
12 2011-05-08 2833 days 5 years to 10 years
13 2017-07-21  567 days   1 year to 3 years

示例数据:

set.seed(10)
df <- data.frame(
  col = Sys.Date() - sample(1:5000, size = 13)
)
df[["days"]] <- Sys.Date() - as.Date(df[["col"]])

【讨论】:

这是一个很好的例子,说明如何以更简单的方式处理问题,谢谢!我将查看这个示例和涉及 cut 的示例,并尝试找出最适合我的示例。再次感谢您的精彩反馈!【参考方案2】:

下面是使用eiter dplyrdata.table 以及case_whencut 的四种解决方案的比较。

感谢 snoram 提供示例数据以及 dplyrcase_when 部分。

在此测试中,dplyrdata.table 的性能大致相同,但 cutcase_when 快。与您的原始解决方案相比,所有解决方案都应该更快,可能对于您的数据集大小的数据集绝对足够快。

require(data.table)
require(dplyr)
require(microbenchmark)
require(ggplot2)

set.seed(10)
df <- data.frame(
  col = Sys.Date() - sample(1:5000, size = 13)
)
df[["days"]] <- Sys.Date() - as.Date(df[["col"]])


benchmark <- microbenchmark(
  data.table=
    dt <- data.table(df)
    dt[, col_bin := cut(
      as.numeric(days, units="days"), 
      breaks=c(-Inf, 366, 1095, 1825, 3650, Inf), 
      labels=c(
        "Less than 1 year",
        "1 year to 3 years",
        "3 years to 5 years",
        "5 years to 10 years",
        "More than 10 years"
      ))]
  ,
  dplyr=
    res <- df %>% 
      mutate(
        col_bin = case_when(
          days < 366 ~ "Less than 1 year",
          days < 1095 ~ "1 year to 3 years",
          days < 1825 ~ "3 years to 5 years",
          days < 3650 ~ "5 years to 10 years",
          TRUE ~ "More than 10 years"
        )
      )
  ,
  `data.table & case_when`=
    dt <- data.table(df)
    dt[, col_bin := case_when(
          days < 366 ~ "Less than 1 year",
          days < 1095 ~ "1 year to 3 years",
          days < 1825 ~ "3 years to 5 years",
          days < 3650 ~ "5 years to 10 years",
          TRUE ~ "More than 10 years"
        )]
  ,
  `dplyr & cut`=
    res <- df %>% 
      mutate(
        col_bin = cut(
      as.numeric(days, units="days"), 
      breaks=c(-Inf, 366, 1095, 1825, 3650, Inf), 
      labels=c(
        "Less than 1 year",
        "1 year to 3 years",
        "3 years to 5 years",
        "5 years to 10 years",
        "More than 10 years"
      ))
      )
  

  )

autoplot(benchmark)

【讨论】:

如果您将data.tablecase_when 一起使用,看看第三个选项的基准测试会很有趣。还有更大的数据。 顺便说一句。 data.table 起初很难阅读,但几周后我认为它比 dplyr 更易读,因为它简洁。 你可以用setDT(df)代替dt &lt;- data.table(df) 嗯,好的,我会完全编辑这个答案,似乎 dplyr 不是这里的慢东西,但在这种情况下,更新版本即将到来。 我使用了dt &lt;- data.table(df),因为我想在每次评估中都转换为data.table。据我记得setDT 就地工作,因此不会测量转换的开销。我认为根据上下文,两者都可能是正确的方法。如果您为整个分析选择一个数据结构并且只在开始时转换而不测量开销将是正确的方法。

以上是关于当有大量数据[超过一百万行] [重复]时,改进 R 中的循环以提高时间效率的主要内容,如果未能解决你的问题,请参考以下文章

如何使用具有超过 2^31 个观测值的 biglm

写了一百万行代码是什么体验?

熊猫-遍历一百万个单元格

如何从一个表中检索一百万行并将其插入到另一个表中? [复制]

数百万行的数据库设计

java题:一百万个乱序数字排序,中间有重复的,但由于内存不够,不能一下存储100万个数,怎样才排序?