【问题标题】:Odd behaviour of data.table's update on non-equi self-joindata.table 对 non-equi 自连接的更新的奇怪行为
【发布时间】:2018-01-29 06:37:21
【问题描述】:

在为问题dplyr or data.table to calculate time series aggregations in R 准备answer 时,我注意到我确实得到了不同的结果,具体取决于表是更新到位还是作为新对象返回。此外,当我更改 non-equi join 条件中的列顺序时,我确实得到了不同的结果。

目前,我对此没有任何解释,可能是由于我方面的重大误解或简单的编码错误。

请注意,这个问题特别要求 对观察到的data.table 连接行为的解释。如果你 有潜在问题的替代解决方案,请感觉 免费向original question发布答案。

原始问题和工作答案

最初的问题是如何使用这些数据计算每位患者在住院前 365 天内的住院次数(包括实际住院次数):

library(data.table)   # version 1.10.4 (CRAN) or 1.10.5 (devel built 2017-08-19)
DT0 <- data.table(
  patient.id = c(1L, 2L, 1L, 1L, 2L, 2L, 2L),
  hospitalization.date = as.Date(c("2013/10/15", "2014/10/15", "2015/7/16", "2016/1/7", 
                                   "2015/12/20", "2015/12/25", "2016/2/10")))
setorder(DT0, patient.id, hospitalization.date)
DT0
   patient.id hospitalization.date
1:          1           2013-10-15
2:          1           2015-07-16
3:          1           2016-01-07
4:          2           2014-10-15
5:          2           2015-12-20
6:          2           2015-12-25
7:          2           2016-02-10

下面的代码给出了预期的答案(为清楚起见,此处添加了额外的帮助列)

# add helper columns
DT0[, start.date := hospitalization.date - 365][
  , end.date := hospitalization.date][]
DT0
   patient.id hospitalization.date start.date   end.date
1:          1           2013-10-15 2012-10-15 2013-10-15
2:          1           2015-07-16 2014-07-16 2015-07-16
3:          1           2016-01-07 2015-01-07 2016-01-07
4:          2           2014-10-15 2013-10-15 2014-10-15
5:          2           2015-12-20 2014-12-20 2015-12-20
6:          2           2015-12-25 2014-12-25 2015-12-25
7:          2           2016-02-10 2015-02-10 2016-02-10
result <- DT0[DT0, on = c("patient.id", "hospitalization.date>=start.date", 
              "hospitalization.date<=end.date"), 
   .(hospitalizations.last.year = .N), by = .EACHI][]
result
   patient.id hospitalization.date hospitalization.date hospitalizations.last.year
1:          1           2012-10-15           2013-10-15                          1
2:          1           2014-07-16           2015-07-16                          1
3:          1           2015-01-07           2016-01-07                          2
4:          2           2013-10-15           2014-10-15                          1
5:          2           2014-12-20           2015-12-20                          1
6:          2           2014-12-25           2015-12-25                          2
7:          2           2015-02-10           2016-02-10                          3

除了重命名和重复的列名(保留原样用于比较)。

对于patient.id == 2,最后一行的结果为 3,因为该患者自 2015-02-10 以来第三次于 2016-02-10 住院。

就地加入更新

result 是一个新的data.table 对象,它占用了额外的内存。我尝试使用以下方法更新原始 data.table 对象:

# use copy of DT0 which can be safely modified
DT <- copy(DT0)

DT[DT, on = c("patient.id", "hospitalization.date>=start.date", 
            "hospitalization.date<=end.date"), 
   hospitalizations.last.year := .N, by = .EACHI]
DT
   patient.id hospitalization.date start.date   end.date hospitalizations.last.year
1:          1           2013-10-15 2012-10-15 2013-10-15                          1
2:          1           2015-07-16 2014-07-16 2015-07-16                          2
3:          1           2016-01-07 2015-01-07 2016-01-07                          2
4:          2           2014-10-15 2013-10-15 2014-10-15                          1
5:          2           2015-12-20 2014-12-20 2015-12-20                          3
6:          2           2015-12-25 2014-12-25 2015-12-25                          3
7:          2           2016-02-10 2015-02-10 2016-02-10                          3

DT 现已更新就地,但第 5 行和第 6 行现在分别显示 3 次住院,而不是 1 或 2 次。现在似乎为每一行返回了上一时期内的住院总数。

更改条件中列的顺序。

非等值连接条件中的列顺序也很重要,即使在自连接中也是如此:

result <- DT0[DT0, on = c("patient.id", "start.date<=hospitalization.date", 
                          "end.date>=hospitalization.date"), 
              .(hospitalizations.last.year = .N), by = .EACHI][]
result

我的期望是 "start.date&lt;=hospitalization.date" 将等同于 "hospitalization.date&gt;=start.date"(请注意,&lt;&gt; 也已切换)但结果

   patient.id start.date   end.date hospitalizations.last.year
1:          1 2013-10-15 2013-10-15                          1
2:          1 2015-07-16 2015-07-16                          2
3:          1 2016-01-07 2016-01-07                          1
4:          2 2014-10-15 2014-10-15                          1
5:          2 2015-12-20 2015-12-20                          3
6:          2 2015-12-25 2015-12-25                          2
7:          2 2016-02-10 2016-02-10                          1

不同。看来现在正在统计即将住院的人数

有趣的是,更新就地现在确实返回了相同的结果(除了一些列名):

# use copy of DT0 which can be safely modified
DT <- copy(DT0)
DT[DT, on = c("patient.id", "start.date<=hospitalization.date", 
              "end.date>=hospitalization.date"), 
   hospitalizations.last.year := .N, by = .EACHI]
DT
   patient.id hospitalization.date start.date   end.date hospitalizations.last.year
1:          1           2013-10-15 2012-10-15 2013-10-15                          1
2:          1           2015-07-16 2014-07-16 2015-07-16                          2
3:          1           2016-01-07 2015-01-07 2016-01-07                          1
4:          2           2014-10-15 2013-10-15 2014-10-15                          1
5:          2           2015-12-20 2014-12-20 2015-12-20                          3
6:          2           2015-12-25 2014-12-25 2015-12-25                          2
7:          2           2016-02-10 2015-02-10 2016-02-10                          1

相关

有一个潜在的related question 导致issue reported on github

有一个answer by Arunx. 前缀的使用有关非等连接

【问题讨论】:

    标签: r join data.table self-join


    【解决方案1】:

    by=.EACHI 分组表示“按每个 i”而不是“按每个 x”。

    # for readability / my sanity
    DT = copy(DT0)
    setnames(DT, "hospitalization.date", "h.date")
    
    z = DT[DT, on = .(patient.id, h.date >= start.date, h.date <= end.date), 
       .(x.h.date, patient.id, i.start.date, i.end.date, g = .GRP, .N)
    , by=.EACHI][, utils:::tail.default(.SD, 6)]
    
          x.h.date patient.id i.start.date i.end.date g N
     1: 2013-10-15          1   2012-10-15 2013-10-15 1 1 * 
     2: 2015-07-16          1   2014-07-16 2015-07-16 2 1 
     3: 2015-07-16          1   2015-01-07 2016-01-07 3 2 *
     4: 2016-01-07          1   2015-01-07 2016-01-07 3 2 *
     5: 2014-10-15          2   2013-10-15 2014-10-15 4 1 *  
     6: 2015-12-20          2   2014-12-20 2015-12-20 5 1
     7: 2015-12-20          2   2014-12-25 2015-12-25 6 2  
     8: 2015-12-25          2   2014-12-25 2015-12-25 6 2 
     9: 2015-12-20          2   2015-02-10 2016-02-10 7 3 *
    10: 2015-12-25          2   2015-02-10 2016-02-10 7 3 *
    11: 2016-02-10          2   2015-02-10 2016-02-10 7 3 *
    

    对于患者 1,组是

    • .(start.date = 2012-10-15, end.date = 2013-10-15),计数为 1
    • .(start.date = 2014-07-16, end.date = 2015-07-16),计数 1
    • .(start.date = 2015-01-07, end.date = 2016-01-07),计数 2

    幸运的是,此连接中有七个组,原始表中有七行。

    对于更棘手的问题,我将从我的笔记中借用一个例子:

    注意更新连接中的多个匹配项。当有多个匹配项时,更新连接显然只会使用最后一个。不幸的是,这是默默地完成的。试试:

    a = data.table(id = c(1L, 1L, 2L, 3L, NA_integer_), 
      t = c(1L, 2L, 1L, 2L, NA_integer_), x = 11:15)
    b = data.table(id = 1:2, y = c(11L, 15L))
    b[a, on=.(id), x := i.x, verbose = TRUE ][]
    
    # Calculated ad hoc index in 0 secs
    # Starting bmerge ...done in 0.02 secs
    # Detected that j uses these columns: x,i.x 
    # Assigning to 3 row subset of 2 rows
    #    id  y  x
    # 1:  1 11 12
    # 2:  2 15 13
    

    启用详细信息后,我们会看到一条关于分配“to 3 row subset of 2 rows”的有用消息。

    --修改自"Quick R Tutorial", section "Updating in a join"

    不幸的是,在 OP 的情况下,verbose=TRUE没有提供这样的信息。

    DT[DT, on = .(patient.id, h.date >= start.date, h.date <= end.date), 
       n := .N, by = .EACHI, verbose=TRUE]
    # Non-equi join operators detected ... 
    #   forder took ... 0.01 secs
    #   Generating group lengths ... done in 0 secs
    #   Generating non-equi group ids ... done in 0 secs
    #   Found 1 non-equi group(s) ...
    # Starting bmerge ...done in 0.02 secs
    # Detected that j uses these columns: <none> 
    # lapply optimization is on, j unchanged as '.N'
    # Making each group and running j (GForce FALSE) ... 
    #   memcpy contiguous groups took 0.000s for 7 groups
    #   eval(j) took 0.000s for 7 calls
    # 0.01 secs
    

    但是,我们可以看到每个 x 组的最后一行确实包含 OP 看到的值。我在上面用星号手动标记了这些。或者,您可以使用z[, mrk := replace(rep(0, .N), .N, 1), by=x.h.date] 标记它们。


    供参考,这里的更新加入是……

    DT[, n := 
      .SD[.SD, on = .(patient.id, h.date >= start.date, h.date <= end.date), .N, by=.EACHI]$N 
    ]
    
       patient.id hospitalization.date start.date   end.date     h.date n
    1:          1           2013-10-15 2012-10-15 2013-10-15 2013-10-15 1
    2:          1           2015-07-16 2014-07-16 2015-07-16 2015-07-16 1
    3:          1           2016-01-07 2015-01-07 2016-01-07 2016-01-07 2
    4:          2           2014-10-15 2013-10-15 2014-10-15 2014-10-15 1
    5:          2           2015-12-20 2014-12-20 2015-12-20 2015-12-20 1
    6:          2           2015-12-25 2014-12-25 2015-12-25 2015-12-25 2
    7:          2           2016-02-10 2015-02-10 2016-02-10 2016-02-10 3
    

    这是处理这种情况的正确/惯用方法,基于在另一个表中查找 x 的每一行并计算结果摘要,将列添加到 x

    x[, v := DT2[.SD, on=, j, by=.EACHI]$V1 ]
    

    【讨论】:

    • 发现了一个bug:如果我在j中在x.h.date之前写.GRP,data.table can't find x.h.date...会报错。
    • 我不会回答另一个问题,因为 OP 的输入和输出不一致。不过,请随意使用这篇文章在那里回答。顺便说一句,我提出的问题在这里:github.com/Rdatatable/data.table/issues/2313
    • 非常感谢您详细而全面的解释,我不确定即使我会咨询您的Quick R tutorial,我是否会自己找到解释 - 我应该做更多经常。
    猜你喜欢
    • 2021-05-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多