有效合并两个匹配一个变量或另一个变量的数据集

时间:2017-02-10 09:37:07

标签: sql r merge data.table

我有两个大型数据集d1d2,我想根据EITHER变量idAidB的匹配进行合并。

两者都存在于两个数据集中,但两者都可以包含错误和缺失值(NA),或者idAidB指向其他数据集中的不同观察值。例如,下面的例子。

下面列出了预期的结果。基本上,匹配在idA或idB上。如果重复匹配,则应优先idA次匹配idB匹配。

实际数据集非常大(约10亿)。

是否有有效的方法在R中实现这一点?

此外,SQL中是否存在此类连接的技术术语?

library(tidyverse)
library(data.table)

d1 <- read.table(text=
"idA idB value1
A 10 500
B 1  111
C 4  234
D NA 400
E 7  500
NA 3 700
Z 5  543
Q 9  567
U 23 723
", 
header=T) %>% data.table


d2 <- read.table(text=
"idA idB value2
A 10 11
B 1  12
L 21 15
D 15 12
E 8  17
M 3  18
N 5  13
Z 25 17
Q 23 12
",
header=T) %>% data.table

期望的结果是:

Out <- read.table(text=
"d1.idA d2.idA d1.idB d2.idB d1.v1 d2.v2
A  A  10 10 500 11 # matched on idA and idB
B  B  1  1  111 12 # matched on idA and idB
D  D  NA 15 400 12 # matched on idA. d2.idB had NAs 
E  E  7  8  500 17 # matched on idA. idB had divergent values
NA M  3  3  700 18 # matched on idB. d1.idA had NAs
Z  Z  5  25 543 13 # d1[7,] matched to d2[8,] on idA and d2[9,] on idB. Priority given to idA match. 
Q  Q  9  23 657 17 #  d2[9,] matched to d1[8,] on idA and d1[9,] on idB. Priority given to idA match.
",
header=T) %>% data.table

#Non matched rows
# d1[3,]
# d2[3,]

EDIT1:

  • 添加了预期的结果
  • 由于易于保存data.table(read.table) 读者解析,实际数据来自fread(文件)

EDIT2:从期望的结果中删除不匹配的行

2 个答案:

答案 0 :(得分:2)

我不知道实现所需结果的优雅方式(而且我也不知道SQL中此类操作的技术术语。)

因此,我建议分四步完成:

  1. 内部加入idAidB上的两个data.tables,确定每个data.tables中的剩余行。
  2. 内部加入idA上两个data.tables的剩余行,再次确定剩余的行。
  3. 内部加入idB上的两个data.tables的剩余行。
  4. 合并前面步骤的结果。
  5. 所有4个步骤的代码:

    library(data.table)
    
    # create index column in both data.tables
    d1[, idx := .I]
    d2[, idx := .I]
    
    # inner join on idA and idB
    j1 <- d1[d2, .(idx, i.idx), on = c("idA", "idB"), nomatch = 0L]
    m1 <- unique(j1$idx)
    m2 <- unique(j1$i.idx)
    
    # inner join on idA
    j2 <- d1[!(idx %in% m1)][d2[!(idx %in% m2)], .(idx, i.idx), on = c("idA"), nomatch = 0L]
    m1 <- append(m1, unique(j2$idx))
    m2 <- append(m2, unique(j2$i.idx))
    
    # inner join on idB
    j3 <- d1[!(idx %in% m1)][d2[!(idx %in% m2)], .(idx, i.idx), on = c("idB"), nomatch = 0L]
    m1 <- append(m1, unique(j3$idx))
    m2 <- append(m2, unique(j3$i.idx))
    
    # combine results
    rbindlist(
      list(
        AB = cbind(
          d1[idx %in% j1[, idx]],
          d2[idx %in% j1[, i.idx]]),
        A. = cbind(
          d1[idx %in% j2[, idx]],
          d2[idx %in% j2[, i.idx]]),
        .B = cbind(
          d1[idx %in% j3[, idx]],
          d2[idx %in% j3[, i.idx]])),
      fill = TRUE, 
      idcol = "match_on")
    

    产生

    #   match_on idA idB value1 idx idA idB value2 idx
    #1:       AB   A  10    500   1   A  10     11   1
    #2:       AB   B   1    111   2   B   1     12   2
    #3:       A.   D  NA    400   4   D  15     12   4
    #4:       A.   E   7    500   5   E   8     17   5
    #5:       A.   Z   5    543   7   Z  25     17   8
    #6:       A.   Q   9    567   8   Q  23     12   9
    #7:       .B  NA   3    700   6   M   3     18   6
    

    m1m2用于记住d1d2中的行的行ID,而这些行已经在其中一个中使用过了。以前的加入操作。

    因此,最后可以打印d1d2中未找到匹配项的剩余行:

    d1[!(idx %in% m1)]
    #   idA idB value1 idx
    #1:   C   4    234   3
    #2:   U  23    723   9
    d2[!(idx %in% m2)]
    #   idA idB value2 idx
    #1:   L  21     15   3
    #2:   N   5     13   7   
    

    请注意,在每个连接操作中,仅保留行索引而不是保留所有列。不同连接操作的结果在列的名称和位置上不同。

    在最后的组合步骤中,使用这些索引选择原始data.tables d1d2的行,以生成统一的结果表。

答案 1 :(得分:1)

一年后,我相信我找到了一种比my previous answer更直接的方法。这种方法截然不同,可以扩展到不止两个id列,因此为了清楚起见,最好将其作为单独的答案发布。

OP已请求在idA idB上找到匹配项。如果出现重复的匹配,则应优先选择idA个匹配项,而不是idB个匹配项。

这种方法将中的两个data.tables表中的id列从宽到长格式进行重塑,以使id列的名称成为可在后续使用的数据项加入行动。重塑是可以将此方法缩放为包括任意数量的id列的原因,例如,以在idA idB idC

library(data.table)
library(magrittr)

# create index column in both data.tables
d1[, idx := .I]
d2[, idx := .I]

# reshape only id columns from wide to long format
# rename columns just for clarity
l1 <- d1[, .(idx, idA, idB)] %>% 
  melt(id.vars = "idx", variable.name = "id.col", value.name = "key")
l2 <- d2[, .(idx, idA, idB)] %>% 
  melt(id.vars = "idx", variable.name = "id.col", value.name = "key")

# inner join to find all matching combinations of name of id col & key value
m <- l1[l2, on = .(id.col, key), 
        .(match_on = id.col, i1 = idx, i2 = i.idx), nomatch = 0L]

# remove duplicate entries with precedence of "idA"
m %<>% 
  setorder(match_on) %>% 
  unique(by = c("i1")) %>% 
  unique(by = c("i2")) %>% 
  setorder(i1, i2)

# double join of index table with original tables
d1[d2[m, on = .(idx = i2)], on = .(idx = i1)]
    idA idB value1 idx i.idA i.idB value2 i.idx match_on
1:    A  10    500   1     A    10     11     1      idA
2:    B   1    111   2     B     1     12     2      idA
3:    D  NA    400   4     D    15     12     4      idA
4:    E   7    500   5     E     8     17     5      idA
5: <NA>   3    700   6     M     3     18     6      idB
6:    Z   5    543   7     Z    25     17     8      idA
7:    Q   9    567   8     Q    23     12     9      idA

一些补充说明:

  1. 引入行索引以节省用于存储中间结果的内存,而不是保留所有数据列。
  2. idAidB的值通过重塑在key列中组合时被强制键入字符。
  3. setorder(match_on)是决定idAidB优先级的关键部分。偶然地,idA在字典上排在idB之前。可以通过重新排列因子级别来强制使用其他优先级,例如使用forcats包。