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


123

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

X[Y]data.tables的联接格式非常简洁,方便且有效,但是据我所知,它仅支持内部联接和正确的外部联接。要获得左侧或完全外部联接,我需要使用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对问题的答案如何连接(合并)数据框(内部,外部,左侧,右侧)?提供了data.table可以处理的其他联接类型的一些示例。


4
您阅读常见问题解答1.12吗?Y[X]如果您想要左侧的外部联接X[Y]并且rbind(Y[X],X[Y])想要完整的外部
联接,

见我的回答一个更data.table的方法来完全外连接
MNEL

@mnel,我认为您unique()下面的完全连接方法比更为可取rbind(Y[X],X[Y]),因为rbind将涉及复制表。那正确吗?
道格拉斯·克拉克

据我所知,是的。我还没有测试过三个较小的唯一调用是否比一个较大的调用快(例如unique(c(unique(X[,t]), unique(Y[,t])),这应该提高内存效率,因为它仅组合了两个列表,这些列表将小于或等于X和Y中的行数。
MNEL

2
您的问题如此好描述;我在您的问题中找到了我的问题的答案。谢谢
惹恼了

Answers:


71

引用data.table FAQ 1.11 X[Y]和之间有什么区别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]),但这要求程序员确定需要哪些列。X[Y,jdata.table中的]为您完成所有这些工作。在编写时X[Y,sum(foo*bar)],data.table会自动检查j表达式以查看其使用的列。它将仅对那些列进行子集化;其他被忽略。仅为j使用的列创建内存,并且Y列在每个组的上下文中享有标准的R回收规则。假设fooin中X,而bar在中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)]]

5
谢谢@mnel。常见问题解答1.12没有提及完全或左外部联接。您对unique()的完整外部连接建议非常有帮助。那应该在FAQ中。我知道Matthew Dowle是“为自己使用而设计的,他想那样做。” (常见问题解答1.9),但我认为这 X[Y,all=T]可能是在data.table X [Y]语法中指定完全外部联接的绝佳方法。或X[Y,all.x=T]用于左联接。我想知道为什么它不是那样设计的。只是一个想法。
道格拉斯·克拉克

1
@DouglasClark已添加答案,并提交了2302:将mnel的merge join语法添加到FAQ(带有计时)中。很棒的建议!
马特·道尔

1
@mnel感谢您的解决方案...让我开心... :)
Ankit

@mnel在执行时,有什么方法可以将NA赋值为0 X[Y[J(unique_keys)]]
Ankit

11
关于data.table文档,给我印象最深的是,它是如此冗长,却仍然如此神秘……
NiuBiBang 2016年

24

@mnel的答案很明确,因此请接受该答案。这只是跟进,评论时间过长。

作为MNEL说,左/右外连接是通过交换获得YXY[X]航班吗X[Y]。因此,该语法支持4种连接类型中的3种,而不是2种iiuc。

添加第四似乎是一个好主意。假设我们添加full=TRUEboth=TRUEmerge=TRUE(不确定最佳的参数名称?),那么X[Y,j,merge=TRUE]在FAQ 1.12中,但由于我之后没有想到,它才有用。现在添加了新功能请求,并已链接回这里,谢谢:

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.x并且all.y然后就用这个来代替X[Y]。认为它应该涵盖所有情况。还是您的意思是为什么论点是单一nomatch[.data.table?如果是这样的话,这对于FAQ 2.14来说似乎就是自然的方式:“您能否进一步解释为什么data.table受到base中A [B]语法的启发?”。而且,nomatch当前0和仅接受两个值NA。可以扩展该值,例如,负值表示某种含义,或者12表示使用第12行的值来填充NA,或者nomatch将来可能是向量,甚至可能是本身data.table

嗯 怎么会通过,而无需按与合并交互= TRUE?也许我们应该把它交给datatable-help


谢谢@马修。@mnel的答案很好,但是我的问题不是如何进行完全或左连接,而是“是否有理由仅支持两种类型的连接?” 因此,现在它有点哲学性;-)实际上,我不喜欢合并语法,但是在人们熟悉的现有内容上似乎有一种R传统。我join="all", join="all.x", join="all.y" and join="x.and.y"在笔记的边缘草了。不知道这是否更好。
道格拉斯·克拉克

@DouglasClark也许join是这样,好主意。我发布到datatable-help,让我们看看。也许data.table也要花些时间安顿下来。例如,您是否必须逐个通过,并加入继承的作用域
马特·道尔

正如我在上面的评论中所指出的join,当我是数据表时,我建议向中添加一个关键字:X[Y,j,join=string]。建议连接的可能字符串值为:1)“ all.y”和“ right”
Douglas Clark,

1
嗨,马特,data.table库很棒。谢谢你 尽管我认为联接行为(默认为正确的外部联接)应在主文档中突出说明;我花了三天的时间才弄清楚。
蒂莫西·亨利

1
@tucson只是要链接到这里,现在已成为问题#709
Matt Dowle 2014年

17

这个“答案”是一个讨论的建议:如我的评论中所述,我建议向join[.data.table()添加一个参数以启用其他类型的联接,即:X[Y,j,join=string]。除了4种普通联接之外,我还建议支持3种类型的互斥联接和交叉联接。

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

  1. "all.y""right"-右连接,本data.table默认(NOMATCH = NA) -所有的Y行的NA与其中不存在X匹配;
  2. "both""inner" -内部联接(NOMATCH = O) -仅行,其中X和Y匹配;

  3. "all.x""left" -左连接-X,NA中所有与Y不匹配的行:

  4. "outer""full" -完全外部联接-X和Y,NA中的所有行均不匹配

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

  6. "only.y""not.x"-非连接或反连接返回y行不存在X匹配
  7. "not.both" -互斥连接返回与另一张表不匹配的X和Y行,即,异或(XOR)
  8. "cross"-交叉联接或笛卡尔乘积,其中X的每一行与Y的每一行匹配

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

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

目前,“ both”和“ not.both”字符串是我的最佳建议-但对于内部联接和互斥联接,有人可能会提供更好的字符串建议。(我不确定“排他”是否是正确的术语,如果对“ XOR”联接有合适的术语,请更正我。)

尽管我不确定它们是否相同(data.table版本1.8.3中的新功能),但使用或join="not.y"可以替代 X[-Y,j]X[!Y,j]不使用联接语法,对我来说也许更清楚。

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


1
请将其发送到datatable-help进行讨论。
马特·道尔

3
+1但是,发送至datatable-help或提出功能请求。我不介意添加,join但是除非它进入跟踪器,否则它将被忘记。
马特·道尔

1
我看到您有一段时间没有登录SO了。因此,我已将此文件提交给FR#2301
Matt Dowle 2012年

@MattDowle,为此功能+1。(尝试通过FR#2301来执行此操作,但收到一条拒绝权限消息)。
adilapapaya

@adilapapaya我们从RForge移至GitHub。请在此处+1:github.com/Rdatatable/data.table/issues/614。阿伦将这些问题移植了过来,因此它们不会丢失。
Matt Dowle 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.