使用 data.table 加速 j 中的顺序任务的 for 循环

Posted

技术标签:

【中文标题】使用 data.table 加速 j 中的顺序任务的 for 循环【英文标题】:Speed up for-loop with sequential tasks in j using data.table 【发布时间】:2022-01-11 15:18:51 【问题描述】:

我面临一个大型数据集的挑战,有几百万行和几百列。我正在使用 data.table 格式。

我的(长)代码运行良好,除了在数据集中的所有日期为特定个人开处方的部分代码。

我想为每个日期为每个类别的药物创建一个单行“内存”,以供稍后在代码中使用,并且使用 for 循环、引用分配和 toString 命令来实现 - 但这是非常非常慢。

我已经看了好几个小时,并试图为这个问题做一个提炼的例子——欢迎提出任何建议。

我怀疑将多行按组组合成一个更快的方法,即更快的 toString,会解决问题,但我想不出更聪明的方法来做到这一点。欢迎提出任何建议。

这里是代码(数据集故意很大以便在几秒钟内重现速度问题),给我带来问题的循环是代码的最后一段:

library(data.table)
##This is one long piece of code generating the dataset - apologies for the complexity, did what I could (within my abilities) to simplify:
set.seed(2532)
healthData <- data.table(id = sample(1:10000 , 10000))
healthData <- healthData[ , list(id = id ,
                   date = seq(as.Date("2000-01-01") ,
                              as.Date("2001-01-01") ,
                              by = "day")) ,
                   by = 1:nrow(healthData)]
healthData[ , nrow := NULL]
prescriptionRegistry <- data.table(id = sample(1:10000 , 1000 , replace = TRUE) ,
                                   category = sample(c("paracetamol" , "oxycodon" , "seroquel") , 1000 , replace = TRUE) ,
                                   dose = sample(c(0.5 , 1 , 2) , 1000 , replace = TRUE) ,
                                   endDate = sample(as.Date(as.Date("2000-02-01"):as.Date("2000-12-31") ,
                                                            "1970-01-01") ,
                                                    1000 ,
                                                    replace = TRUE))
prescriptionRegistry <- prescriptionRegistry[ , list(id = id ,
                                                     category = category ,
                                                     dose = dose ,
                                                     endDate = endDate , 
                                                     date = seq(as.Date("2000-01-01") ,
                                                                endDate , by = "day")) ,
                                by = 1:nrow(prescriptionRegistry)]
prescriptionRegistry[ , nrow := NULL]
prescriptionRegistry[category == "seroquel" , c("seroquelDose" , "seroquelEndDate") :=
                                                  list(dose , endDate)]
prescriptionRegistry[category == "paracetamol" , c("paracetamolDose" , "paracetamolEndDate") :=
                                                     list(dose , endDate)]
prescriptionRegistry[category == "oxycodon" , c("oxycodonDose" , "oxycodonEndDate") :=
                                                  list(dose , endDate)]
healthData <- merge(healthData , prescriptionRegistry , by.x = c("id" , "date") , by.y = c("id" , "date") , all.x = TRUE , allow.cartesian = TRUE)

##The purpose of this is to reduce to the data that gives me problems - that is when an individual has several prescriptions a day for the same drug:
setorder(healthData , id , date)
healthData[ , index := 1:.N , by = c("id" , "date")]
index <- healthData[index == 2 , .(id)]
index <- unique(index)
setkey(healthData , id)
setkey(index , id)
healthData <- healthData[index]
rm(index)
##End of code generating dataset

##This is the loop that is very slow on large datasets - suggestions are most welcome.
categories <- c("paracetamol" , "oxycodon" , "seroquel")
for (i in categories) 
    healthData[ ,
               c(paste0(i , "DoseTotal") ,
                 paste0(i , "DoseText") ,
                 paste0(i , "EndDateText")) := list(
                   sum(get(paste0(i , "Dose")) , na.rm = TRUE) ,
                   toString(get(paste0(i , "Dose"))) ,
                   toString(get(paste0(i , "EndDate")))) ,
               by = c("id" , "date")]

我真正的问题是在 Windows server 2012 R2 上带有 data.table 1.12.2 和 R 3.61 的服务器上,但在带有 Lubuntu 20.04、R 4.1.2 和数据的笔记本电脑上似乎也很慢。表 4.14.2。为了量化,服务器上循环的每次迭代需要 2-3 个小时,使用 30 个处理器线程并访问 1 TB RAM。

感谢您的宝贵时间!

【问题讨论】:

我认为您可以使用长格式工作并计算总和以及您想要的任何内容,然后将其设为宽格式以获取您的 categoryDose、categoryDoseSum 等。melt 和 dcast 在这里是您的朋友。跨度> 亲爱的@MerijnvanTilborg 谢谢你的建议!将为此尝试某种 dcast,我同意这可能是可行的 - 如果我成功,就会回来。 你能把它归结为一个更短的例子吗? @sindri_baldur 道歉 - 我已经尝试过,但也许我没有足够的创造力或经验。这是我能想出的最短的例子来重现我的问题。 【参考方案1】:

如果您正在寻找更快的toString,您可以使用列表列。在我的电脑上,您的示例从 2.3 秒变为 0.6 秒。

for (i in categories) 
  healthData[ ,
              c(paste0(i , "DoseTotal") ,
                paste0(i , "DoseText") ,
                paste0(i , "EndDateText")) := list(
                  sum(get(paste0(i , "Dose")) , na.rm = TRUE) ,
                  list(get(paste0(i , "Dose"))) ,
                  list(get(paste0(i , "EndDate")))) ,
              by = c("id" , "date")]

【讨论】:

非常感谢 - 这正是我想要的!

以上是关于使用 data.table 加速 j 中的顺序任务的 for 循环的主要内容,如果未能解决你的问题,请参考以下文章

在列表中有效地重复data.table,从循环中的另一个data.table顺序替换具有相同名称的列

如果名称按组的顺序不同,R data.table 分组操作返回错误值?

单击JQuery Data Table示例中的行

如何填充(自动填充)值,例如使用 R 中的 data.table 将 NA 替换为组中的第一个值?

在 R data.table 中创建虚拟变量

有没有办法在 i 中自我引用 data.table