使用data.table进行左连接

75
假设我有两个:

dataA:

   A  B
1: 1 12
2: 2 13
3: 3 14
4: 4 15

数据B:

   A  B
1: 2 13
2: 3 14

我有以下代码:

merge_test = merge(dataA, dataB, by="A", all.data=TRUE)

我得到:

   A B.x B.y
1: 2  13  13
2: 3  14  14
然而,我想在最终合并的表中保留dataA中的所有行。有方法可以做到这一点吗?

然而,我希望最终合并的表格中包含dataA中的所有行。是否有方法实现这个要求?


7
搜索应该会出现一些涵盖这个问题的问题。这里有一个:https://dev59.com/bWcs5IYBdhLWcg3wh0f7 - mrp
5
如果你想要进行左连接,可以使用 all.x = TRUE。如果你想要进行全外连接,可以使用 all = TRUE - ytk
4
从投票情况来看,或许考虑更改被接受的答案? - zx8754
3个回答

183

如果您想将Bb值添加到A中,最好是将AB连接,并通过引用更新A,操作如下:

A[B, on = 'a', bb := i.b]
which gives:
> A
   a  b bb
1: 1 12 NA
2: 2 13 13
3: 3 14 14
4: 4 15 NA
B[A, on='a']只是将结果打印到控制台,使用A <- B[A, on='a']可以将结果返回到A中。但使用A[B, on = 'a', bb := i.b]A <- B[A, on = 'a']更好,因为它具有更高的内存效率,能够使A在内存中位置不变。
> address(A)
[1] "0x102afa5d0"
> A[B, on = 'a', bb := i.b]
> address(A)
[1] "0x102afa5d0"
另一方面,使用A <- B[A, on = 'a'],将创建一个新对象并将其保存在内存中作为A,因此在内存中具有另一个位置:
> address(A)
[1] "0x102abae50"
> A <- B[A, on = 'a']
> address(A)
[1] "0x102aa7e30"
使用merge(merge.data.table)会导致内存位置类似地发生变化:

数据表的合并可以通过 merge 函数实现,合并后的结果会占用新的内存空间。

> address(A)
[1] "0x111897e00"
> A <- merge(A, B, by = 'a', all.x = TRUE)
> address(A)
[1] "0x1118ab000"
为了提高内存效率,因此最好使用“按引用连接更新”语法:'update-by-reference-join'
A[B, on = 'a', bb := i.b] 

虽然像这样的小数据集不会有明显的差异,但它确实会对像 data.table 这样被设计用于处理大型数据集的工具产生影响。

值得一提的是,A 的顺序仍然保持不变。


为了查看速度和内存使用情况的影响,让我们使用一些更大的数据集进行基准测试(有关数据,请参见下面“使用数据”部分的第二部分):

library(bench)
bm <- mark(AA <- BB[AA, on = .(aa)],
           AA[BB, on = .(aa), cc := cc],
           iterations = 1)
提供以下相关测量数据:
> bm[,c(1,3,5)]
# A tibble: 2 x 3
  expression                         median mem_alloc
  <bch:expr>                       <bch:tm> <bch:byt>
1 AA <- BB[AA, on = .(aa)]            4.98s     4.1GB
2 AA[BB, on = .(aa), `:=`(cc, cc)] 560.88ms   384.6MB
因此,在这个设置中,'update-by-reference-join' 大约快了9倍,并且消耗的内存少了11倍。
注:速度和内存使用方面的收益可能因不同设置而异。
---
使用的数据:
# initial datasets
A <- data.table(a = 1:4, b = 12:15)
B <- data.table(a = 2:3, b = 13:14)

# large datasets for the benchmark
set.seed(2019)
AA <- data.table(aa = 1:1e8, bb = sample(12:19, 1e7, TRUE))
BB <- data.table(aa = sample(AA$a, 2e5), cc = sample(2:8, 2e5, TRUE))

9
好的回答。只需确认,我认为"A[B, bb:=i.b, on='a']"中的"i"是指一般data.table "DT[i, j, by]"语法中的"i"? - cbailiss
11
是的,i.b 的意思是在使用联接操作更新 A 时应该查看 B 表中的 b 列。同样地,使用 x. 前缀可以引用 A 表的列。 - Jaap
6
当创建多个新列时,如何在引用连接中管理?例如,在这里创建了新列bb:=i.b,正如您所述,它查找与i对应的B data.table中相应的b列值。但是当您从(通过引用)合并更大的data.tables来创建许多新列时会发生什么情况? - Prevost
4
@Prevost,这里有一个例子,希望能回答你的问题。 - Jaap
4
关键在于使用mget,请参考我在上一条评论中回答的最后部分。 - Jaap
显示剩余2条评论

24

你可以尝试这个:

# used data
# set the key in 'B' to the column which you use to join
A <- data.table(a = 1:4, b = 12:15)
B <- data.table(a = 2:3, b = 13:14, key = 'a') 

B[A]

如果一个数据表的键是另一个数据表键的子集,那么这个答案可以很好地工作。但如果它们部分交叉,是否有可能进行连接?例如,如果 A, B 如下所示: A <- data.table(a = 1:4, b = 12:15) B <- data.table(a = 2:5, c = 13:16) - Lodyk Vovchak

2
为了完整起见,我添加了table.express版本的答案来回答你的问题。table.express很好地扩展了tidyverse语言到data.table,使其成为处理大型数据集的便利工具。以下是使用你上面提出的问题中的数据集的解决方案:

merge_test = dataA %>% left_join(dataB, by="A")

left_join保留了连接数据集中所有来自dataA的行。
注意:您必须加载data.tabletable.express包。

1
太好了!谢谢 - 我之前没听说过这个包。 - nate-m
1
你能解释一下这个是怎么工作的吗?在tidyverse中,我可以使用left_join(table1, table2, by = c("colname" = "colname"))。但是在table.express中无法使用。我需要一个on =参数。然而,在文档中我找不到任何相关信息。 - gernophil
@gernophil 请查看 table.expressjoin 文档left_join 中的第二个参数,即上面的 by="A",就是你所提到的 on= 参数。可选地,您可以不使用引号来使用变量名。 - ToWii

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接