【问题标题】:How to optimize iterating over a huge dataframe with non-unique rows如何优化对具有非唯一行的巨大数据帧的迭代
【发布时间】:2018-05-01 05:40:13
【问题描述】:

我知道,如果 R 没有在 for 循环的范围内更新变量,那么我只是编写了一些极其缓慢和昂贵的代码。不幸的是,由于有一系列非常紧迫的截止日期和强大的 C++/Java 背景,在我戴上 R 帽子之前,这是我的首选行为。

我有一个需要改进的功能。它需要一个数据框(如下所示)返回唯一的 patid 值,并使用这些值来检索该数据框的子集以进行日期修改。下面是一个经过修剪的示例(注意,我刚刚将其从已完成的运行中拉出,因此日期已被修改)。我执行的最后一次 R 运行是超过 2700 万行的数据帧,大约需要四/五个小时。数据框的大小会大很多。

patid eventdate
1     12/03/1998   
1     12/03/1998   
2     04/03/2007   
3     15/11/1980   
3     15/11/1980   
3     01/02/1981  

函数的修剪示例:

rearrangeDates <- function(dataFrame) {
   #return a list of the unique patient ids
   uniquePatids <- getUniquePatidList(dataFrame) #this is only called once and is very fast

    out=NULL
    for(i in 1:length(uniquePatids)) { # iterate over the list 
        idf <- subset(dataFrame, dataFrame$patid=uniquePatids[[i]])
        idf$eventdate <- as.POSIXct(idf$eventdate,format="%d/%m/%Y")
        idf <- idf[order(idf$eventdate,decreasing=FALSE),]
        out = rbind(out,idf)
    }
    return(out)
}

谁能提出改进建议?

【问题讨论】:

  • 您是否只是想将日期转换为POSIX.ct,然后按 patid 和升序排列?
  • 试试library(dplyr); df %&gt;% mutate(eventdate = as.Date(eventdate, format="%d/%m/%Y")) %&gt;% arrange(patid, eventdate)
  • @Prem 哇!这需要花费数小时并将其减少到几秒钟的东西!谢谢。
  • 您是否在每次循环迭代中继续将输入日期作为字符串读取,并将它们转换为as.Date()?!只需在循环外执行一次dataFrame$eventdate &lt;- as.POSIXct(dataFrame$eventdate,format="%d/%m/%Y")
  • 而迭代追加 out = rbind(out,idf) 是一个臭名昭著的反模式。您已经知道输出 df 的长度为length(uniquePatids),您可以在循环外预先声明它。在循环内部,不做追加,直接赋值给 outputDf 的行。

标签: r for-loop optimization dplyr data.table


【解决方案1】:

由于您想对patideventdate 上的数据进行排序,这应该可以。

library(dplyr)

df %>% 
  mutate(eventdate = as.Date(eventdate, format="%d/%m/%Y")) %>% 
  arrange(patid, eventdate)

输出为:

  patid  eventdate
1     1 1998-03-12
2     1 1998-03-12
3     2 2007-03-04
4     3 1980-11-15
5     3 1980-11-15
6     3 1981-02-01

样本数据:

df <- structure(list(patid = c(1L, 1L, 2L, 3L, 3L, 3L), eventdate = c("12/03/1998", 
"12/03/1998", "04/03/2007", "15/11/1980", "15/11/1980", "01/02/1981"
)), class = "data.frame", row.names = c(NA, -6L))

【讨论】:

    【解决方案2】:

    这非常适合data.table:您的数据有一个明确定义的键,您可以按(patid,eventdate) 分组,您知道输出 df 的大小将 unique 函数。所以请尝试下面的(无循环!)代码,让我们知道它与您的原始代码和dplyr 方法的比较:

    require(data.table)
    dt = data.table(patid=c(1,1,2,3,3,3), eventdate=c('12/03/1998','12/03/1998',
             '04/03/2007', '15/11/1980', '15/11/1980','01/02/1981'))  
    dt[, eventdate := as.POSIXct(eventdate,format="%d/%m/%Y") ]
    
    # If you set a key, the `by` operation will be super-fast
    setkeyv(dt, c('patid','eventdate'))
    
    odt <- dt[, by=.(patid,eventdate)]
    
       patid  eventdate
    1:     1 1998-03-12
    2:     1 1998-03-12
    3:     2 2007-03-04
    4:     3 1980-11-15
    5:     3 1980-11-15
    6:     3 1981-02-01
    

    (最后一件事:不要害怕 POSIXct/lt,尽早转换为它们,它们比字符串更有效,它们支持比较运算符,因此列可以用作键,排序,比较。)

    (对于最快的dplyr 实现,请使用dplyr::distinct()

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-07-09
      • 2019-11-28
      • 2021-02-16
      • 2015-01-16
      • 2021-01-13
      • 2021-12-26
      • 1970-01-01
      • 2018-06-22
      相关资源
      最近更新 更多