data.table non-equi join

时间:2017-07-20 10:59:37

标签: r join data.table left-join match

这是对this问题的跟进,其中接受的答案显示了使用data.table进行匹配练习的示例,包括非等距条件。

背景

基本设置是我们DT1包含人员详细信息的样本,DT2是一种主数据库。目的是确定DT1中的每个人是否与DT2中的至少一个条目匹配。

首先,我们初始化一个表示与FALSE匹配的列,以便每当找到匹配项时,其值都可以更新为TRUE

DT1[, MATCHED := FALSE]

然后使用以下通用解决方案更新列:

DT1[, MATCHED := DT2[.SD, on=.(Criteria), .N, by=.EACHI ]$N > 0L ]

理论上,它看起来(并且应该工作)很好。子表达式DT2[.SD, on=.(Criteria), .N, by=.EACHI]生成一个子表,其中每一行都来自DT1,并计算N列,该列是DT2中找到的该行的匹配数。然后,只要N大于零,MATCHEDDT1的值就会更新为TRUE

它按照预期的reproducible example工作。但是我遇到了一些与真实数据一起使用它的意外行为,并且无法找到它的底部。我可能会遗漏某些东西或者它可能是一个错误。不幸的是,我无法提供一个可重复性最小的示例,因为数据很大,而且它只显示在大数据中。但我会尽力记录下来。

意外行为或错误

有助于注意到这一点的是,出于历史原因,需要在两个单独的数据库中搜索匹配,因此,过滤器!(MATCHED)被添加到表达式中以仅更新那些尚未存在的值匹配:

DT1[!(MATCHED), MATCHED := DT2[.SD, on=.(Criteria), .N, by=.EACHI ]$N > 0L ]

然后我注意到如果该行重新运行多次,每次后续运行,将会有越来越多的匹配,这些匹配在前面的运行中不匹配。 (与单独的数据库无关,每次运行都与DT2匹配)。

首先运行:

   MATCHED       N
1:   FALSE 3248007
2:    TRUE 2379514

第二轮:

   MATCHED       N
1:   FALSE 2149648
2:    TRUE 3477873

为了调查,我然后过滤了第一次运行时没有匹配但在第二次运行时匹配的情况。看起来大多数情况都是假阴性,即那些应该在第一次运行时匹配的情况,但事实并非如此。 (但随着许多次运行,最终也出现了许多误报)。

例如,以下是DT1的一个条目:

         DATE FORENAME SURNAME
1: 2016-01-01     JOHN   SMITH

来自DT2的匹配条目:

   START_DATE EXPIRY_DATE FORENAME SURNAME
1: 2015-09-09  2017-05-01     JOHN   SMITH

在主表达式之外单独运行子表达式(如上所述)来查看N数字,我们发现它不会导致匹配,应该是什么时候(N=0 )。 (您可能还会注意到START_DATEEND_DATE在输出中采用DATE的值,但这是另一个问题。)

SUB <- DF2[DF1, on=.(FORENAME, SURNAME, START_DATE <= DATE, EXPIRY_DATE >= DATE), .N, by=.EACHI]
SUB[FORENAME=="JOHN" & "SURNAME=="SMITH"]

   FORENAME SURNAME START_DATE EXPIRY_DATE N
1:     JOHN   SMITH 2016-01-01  2016-01-01 0

然而,错误的行为是结果受DF1中其他行的影响。例如,假设我知道JOHN SMITHDF1的行号为149,并且只将DF1过滤到该行:

DF2[DF1[149], on=.(FORENAME, SURNAME, START_DATE <= DATE, EXPIRY_DATE >= DATE), .N, by=.EACHI]

   FORENAME SURNAME START_DATE EXPIRY_DATE N
1:     JOHN   SMITH 2016-01-01  2016-01-01 1

其次,我还注意到,在条件中只有一个以上的非等标准才会出现有缺陷的行为。如果条件为on=.(FORENAME, SURNAME, START_DATE <= DATE),则运行之间不再存在任何差异,并且所有行似乎在第一次正确匹配。

不幸的是,为了解决现实问题,我必须有几个非等匹配条件。不仅要确保DT1的{​​{1}}介于DATE的{​​{1}}和DT2之间,还要确保START_DATE的{​​{1}} { {1}}在END_DATE之前DT1等等。

总结

CHECKING_DATE中的非等连接在以下情况下以错误的方式运行:

  1. 其中一个表格中存在/缺少某些行
    1. 多个非等同条件
    2. 更新:可重现的示例

      DT2

1 个答案:

答案 0 :(得分:1)

这是一种解决方案,虽然不是很优雅,但在错误未修复的情况下似乎能够提供正确的结果。

首先,我们需要DT1DT2中的每一行都有唯一的ID。行号可以。

DT1[, DT1_ID := 1:nrow(DT1)]
DT2[, DT2_ID := 1:nrow(DT2)]

然后,我们执行以下右连接以查找匹配项:

M <- DT2[DT1, on=.(RANDOM_STRING, START_DATE <= DATE, EXPIRY_DATE >= DATE)]

head(M, 3)

   RANDOM_STRING START_DATE EXPIRY_DATE DT2_ID DT1_ID
1:         diejk 2016-03-30  2016-03-30     NA      1
2:         afjgf 2016-09-14  2016-09-14     NA      2
3:         kehgb 2016-12-11  2016-12-11     NA      3

M的{​​{1}}中的每一行都位于DT1中该行的所有匹配项旁边。当DT2时,没有匹配项。 DT2_ID = NA,表示某些nrow(M) = 100969行与&gt; 1 DT1行匹配。 (日期也接受了错误的价值观。)

接下来,我们可以使用DT2语句标记原始ifelse()中的行,具体取决于它们是否匹配。

DT1

最终结果:13,316场比赛为100,000

DT1$MATCHED <- ifelse(DT1$DT1_ID %in% M[!is.na(DT2_ID)]$DT1_ID, TRUE, FALSE)