【问题标题】:Cartesian product with filter data.table带过滤器 data.table 的笛卡尔积
【发布时间】:2014-11-21 11:08:22
【问题描述】:

我正在尝试通过 data.table 调用替换 SQL 生成的笛卡尔积。 我拥有丰富的资产和价值历史,我需要所有组合的子集。 假设我有 T = [日期,合同,价值] 的表 a。在 SQL 中看起来像

SELECT a.date, a.contract, a.value, b.contract. b.value 
FROM T a, T b
WHERE a.date = b.date AND a.contract <> b.contract AND a.value + b.value < 4

在 R 中,我现在有以下内容

library(data.table)

n <- 1500
dt <- data.table(date     = rep(seq(Sys.Date() - n+1, Sys.Date(), by = "1 day"), 3),
                 contract = c(rep("a", n), rep("b", n), rep("c", n)),
                 value    = c(rep(1, n), rep(2, n), rep(3, n)))
setkey(dt, date)

dt[dt, allow.cartesian = TRUE][(contract != i.contract) & (value + i.value < 4)]

我相信我的解决方案首先创建所有组合(在本例中为 13,500 行),然后过滤(到 3000 行)。然而,SQL(我可能错了)加入子集,更重要的是不要将所有组合加载到 RAM 中。任何想法如何更有效地使用 data.table?

【问题讨论】:

    标签: r data.table


    【解决方案1】:

    使用by = .EACHI 功能。在data.table 中,joinssubsets 联系非常紧密;即,join 只是另一个 subset - 使用 data.table - 而不是通常的整数/逻辑/行名称。考虑到这些情况,它们的设计方式是这样的。

    基于子集的联接允许在联接时将j-表达式和分组操作合并在一起。

    require(data.table)
    dt[dt, .SD[contract != i.contract & value + i.value < 4L], by = .EACHI, allow = TRUE]
    

    这是惯用的方式(如果您只想将 i.* cols 用于条件,但不返回它们),但是,.SD 尚未优化,正在评估 j每个组在.SD 上的-expression 成本很高。

    system.time(dt[dt, .SD[contract != i.contract & value + i.value < 4L], by = .EACHI, allow = TRUE])
    #    user  system elapsed 
    #   2.874   0.020   2.983 
    

    Some cases using .SD have already been optimised。在解决这些情况之前,您可以通过以下方式解决它:

    dt[dt, {
            idx = contract != i.contract & value + i.value < 4L
            list(contract = contract[idx],
                 value = value[idx], 
                 i.contract = i.contract[any(idx)],
                 i.value = i.value[any(idx)]
            )
           }, by = .EACHI, allow = TRUE]
    

    这需要 0.045 秒,而您的方法需要 0.005 秒。但是by = .EACHI 每次都会评估j-表达式(因此内存效率很高)。这是你必须接受的权衡。

    【讨论】:

      【解决方案2】:

      自 v1.9.8 版(2016 年 11 月 25 日在 CRAN 上)起,非 equi 连接可以使用 data.table,可在此处使用。

      此外,OP 的方法会创建“对称重复”(a, b) 和 (b, a)。避免重复将使结果集的大小减半而不会丢失信息(比较?combn

      如果这是 OP 的意图,我们可以使用 non-equi joins 来避免那些对称重复:

      library(data.table)
      dt[, rn := .I][dt, on = .(date, rn < rn), nomatch = 0L][value + i.value < 4]
      

      给了

                  date contract value   rn i.contract i.value
         1: 2013-09-24        a     1 1501          b       2
         2: 2013-09-25        a     1 1502          b       2
         3: 2013-09-26        a     1 1503          b       2
         4: 2013-09-27        a     1 1504          b       2
         5: 2013-09-28        a     1 1505          b       2
        ---                                                  
      1496: 2017-10-28        a     1 2996          b       2
      1497: 2017-10-29        a     1 2997          b       2
      1498: 2017-10-30        a     1 2998          b       2
      1499: 2017-10-31        a     1 2999          b       2
      1500: 2017-11-01        a     1 3000          b       2
      

      与使用 OP 代码的结果相反

                  date contract value i.contract i.value
         1: 2013-09-24        b     2          a       1
         2: 2013-09-24        a     1          b       2
         3: 2013-09-25        b     2          a       1
         4: 2013-09-25        a     1          b       2
         5: 2013-09-26        b     2          a       1
        ---                                             
      2996: 2017-10-30        a     1          b       2
      2997: 2017-10-31        b     2          a       1
      2998: 2017-10-31        a     1          b       2
      2999: 2017-11-01        b     2          a       1
      3000: 2017-11-01        a     1          b       2
      

      下一步是进一步减少创建后需要过滤掉的对数:

      dt[, val4 := 4 - value][dt, on = .(date, rn < rn, val4 > value), nomatch = 0L]
      

      返回与上面相同的结果。

      请注意,过滤条件 value + i.value &lt; 4 被另一个连接条件 val4 &gt; value 替换,其中 val4 是一个特别创建的辅助列。

      基准测试

      对于n &lt;- 150000L 的基准案例,dt 中的行数为 45 万行,时间为:

      n <- 150000L
      dt <- data.table(date     = rep(seq(Sys.Date() - n+1, Sys.Date(), by = "1 day"), 3),
                       contract = c(rep("a", n), rep("b", n), rep("c", n)),
                       value    = c(rep(1, n), rep(2, n), rep(3, n)))
      
      dt0 <- copy(dt)
      microbenchmark::microbenchmark(
        OP = {
          dt <- copy(dt0)
          dt[dt, on = .(date), allow.cartesian = TRUE][
            (contract != i.contract) & (value + i.value < 4)]
        },
        nej1 = {
          dt <- copy(dt0)
          dt[, rn := .I][dt, on = .(date, rn < rn), nomatch = 0L][value + i.value < 4]
        },
        nej2 = {
          dt <- copy(dt0)
          dt[, rn := .I][, val4 := 4 - value][dt, on = .(date, rn < rn, val4 > value), nomatch = 0L]
        },
        times = 20L
      )
      
      Unit: milliseconds
       expr      min       lq     mean   median       uq      max neval cld
         OP 136.3091 143.1656 246.7349 298.8648 304.8166 311.1141    20   b
       nej1 127.9487 133.1772 160.8096 136.0825 146.0947 298.3348    20  a 
       nej2 180.4189 183.9264 219.5171 185.9385 198.7846 351.3038    20   b
      

      因此,在连接后检查value + i.value &lt; 4 似乎比将其包含在非等连接中要快。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2012-09-01
        • 2021-09-05
        • 1970-01-01
        • 2017-08-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多