在日期范围内有效地加入 R 中的两个 data.tables(或 SQL 表)?

Efficiently joining two data.tables in R (or SQL tables) on date ranges?

我正在使用入院数据,并试图将 table 的观察结果联系起来:

dt_taken patient_id observation value
2020-04-13 00:00:00 patient01 "Heart rate" 69
...

... 录取人数 table:

patient_id admission_id startdate enddate
patient01 admission01 2020-04-01 00:04:20 2020-05-01 00:23:59
...

... 这样它 returns 与入院相关的观察列表,并拒绝所有在入院期间未进行的观察(错误记录,或在门诊就诊等):

dt_taken admission_id observation value
2020-04-13 00:00:00 admission01 "Heart rate" 69
...

我迄今为止相对简单的方法是遍历每个患者,然后是每个患者的入院,然后是每个观察并将其分配给那个入院,但考虑到我有超过 36k 的入院和超过 100 万的观察,这是令人难以置信的时间-消费(我的政府发行的笔记本电脑因此讨厌我)。

有没有我遗漏的更有效的方法,要么使用 {data.table}(我必须承认我在这里绝对是个新手,更喜欢在 {tidyverse} 中工作)或者我什至可以运行 在存储 table 的 SQL 服务器上以拯救我老化的笔记本电脑?

data.table

对于 data.table,这主要是对 How to perform join over date ranges using data.table? 的欺骗,尽管它不提供 RHS[LHS, on=.(..)] 方法。

observations
#              dt_taken patient_id observation value
# 1 2020-04-13 00:00:00  patient01  Heart rate    69
admissions
#   patient_id admission_id           startdate             enddate
# 1  patient01  admission01 2020-04-01 00:04:20 2020-05-01 00:23:59

### convert to data.table
setDT(observations)
setDT(admissions)

### we need proper 'POSIXt' objects
observations[, dt_taken := as.POSIXct(dt_taken)]
admissions[, (dates) := lapply(.SD, as.POSIXct), .SDcols = dates]

然后加入。

admissions[observations, on = .(patient_id, startdate <= dt_taken, enddate >= dt_taken)]
#    patient_id admission_id  startdate    enddate observation value
#        <char>       <char>     <POSc>     <POSc>      <char> <int>
# 1:  patient01  admission01 2020-04-13 2020-04-13  Heart rate    69

我认为有两件事值得注意:

  • 在SQL中(在其他加入友好的语言中也类似),通常显示为

    select ...
    from TABLE1 left join TABLE2 ...
    

    建议 TABLE1 是 LHS(左侧),TABLE2 是 RHS table。 (这是一个粗略的概括,主要针对左连接,因为这是 data.table::[ 支持的所有内容;对于 inner/outer/full 连接,您将需要 merge(.) 或其他外部机制。参见 How to join (merge) data frames (inner, outer, left, right) and 以获得关于 JOIN 等的更多讨论)

    由此可见,data.table::[的机制是有效的

    TABLE2[TABLE1, on = .(...)]
    RHS[LHS, on = .(...)]
    

    (意思是右边table实际上是从左到右第一个table...)

  1. 不等值连接输出中的名称是从 RHS 中保留下来的,看到没有找到 dt_taken但是,那些startdateenddate列的来自dt_taken.

    正因为如此,我经常找到最简单的方法让我全神贯注于重命名和值,当我不确定时,我将一个连接列复制到一个新列中并使用该列,然后将其删除 post-merge。它草率而懒惰,但我发现自己错过了太多次,并认为这不是我想的那样。

sqldf

如果 SQL 看起来更直观,这可能会更直接一些。

sqldf::sqldf(
  "select ob.*, ad.admission_id
   from observations ob
     left join admissions ad on ob.patient_id=ad.patient_id
         and ob.dt_taken between ad.startdate and ad.enddate")
#     dt_taken patient_id observation value admission_id
# 1 2020-04-13  patient01  Heart rate    69  admission01

数据(已经 data.tablePOSIXt,与 sqldf 一样好用,尽管常规 data.frame 也可以正常工作):

admissions <- setDT(structure(list(patient_id = "patient01", admission_id = "admission01", startdate = structure(1585713860, class = c("POSIXct", "POSIXt" ), tzone = ""), enddate = structure(1588307039, class = c("POSIXct", "POSIXt"), tzone = "")), class = c("data.table", "data.frame"), row.names = c(NA, -1L)))
observations <- setDT(structure(list(dt_taken = structure(1586750400, class = c("POSIXct", "POSIXt"), tzone = ""), patient_id = "patient01", observation = "Heart rate", value = 69L), class = c("data.table", "data.frame"), row.names = c(NA, -1L)))

(我用setDT来修复我们这里不能传.internal.selfref属性的问题。)