使用data.table执行半连接

时间:2013-09-23 21:43:37

标签: r data.table semi-join

如何使用data.table执行semi-join?半连接类似于内部连接,除了它只返回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]

8 个答案:

答案 0 :(得分:16)

更多可能性:

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)&lt;&lt; nrow(y)然后y [x]方法应该更快 如果nrow(x)&gt;&gt; nrow(y)然后x [y]方法应该更快。

但反抗联盟也有吸引力: - )

答案 1 :(得分:11)

我能想到的一个解决方案是:

tmp <- x[!y]
x[!tmp]

data.table中,您可以将另一个数据表作为i表达式(即data.table.[调用中的第一个表达式),并执行连接,例如:< / p>

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

!表达式之前的i是上述语法的扩展,执行“非连接”,如第4页所述。 11 of data.table documentation。因此,第一个作业的评估结果为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。因此,第二个语句返回xy 键的所有行。

!中添加了data.table 1.8.4功能,并在NEWS中添加了以下注释:

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在解析参数方面过于聪明。

P.S。正如Josh O&Brien指出另一个答案,一行是x[!eval(x[!y])]

答案 2 :(得分:9)

我对上面的所有非连接感到困惑,不是你想要的简单:

unique(x[y, names(x), with = F])
#   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))), names(x), with = F] # data.table >= 1.9.8 requires by=key(y)
#    x y
# 1: 1 a
# 2: 1 a
# 3: 1 a

答案 3 :(得分:3)

<强>更新即可。根据这里的所有讨论,我会做这样的事情,这应该是快速的,并在最常见的情况下工作:

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])]

答案 4 :(得分:3)

我尝试编写一个不使用任何名称的方法,这在OP的例子中完全令人困惑。

sJ <- function(x,y){
    ycols <- 1:min(ncol(y),length(key(x)))
    yjoin <- unique(y[,ycols,with=FALSE,drop=FALSE])
    yjoin
}

x[eval(sJ(x,y))]

对于Victor更简单的例子,这给出了所需的输出:

   x y
1: 1 a
2: 3 c
3: 5 e

这比Victor的方式慢了约30%。

编辑:在加入之前,Victor的做法非常独特:

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 $对速度没有帮助,但没有检查。

答案 5 :(得分:1)

这个帖子太旧了。但我注意到解决方案可以很容易地从原始帖子中给出的半连接定义中得出:

  

“半连接就像一个内连接,只是它只返回   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

使用data.table:

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

答案 6 :(得分:0)

dplyr 支持以下四种连接类型:

inner_joinleft_joinsemi_joinanti_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

答案 7 :(得分:0)

尝试以下操作:

SELECT *
FROM USER_DEPENDENCIES
WHERE TYPE IN ('PACKAGE', 'PACKAGE BODY')
    AND REFERENCED_TYPE = 'TABLE';

输出将是:

 w <- y[,unique(x)]
 x[x %in% w]