为啥 data.tables 的 X[Y] 连接不允许完全外连接或左连接?
Posted
技术标签:
【中文标题】为啥 data.tables 的 X[Y] 连接不允许完全外连接或左连接?【英文标题】:Why does X[Y] join of data.tables not allow a full outer join, or a left join?为什么 data.tables 的 X[Y] 连接不允许完全外连接或左连接? 【发布时间】:2012-10-07 23:46:03 【问题描述】:这是一个关于 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 = 0
和 nomatch = NA
参数值对于正在执行的操作不是很直观。我更容易理解和记住merge
语法:all = TRUE
、all.x = TRUE
和all.y = TRUE
。既然X[Y]
操作与merge
的相似之处远多于match
,为什么不使用merge
语法而不是match
函数的nomatch
参数呢?
以下是 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's answer 对问题 How to join (merge) data frames (inner, outer, left, right)? 提供了一些 data.table 可以处理的其他连接类型的示例。
【问题讨论】:
你读过FAQ 1.12吗?如果您想要X[Y]
和rbind(Y[X],X[Y])
的左外连接,您可以随时调用Y[X]
,如果您想要完全外连接
查看我的答案以获得更多的 data.table 方法来完全外连接
@mnel,我认为您在下面用于完全连接的unique()
方法比rbind(Y[X],X[Y])
更可取,因为rbind 将涉及复制表。对吗?
据我所知,是的。我还没有测试过三个较小的唯一调用是否比一个大的调用更快(例如unique(c(unique(X[,t]), unique(Y[,t]))
——这应该更有内存效率,因为它只组合了两个将小于或等于行数的列表X 和 Y。
你的问题描述得真好;我在你的问题中找到了我的问题的答案。谢谢
【参考方案1】:
引用data.table
FAQ 1.11 What is the difference between X[Y]
and 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])
,但这需要程序员确定需要哪些列。 data.table 中的X[Y,j
] 一步完成所有这些 你。当您编写X[Y,sum(foo*bar)]
时,data.table 会自动检查j
表达式以查看它使用了哪些列。它只会对这些列进行子集化;其他的被忽略。内存只为j
使用的列创建,Y
列在每个组的上下文中享受标准的 R 回收规则。假设foo
在X
中,而bar 在Y
中(以及Y
中的其他20 个列)。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)]]
【讨论】:
谢谢@mnel。 FAQ 1.12 没有提到完全或左外连接。您对 unique() 的完整外部连接建议很有帮助。那应该在FAQ里。我知道 Matthew Dowle “设计它是为了自己使用,他想要那样做”。 (FAQ 1.9),但我认为X[Y,all=T]
可能是在 data.table X[Y] 语法中指定完整外连接的一种优雅方式。或X[Y,all.x=T]
用于左连接。我想知道为什么它不是这样设计的。只是一个想法。
@DouglasClark 添加了答案,并提交了2302:Add mnel's merge join syntax to FAQ (with timings)。很棒的建议!
@mnel 感谢您的解决方案...让我开心... :)
@mnel unique_keys
data.table 文档让我印象深刻的是它可以如此冗长,但仍然如此神秘......【参考方案2】:
@mnel 的答案是正确的,所以请接受这个答案。这只是跟进,对 cmets 来说太长了。
正如mnel所说,左/右外连接是通过交换Y
和X
获得的:Y[X]
-vs-X[Y]
。因此,该语法支持 4 种连接类型中的 3 种,而不是 2 种,iiuc。
添加第 4 个似乎是个好主意。假设我们添加了full=TRUE
或both=TRUE
或merge=TRUE
(不确定最好的参数名称?),那么我之前没有想到X[Y,j,merge=TRUE]
对FAQ 1.12 中的BUT 之后的原因很有用。新功能请求现已添加并链接回此处,谢谢:
FR#2301 : Add merge=TRUE argument for both X[Y] and Y[X] join like merge() does.
最近的版本加快了merge.data.table
的速度(例如,通过在内部进行浅拷贝以更有效地设置键)。因此,我们正试图让merge()
和X[Y]
更接近,并为用户提供所有选项以实现完全的灵活性。两者都有优点和缺点。另一个突出的功能要求是:
FR#2033 : Add by.x and by.y to merge.data.table
如果还有其他人,请继续。
通过问题中的这一部分:
为什么不使用合并语法而不是 match 函数的 nomatch 参数?
如果您更喜欢 merge()
语法及其 3 个参数 all
、all.x
和 all.y
,那么只需使用它而不是 X[Y]
。认为它应该涵盖所有情况。还是您的意思是为什么[.data.table
中的参数是单个nomatch
?如果是这样,这只是在常见问题解答 2.14 中看起来很自然的方式:“您能否进一步解释为什么 data.table 受到基础中的 A[B] 语法的启发?”。而且,nomatch
目前只接受两个值0
和NA
。这可以扩展为负值意味着什么,或者 12 意味着使用第 12 行的值来填充 NA,例如,或者 nomatch
在未来可能是一个向量,甚至它本身就是一个 data.table
。
嗯。 by-without-by 如何与 merge=TRUE 交互?也许我们应该把它交给datatable-help。
【讨论】:
谢谢@Matthew。 @mnel 的回答非常好,但我的问题不是如何进行完全或左连接,而是“是否有理由只支持两种类型的连接?”所以现在它有点哲学化了 ;-) 实际上我不喜欢合并语法,但似乎 R 的传统是建立在人们熟悉的现有东西上。我在笔记的空白处写下了join="all", join="all.x", join="all.y" and join="x.and.y"
。不知道这样是否更好。
@DouglasClark 也许join
这样,好主意。我发布到 datatable-help 所以让我们看看。也许也给data.table
一些时间安顿下来。例如,您是否必须 by-without-by 并加入继承范围?
正如我在上面的评论中所指出的,我建议在 i 是数据表时添加 join
关键字:X[Y,j,join=string]
。连接的可能字符串值建议为:1)“all.y”和“right”-
嗨,马特,data.table 库太棒了;谢谢你;尽管我认为连接行为(默认为右外连接)应该在主文档中突出说明;我花了 3 天时间才弄明白。
@tucson 只是链接到这里,现在归档为issue #709。【参考方案3】:
这个“答案”是一个供讨论的建议:正如我的评论中所指出的,我建议在 [.data.table() 中添加一个join
参数以启用其他类型的连接,即:X[Y,j,join=string]
。除了 4 种普通 join,我还建议支持 3 种 exclusive join 和 cross join。
各种连接类型的join
字符串值(和别名)建议为:
"all.y"
和 "right"
-- 右连接,当前 data.table 默认 (nomatch=NA) -
在没有 X 匹配的情况下,所有具有 NA 的 Y 行;
"both"
和 "inner"
-- 内连接 (nomatch=0) - 仅 X 和 Y 匹配的行;
"all.x"
和 "left"
-- 左连接 - 来自 X、NA 的所有行,其中没有 Y 匹配:
"outer"
和 "full"
-- 完全外连接 - 来自 X 和 Y 的所有行,NA 不匹配
"only.x"
和 "not.y"
-- 非连接或反连接返回 X 行,其中没有 Y 匹配
"only.y"
和 "not.x"
-- 非连接或反连接返回 Y 行,其中没有 X 匹配
"not.both"
-- 排他连接返回 X 和 Y 行与另一个表不匹配,即异或(XOR)
"cross"
-- 交叉连接或笛卡尔积,X 的每一行与 Y 的每一行匹配
默认值为join="all.y"
,对应于当前默认值。
“all”、“all.x”和“all.y”字符串值对应merge()
参数。 “right”、“left”、“inner”和“outer”字符串可能更适合 SQL 用户。
“both”和“not.both”字符串是我目前最好的建议——但有人可能对内部连接和排他连接有更好的字符串建议。 (我不确定“独占”是否是正确的术语,如果“XOR”连接有合适的术语,请纠正我。)
使用join="not.y"
是X[-Y,j]
或X[!Y,j]
非连接语法的替代方法,并且可能更清楚(对我而言),尽管我不确定它们是否相同(data.table 中的新功能版本 1.8.3)。
交叉连接有时很方便,但它可能不适合 data.table 范式。
【讨论】:
请将此发送至datatable-help 进行讨论。 +1 但是,请发送至datatable-help,或提交feature request。我不介意添加join
,但除非它进入跟踪器,否则它会被遗忘。
我看到你还没有登录 S.O.一阵子。所以我在FR#2301 提交了这个
@MattDowle,为此功能 +1。 (尝试通过FR#2301 执行此操作,但我收到一条权限被拒绝消息)。
@adilapapaya 我们从 RForge 搬到了 GitHub。请在此处 +1:github.com/Rdatatable/data.table/issues/614。 Arun 将问题移植过来,以免丢失。以上是关于为啥 data.tables 的 X[Y] 连接不允许完全外连接或左连接?的主要内容,如果未能解决你的问题,请参考以下文章
data.table 连接然后将列添加到现有的 data.frame 而无需重新复制