具有过滤数据的笛卡儿积。表

时间:2014-11-21 11:08:22

标签: r data.table

我正在尝试通过data.table调用替换SQL生成的笛卡尔积。 我拥有丰富的资产和价值历史,我需要所有组合的子集。 假设我有一个表格,其中T = [date,contract,value]。在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更有效?

2 个答案:

答案 0 :(得分:6)

使用by = .EACHI功能。在data.table 加入子集非常紧密地联系在一起;即, join 只是另一个子集 - 使用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 - 表达式代价很高。

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 - 表达式(因此内存效率很高)。这是你必须接受的权衡。

答案 1 :(得分:0)

自版本v1.9.8(在2016年11月25日CRAN上),{em>非等联接可以与data.table一起使用,可在此处使用。

此外,OP的方法创造了对称重复&#34; (a,b)和(b,a)。避免重复会使结果集的大小减半而不会丢失信息(比较?combn

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

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 < 4会被另一个加入条件val4 > value所取代,其中val4是一个特别创建的帮助列。

基准

对于n <- 150000L导致dt中450 k行的基准案例,时间为:

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 < 4似乎比将其包含在非equi连接中更快。

相关问题