ChatGPT解决这个技术问题 Extra ChatGPT

为什么 data.tables 的 X[Y] 连接不允许完全外连接或左连接?

这是一个关于 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= 语法,它允许在主键以外的字段上进行临时连接。问题 How to join (merge) data frames (inner, outer, left, right)?jangorecki's answer 提供了 data.table 可以处理的其他连接类型的一些示例。

你读过FAQ 1.12吗?如果您想要 X[Y]左外连接,您始终可以调用 Y[X],如果您想要完全外连接,则可以调用 rbind(Y[X],X[Y])
有关完整外部联接的更多 data.table 方法,请参阅我的答案
@mnel,我假设您的 unique() 下面的完全连接方法比 rbind(Y[X],X[Y]) 更可取,因为 rbind 将涉及复制表。那正确吗?
据我所知,是的。我还没有测试过三个较小的唯一调用是否比一个大调用更快(例如 unique(c(unique(X[,t]), unique(Y[,t])) - 这应该更节省内存,因为它只组合两个将小于或等于行数的列表X 和 Y。
你的问题描述得很好;我在你的问题中找到了我的问题的答案。谢谢

H
Henrik

引用 data.table FAQ 1.11 What is the difference between X[Y] and merge(X, Y)?

X[Y] 是一个连接,使用 Y(或 Y 的键,如果有的话)作为索引来查找 X 的行。 Y[X] 是一个连接,使用 X(或 X 的键,如果有的话)查找 Y 的行,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 <- union(X[,t], Y[,t]) 可以用更少的击键来完成这项工作
data.table 文档给我留下深刻印象的是它可以如此冗长,但仍然如此神秘......
M
Matt Dowle

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

正如 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


谢谢@马修。 @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
D
Douglas Clark

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

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

"all.y" 和 "right" -- 右连接,当前 data.table 默认值 (nomatch=NA) - 所有 Y 行的 NAs 没有 X 匹配; "both" 和 "inner" -- 内连接 (nomatch=0) - 只有 X 和 Y 匹配的行; “all.x”和“left”——左连接——来自X的所有行,不匹配Y的NA:“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,但除非它进入跟踪器,否则它会被遗忘。
我看到您有一段时间没有登录 SO。所以我在 FR#2301 中提交了这个
@MattDowle,为此功能 +1。 (尝试通过 FR#2301 执行此操作,但我收到一条权限被拒绝消息)。
@adilapapaya 我们从 RForge 搬到了 GitHub。请在此处 +1:github.com/Rdatatable/data.table/issues/614。 Arun 移植了这些问题,因此它们不会丢失。