139

这是一个关于 data.table 连接语法的哲学问题。我发现 data.tables 的用途越来越多,但仍在学习...

data.tables的连接格式X[Y]非常简洁、方便和高效,但据我所知,它只支持内连接和右外连接。要获得左或完全外连接,我需要使用merge

  • X[Y, nomatch = NA]-- Y 中的所有行 -- 右外连接(默认)
  • X[Y, nomatch = 0]-- 只有在 X 和 Y 中都匹配的行 -- 内连接
  • merge(X, Y, all = TRUE)-- X 和 Y 中的所有行 -- 全外连接
  • merge(X, Y, all.x = TRUE)-- X 中的所有行 -- 左外连接

在我看来,如果X[Y]连接格式支持所有 4 种连接类型会很方便。是否有理由只支持两种类型的连接?

对我来说,nomatch = 0nomatch = NA参数值对于正在执行的操作不是很直观。我更容易理解和记住merge语法:all = TRUE,all.x = TRUEall.y = TRUE. 既然X[Y]操作类似于,为什么不使用连接的语法而不是merge函数的参数呢?matchmergematchnomatch

以下是 4 种连接类型的代码示例:

# sample X and Y data.tables
library(data.table)
X <- data.table(t = 1:4, a = (1:4)^2)
setkey(X, t)
X
#    t  a
# 1: 1  1
# 2: 2  4
# 3: 3  9
# 4: 4 16

Y <- data.table(t = 3:6, b = (3:6)^2)
setkey(Y, t)
Y
#    t  b
# 1: 3  9
# 2: 4 16
# 3: 5 25
# 4: 6 36

# all rows from Y - right outer join
X[Y]  # default
#  t  a  b
# 1: 3  9  9
# 2: 4 16 16
# 3: 5 NA 25
# 4: 6 NA 36

X[Y, nomatch = NA]  # same as above
#    t  a  b
# 1: 3  9  9
# 2: 4 16 16
# 3: 5 NA 25
# 4: 6 NA 36

merge(X, Y, by = "t", all.y = TRUE)  # same as above
#    t  a  b
# 1: 3  9  9
# 2: 4 16 16
# 3: 5 NA 25
# 4: 6 NA 36

identical(X[Y], merge(X, Y, by = "t", all.y = TRUE))
# [1] TRUE

# only rows in both X and Y - inner join
X[Y, nomatch = 0]  
#    t  a  b
# 1: 3  9  9
# 2: 4 16 16

merge(X, Y, by = "t")  # same as above
#    t  a  b
# 1: 3  9  9
# 2: 4 16 16

merge(X, Y, by = "t", all = FALSE)  # same as above
#    t  a  b
# 1: 3  9  9
# 2: 4 16 16

identical( X[Y, nomatch = 0], merge(X, Y, by = "t", all = FALSE) )
# [1] TRUE

# all rows from X - left outer join
merge(X, Y, by = "t", all.x = TRUE)
#    t  a  b
# 1: 1  1 NA
# 2: 2  4 NA
# 3: 3  9  9
# 4: 4 16 16

# all rows from both X and Y - full outer join
merge(X, Y, by = "t", all = TRUE)
#    t  a  b
# 1: 1  1 NA
# 2: 2  4 NA
# 3: 3  9  9
# 4: 4 16 16
# 5: 5 NA 25
# 6: 6 NA 36

更新:data.table v1.9.6 引入了on=语法,它允许在主键以外的字段上进行临时连接。jangorecki如何加入(合并)数据帧(内部、外部、左侧、右侧)的问题的回答?提供了一些 data.table 可以处理的附加连接类型的示例。

4

3 回答 3

79

引用FAQ 1.11和 和有什么data.table 区别?X[Y]merge(X, Y)

X[Y]是一个连接,使用 Y(或 Y 的键,如果有的话)作为索引来查找 X 的行。

Y[X]是一个连接,使用 X 查找 Y 的行(或 X 的键,如果有的话)

merge(X,Y)同时做这两种方式。X[Y]和的行数Y[X]通常不同,而和返回的行数merge(X,Y)merge(Y,X)相同的。

但是这忽略了要点。大多数任务都需要在连接或合并后对数据执行某些操作。为什么要合并所有数据列,然后只使用其中的一小部分?您可能会建议 merge(X[,ColsNeeded1],Y[,ColsNeeded2]),但这需要程序员确定需要哪些列。X[Y,j] 在 data.table 中为您一步完成所有这些。当您编写时X[Y,sum(foo*bar)], data.table 会自动检查j表达式以查看它使用了哪些列。它只会对这些列进行子集化;其他的被忽略。内存只为j使用的列创建,列Y在每个组的上下文中享受标准的 R 回收规则。假设foo是 in X,而 bar 是 in Y(以及 20 个其他列Y)。不是吗X[Y,sum(foo*bar)]比将所有内容合并后跟一个子集更快速地编程和运行?


如果你想要一个左外连接X[Y]

le <- Y[X]
mallx <- merge(X, Y, all.x = T)
# the column order is different so change to be the same as `merge`
setcolorder(le, names(mallx))
identical(le, mallx)
# [1] TRUE

如果你想要一个完整的外部连接

# the unique values for the keys over both data sets
unique_keys <- unique(c(X[,t], Y[,t]))
Y[X[J(unique_keys)]]
##   t  b  a
## 1: 1 NA  1
## 2: 2 NA  4
## 3: 3  9  9
## 4: 4 16 16
## 5: 5 25 NA
## 6: 6 36 NA

# The following will give the same with the column order X,Y
X[Y[J(unique_keys)]]
于 2012-10-08T00:18:23.577 回答
26

@mnel 的答案是正确的,所以请接受这个答案。这只是跟进,评论太长了。

正如 mnel 所说,左/右外连接是通过交换Y和获得的XY[X]-vs- X[Y]。因此,该语法支持 4 种连接类型中的 3 种,而不是 2 种,iiuc。

添加第 4 个似乎是个好主意。假设我们添加full=TRUEboth=TRUEmerge=TRUE(不确定最好的参数名称?)那么在我之前没有想到X[Y,j,merge=TRUE]这对于FAQ 1.12 中的BUT 之后的原因是有用的。现在添加新功能请求并链接回此处,谢谢:

FR#2301:像 merge() 一样为 X[Y] 和 Y[X] 连接添加 merge=TRUE 参数。

最近的版本加快了速度merge.data.table(例如,通过在内部获取浅拷贝以更有效地设置键)。因此,我们正在努力拉近,merge()X[Y]为用户提供所有选项以实现充分的灵活性。两者都有优点和缺点。另一个突出的功能要求是:

FR#2033:将 by.x 和 by.y 添加到 merge.data.table

如果还有其他人,请让他们来。

通过问题中的这一部分:

为什么不使用合并语法而不是 match 函数的 nomatch 参数?

如果您更喜欢merge()语法及其 3 个参数allall.xall.y只需使用它而不是X[Y]. 认为它应该涵盖所有情况。或者你的意思是为什么这个论点是单一nomatch[.data.table?如果是这样,这只是在常见问题解答 2.14 中看起来很自然的方式:“您能否进一步解释为什么 data.table 受到基础中的 A[B] 语法的启发?”。而且,nomatch目前只取两个值0NA。这可以扩展为负值意味着某些东西,或者 12 意味着使用第 12 行的值来填充 NA,例如,或者nomatch将来可能是一个向量,甚至它本身就是 a data.table

嗯。by-without-by如何与 merge=TRUE 交互?也许我们应该把它交给datatable-help

于 2012-10-08T09:30:46.093 回答
19

这个“答案”是一个供讨论的提议:正如我的评论中所指出的,我建议join在 [.data.table() 中添加一个参数以启用其他类型的连接,即:X[Y,j,join=string]。除了4种普通join之外,我还建议支持3种exclusive join,以及cross join。

各种连接类型的join字符串值(和别名)建议为:

  1. "all.y""right"-- 右连接,当前 data.table 默认值 (nomatch=NA) - 所有 Y 行的 NA 没有 X 匹配;
  2. "both""inner" -- 内连接 (nomatch=0) - 只有 X 和 Y 匹配的行;

  3. "all.x""left" -- 左连接 - 来自 X、NA 的所有行,其中没有 Y 匹配:

  4. "outer""full" -- 完全外连接 - 来自 X 和 Y 的所有行,不匹配的 NA

  5. "only.x""not.y"-- 非连接或反连接返回没有 Y 匹配的 X 行

  6. "only.y""not.x"-- 非连接或反连接返回没有 X 匹配的 Y 行
  7. "not.both"-- 排他连接返回与另一个表不匹配的 X 和 Y 行,即异或 (XOR)
  8. "cross"-- 交叉连接或笛卡尔积,X 的每一行与 Y 的每一行匹配

默认值join="all.y"对应于当前默认值。

“all”、“all.x”和“all.y”字符串值对应于merge()参数。“right”、“left”、“inner”和“outer”字符串可能更适合 SQL 用户。

“both”和“not.both”字符串是我目前最好的建议——但对于内部连接和独占连接,可能有人有更好的字符串建议。(我不确定“独占”是否是正确的术语,如果“XOR”连接有合适的术语,请纠正我。)

使用 ofjoin="not.y"是替代 X[-Y,j]或非X[!Y,j]连接语法,可能更清楚(对我而言),尽管我不确定它们是否相同(data.table 版本 1.8.3 中的新功能)。

交叉连接有时很方便,但它可能不适合 data.table 范式。

于 2012-10-31T13:50:08.237 回答