为啥 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 = 0nomatch = NA 参数值对于正在执行的操作不是很直观。我更容易理解和记住merge 语法:all = TRUEall.x = TRUEall.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.tableFAQ 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 回收规则。假设fooX 中,而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 的答案是正确的,所以请接受这个答案。这只是跟进,对 cme​​ts 来说太长了。

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

添加第 4 个似乎是个好主意。假设我们添加了full=TRUEboth=TRUEmerge=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 个参数 allall.xall.y,那么只需使用它而不是 X[Y]。认为它应该涵盖所有情况。还是您的意思是为什么[.data.table 中的参数是单个nomatch?如果是这样,这只是在常见问题解答 2.14 中看起来很自然的方式:“您能否进一步解释为什么 data.table 受到基础中的 A[B] 语法的启发?”。而且,nomatch 目前只接受两个值0NA。这可以扩展为负值意味着什么,或者 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 而无需重新复制

具有开始和结束位置的重叠连接

将继承范围与 data.table 中的 by 连接起来

If x == y: print z - 为啥我的代码不打印? [复制]

当一个是查找表时如何加入 data.tables?

为啥在 dbt 中运行模型时出现“关系 <y> 的列 <x> 不存在”错误,但在 SQL 客户端中运行时却没有?