如何使用 data.table执行半联接?半连接类似于内连接,只是它只返回 X 的列(而不是 Y 的列),并且不重复 X 的行以匹配 Y 的行。例如,以下代码执行内连接加入:
x <- data.table(x = 1:2, y = c("a", "b"))
setkey(x, x)
y <- data.table(x = c(1, 1), z = 10:11)
x[y]
# x y z
# 1: 1 a 10
# 2: 1 a 11
半连接只会返回x[1]
如何使用 data.table执行半联接?半连接类似于内连接,只是它只返回 X 的列(而不是 Y 的列),并且不重复 X 的行以匹配 Y 的行。例如,以下代码执行内连接加入:
x <- data.table(x = 1:2, y = c("a", "b"))
setkey(x, x)
y <- data.table(x = c(1, 1), z = 10:11)
x[y]
# x y z
# 1: 1 a 10
# 2: 1 a 11
半连接只会返回x[1]
更多可能性:
w = unique(x[y,which=TRUE]) # the row numbers in x which have a match from y
x[w]
如果 x 中有重复的键值,则需要:
w = unique(x[y,which=TRUE,allow.cartesian=TRUE])
x[w]
或者,反过来:
setkey(y,x)
w = !is.na(y[x,which=TRUE,mult="first"])
x[w]
如果 nrow(x) << nrow(y) 那么 y[x] 方法应该更快。
如果 nrow(x) >> nrow(y) 那么 x[y] 方法应该更快。
但是反反加入也很有吸引力:-)
我能想到的一种解决方案是:
tmp <- x[!y]
x[!tmp]
在data.table
中,您可以将另一个数据表作为i
表达式(即data.table.[
调用中的第一个表达式),这将执行连接,例如:
x <- data.table(x = 1:10, y = letters[1:10])
setkey(x, x)
y <- data.table(x = c(1,3,5,1), z = 1:4)
> x[y]
x y z
1: 1 a 1
2: 3 c 2
3: 5 e 3
4: 1 a 4
!
before表达式是上述i
语法的扩展,它执行“not-join”,如 p.1 所述。11 的 data.table文档。因此,第一个分配评估为x
其中没有任何行的子集,其中键(列x
)存在于y
:
> x[!y]
x y
1: 2 b
2: 4 d
3: 6 f
4: 7 g
5: 8 h
6: 9 i
7: 10 j
在这方面类似于setdiff
。因此,第二条语句返回x
键所在的所有行y
。
该!
功能已在NEWSdata.table 1.8.4
中添加以下注释:
o A new "!" prefix on i signals 'not-join' (a.k.a. 'not-where'), #1384i. DT[-DT["a", which=TRUE, nomatch=0]] # old not-join idiom, still works DT[!"a"] # same result, now preferred. DT[!J(6),...] # !J == not-join DT[!2:3,...] # ! on all types of i DT[colA!=6L | colB!=23L,...] # multiple vector scanning approach (slow) DT[!J(6L,23L)] # same result, faster binary search '!' has been used rather than '-' : * to match the 'not-join'/'not-where' nomenclature * with '-', DT[-0] would return DT rather than DT[0] and not be backwards compatible. With '!', DT[!0] returns DT both before (since !0 is TRUE in base R) and after this new feature. * to leave DT[+J...] and DT[-J...] available for future use
出于某种原因,以下内容不起作用x[!(x[!y])]
- 解析参数可能data.table
太聪明了。
PS正如乔什·奥布莱恩(Josh O'Brien)在另一个答案中指出的那样,单行将是x[!eval(x[!y])]
.
我对上面所有的非连接感到困惑,这不是你想要的:
unique(x[y, .SD])
# x y
#1: 1 a
如果x
可以有重复的键,那么你可以唯一的y
:
## Creating an example data.table 'a' three-times-repeated first row
x <- data.table(x = c(1,1,1,2), y = c("a", "a", "a", "b"))
setkey(x, x)
y <- data.table(x = c(1, 1), z = 10:11)
setkey(y, x)
x[eval(unique(y, by = key(y))), .SD] # data.table >= 1.9.8 requires by=key(y)
# x y
# 1: 1 a
# 2: 1 a
# 3: 1 a
更新。根据这里的所有讨论,我会做这样的事情,这应该很快并且在最一般的情况下工作:
x[eval(unique(y[, key(x), with = FALSE]))]
这是另一个更直接的解决方案:
unique(x[eval(y$x)])
它更直接且运行速度更快 - 这是运行时间与我之前的解决方案的比较:
# Generate some large data
N <- 1000000 * 26
x <- data.table(x = 1:N, y = letters, z = rnorm(N))
setkey(x, x)
y <- data.table(x = sample(N, N/10, replace = TRUE), z = sample(letters, N/10, replace = TRUE))
setkey(y, x)
system.time(r1 <- x[!eval(x[!y])])
user system elapsed
7.772 1.217 11.998
system.time(r2 <- unique(x[eval(y$x)]))
user system elapsed
0.540 0.142 0.723
在更一般的情况下,您可以执行类似的操作
x[eval(y[, key(x), with = FALSE])]
我试图编写一个不使用任何名称的方法,这在 OP 的示例中完全令人困惑。
sJ <- function(x,y){
ycols <- 1:min(ncol(y),length(key(x)))
yjoin <- unique(y[, ..ycols])
yjoin
}
x[eval(sJ(x,y))]
对于 Victor 的简单示例,这给出了所需的输出:
x y
1: 1 a
2: 3 c
3: 5 e
这比 Victor 的方式慢了约 30%。
编辑:维克多在加入之前采取独特的方法要快得多:
N <- 1e5*26
x <- data.table(x = 1:N, y = letters, z = rnorm(N))
setkey(x, x)
y <- data.table(x = sample(N, N/10, replace = TRUE), z = sample(letters, N/10, replace = TRUE))
setkey(y, x)
require(microbenchmark)
microbenchmark(
sJ=x[eval(sJ(x,y))],
dolla=unique(x[eval(y$x)]),
brack=x[eval(unique(y[['x']]))]
)
Unit: milliseconds
expr min lq median uq max neval
# sJ 120.22700 125.04900 126.50704 132.35326 217.6566 100
# dolla 105.05373 108.33804 109.16249 118.17613 285.9814 100
# brack 53.95656 61.32669 61.88227 65.21571 235.8048 100
我猜[[
vs$
对速度没有帮助,但没有检查。
这个话题太老了。但是我注意到解决方案可以很容易地从原始帖子中给出的半连接定义中得出:
“半连接类似于内连接,只是它只返回 X 的列(而不是 Y 的列),并且不重复 X 的行以匹配 Y 的行”
library(data.table)
dt1 <- data.table(ProdId = 1:4,
Product = c("Bread", "Cheese", "Pizza", "Butter"))
dt2 <- data.table(ProdId = c(1, 1, 3, 4, 5),
Company = c("A", "B", "C", "D", "E"))
# semi-join
unique(merge(dt1, dt2, on="ProdId")[, names(dt1), with=F])
ProdId Product
1: 1 Bread
2: 3 Pizza
3: 4 Butter
我只是应用了内部连接的语法,然后仅从第一个表中过滤列,并unique()
删除第一个表中重复匹配第二个表行的行。
编辑:dplyr::semi_join()
仅当我们在第一个表中有唯一行时,上述方法才会匹配输出。如果我们需要从第一个表中输出包括重复的所有行,那么我们可以使用fsetdiff()
如下所示的方法。
另一种单行data.table
解决方案:
fsetdiff(dt1, dt1[!dt2, on="ProdId"])
ProdId Product
1: 1 Bread
2: 3 Pizza
3: 4 Butter
我刚刚从第一个表中删除了第一个和第二个的反连接。对我来说似乎更简单。如果第一个表有重复的行,我们将需要:
fsetdiff(dt1, dt1[!dt2, on="ProdId"], all=T)
结果fsetdiff()
与,all=T
dplyr 的输出相匹配:
dplyr::semi_join(dt1, dt2, by="ProdId")
ProdId Product
1 1 Bread
2 3 Pizza
3 4 Butter
使用从以前的一篇文章中获取的另一组数据:
x <- data.table(x = c(1,1,1,2), y = c("a", "a", "a", "b"))
y <- data.table(x = c(1, 1), z = 10:11)
使用 dplyr:
dplyr::semi_join(x, y, by="x")
x y
1 1 a
2 1 a
3 1 a
使用数据表:
fsetdiff(x, x[!y, on="x"], all=T)
x y
1: 1 a
2: 1 a
3: 1 a
如果没有,all=T
,将删除重复的行:
fsetdiff(x, x[!y, on="x"])
x y
1: 1 a
dplyr包支持以下四种连接类型:
inner_join
, left_join
, semi_join
,anti_join
所以对于半连接试试下面的代码
library("dplyr")
table1 <- data.table(x = 1:2, y = c("a", "b"))
table2 <- data.table(x = c(1, 1), z = 10:11)
semi_join(table1, table2)
输出如预期:
# Joining by: "x"
# Source: local data table [1 x 2]
#
# x y
# (int) (chr)
# 1 1 a
尝试以下操作:
w <- y[,unique(x)]
x[x %in% w]
输出将是:
x y
1: 1 a