这是一个关于 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=
语法,它允许在主键以外的字段上进行临时连接。问题 How to join (merge) data frames (inner, outer, left, right)? 的 jangorecki's answer 提供了 data.table 可以处理的其他连接类型的一些示例。
unique()
下面的完全连接方法比 rbind(Y[X],X[Y])
更可取,因为 rbind 将涉及复制表。那正确吗?
unique(c(unique(X[,t]), unique(Y[,t]))
- 这应该更节省内存,因为它只组合两个将小于或等于行数的列表X 和 Y。
引用 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 的答案是正确的,所以请接受这个答案。这只是跟进,评论太长了。
正如 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。
join="all", join="all.x", join="all.y" and join="x.and.y"
。不确定这是否更好。
join
这样,好主意。我发布到 datatable-help 所以让我们看看。也许也给data.table
一些时间安顿下来。例如,您是否必须by-without-by 并加入继承范围?
join
关键字:X[Y,j,join=string]
。连接的可能字符串值建议为:1)“all.y”和“right” -
这个“答案”是一个供讨论的建议:正如我的评论中所指出的,我建议在 [.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 范式。
X[Y,all=T]
可能是在 data.table X[Y] 语法中指定完整外连接的一种优雅方式。或X[Y,all.x=T]
用于左连接。我想知道为什么它不是这样设计的。只是一个想法。