【问题标题】:Perform a semi-join with data.table使用 data.table 执行半连接
【发布时间】:2013-09-28 22:47:30
【问题描述】:

如何使用 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]

【问题讨论】:

    标签: r data.table semi-join


    【解决方案1】:

    我试图编写一个不使用任何名称的方法,这在 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%。

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

    【讨论】:

      【解决方案2】:

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

      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
      

      【讨论】:

        【解决方案3】:

        尝试以下方法:

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

        输出将是:

           x y
        1: 1 a
        

        【讨论】:

          【解决方案4】:

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

          "半连接类似于内连接,只是它只返回 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
          

          【讨论】:

          • x 具有相同的(键)行时,您的第一个方法会遇到与 eddi 的第一个答案相同的问题。
          • 我注意到它实际上是同一个表达式。您能指出fsetdiff() 方法会失败的情况吗?
          • 似乎fsetdiff 方法也像第一种方法一样摆脱了 x 中的重复行。尝试使用 eddi 的第二种方法中的数据。结果应该是 3 行,但 fsetdiff 只返回 1。
          • fsetdiff(dt1, dt1[!dt2, on="ProdId"], all=T) 将获取所有行。我已经更新了我的帖子以包含这两种情况。
          • all=TRUE 的 fsetdiff 是 Victor 答案的一个很好的变体,但我仍然觉得它在语法方面并不令人满意。有几个未解决的请求,仅供参考:#915(也链接在马特的回答下)和我的 github.com/Rdatatable/data.table/issues/2158(我之所以写是因为我忘记了第一个......)
          【解决方案5】:

          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
          

          【讨论】:

          • 是的,我问了这个问题,以便弄清楚如何让semi_join() 为数据表工作;)
          【解决方案6】:

          更多可能性:

          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(x) >> nrow(y) 那么 x[y] 方法应该更快。

          但反反加入也很有吸引力:-)

          【讨论】:

          • 酷!现在我明白allow.cartesian=TRUE 的用途了。
          • 我就知道大师会过来的!既然您正式喜欢反反连接方法,那么在语法中添加x[!!y] 怎么样? :)
          • @VictorK。 :) x[!!y] 目前是一个错误,不是吗,没关系。请作为功能请求提交。
          • 不管怎样,反连接操作被实现为x[!y],而不是上面的语法。
          • 这个解决方案会导致 R 在我使用中等大小的 data.tables 时崩溃。 (x ~ 300 万行,y ~ 2,000 行)。真希望半连接运算符作为 data.table 核心的一部分实现......
          【解决方案7】:

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

          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
          

          i 表达式之前的 ! 是上述语法的扩展,它执行“不加入”,如 p.1 所述。 11 的 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。因此,第二条语句返回 x 中的所有行,其中键 is 出现在 y 中。

          ! 功能已添加到 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 在解析参数方面太聪明了。

          附:正如 Josh O'Brien 在另一个答案中指出的那样,单行代码是 x[!eval(x[!y])]

          【讨论】:

          • 您介意解释一下它是如何工作的吗?数据表的否定是什么?
          • 在第 11 页here,上面写着All types of ‘i‘ may be prefixed with !. This signals a not-join or not-select should be performed.
          • 所以您需要知道这一点以及后面的两个高级说明。我认为完整的解释对您的回答很有帮助。
          • @hadley - 现在看起来更好了吗?
          • 我知道:)。让我们等到@Matthew Dowle 来启发我们所有人。
          【解决方案8】:

          更新。根据这里的所有讨论,我会做这样的事情,它应该很快并且在最一般的情况下工作:

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

          【讨论】:

          • 是的,我已经在上面添加了
          • 如果你在加入之前采取独特的方式,它会更快(在我的回答中进行了类似的基准测试)。
          猜你喜欢
          • 2019-12-02
          • 1970-01-01
          • 2015-02-13
          • 2021-04-22
          • 2016-04-08
          • 2021-11-24
          • 2013-02-16
          • 2014-03-02
          相关资源
          最近更新 更多