我正在尝试通过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更有效?
答案 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连接中更快。