R data.table多条件加入

时间:2016-07-10 22:53:46

标签: r join data.table

我已经设计了一个解决方案,可以从两个独立数据表的多列中查找值,并添加一个基于列的新值计算(多个条件比较)。代码如下。它涉及在计算两个表的值时使用data.table和join,但是,这些表没有连接到我正在比较的列上,因此我怀疑我可能没有获得data.tables固有的速度优势。我已经阅读了很多关于我的内容并且很兴奋。换句话说,我正在加入一个“虚拟”栏目,所以我认为我没有“正确”加入。

在X网格dtGrid和X ^ 2随机事件dtEvents的列表中给出练习,以确定每个网格的1个单位半径内发生的事件数量点。代码如下。我选择了100 X 100的网格尺寸,在我的机器上运行连接需要大约1.5秒。但是如果不引入巨大的性能(200 X 200需要~22秒),我就无法做得更大。

我非常喜欢能够为我的val语句添加多个条件的灵活性(例如,如果我想添加一堆AND和OR组合,我可以这样做),所以我' d喜欢保留这种功能。

有没有办法使用data.table连接'正确'(或任何其他data.table解决方案)来实现更快更有效的结果?

非常感谢!

#Initialization stuff
library(data.table)
set.seed(77L)

#Set grid size constant
#Increasing this number to a value much larger than 100 will result in significantly longer run times
cstGridSize = 100L

#Create Grid
vecXYSquare <- seq(0, cstGridSize, 1)
dtGrid <- data.table(expand.grid(vecXYSquare, vecXYSquare))
setnames(dtGrid, 'Var1', 'x')
setnames(dtGrid, 'Var2', 'y')
dtGrid[, DummyJoin:='A']
setkey(dtGrid, DummyJoin)

#Create Events
xrand <- runif(cstGridSize^2, 0, cstGridSize + 1)
yrand <- runif(cstGridSize^2, 0, cstGridSize + 1)
dtEvents <- data.table(x=xrand, y=yrand)
dtEvents[, DummyJoin:='A']
dtEvents[, Counter:=1L]
setkey(dtEvents, DummyJoin)

#Return # of events within 1 unit radius of each grid point
system.time(
    dtEventsWithinRadius <- dtEvents[dtGrid, {
        val = Counter[(x - i.x)^2 + (y - i.y)^2 < 1^2];  #basic circle fomula: x^2 + y^2 = radius^2
        list(col_i.x=i.x, col_i.y=i.y, EventsWithinRadius=sum(val))
    }, by=.EACHI]
)

1 个答案:

答案 0 :(得分:12)

非常有趣的问题......以及by = .EACHI的大量使用!这是另一种使用 NEW non-equi joins from the current development version, v1.9.7的方法。

问题:您使用by=.EACHI是完全合理的,因为另一种选择是执行交叉连接(dtGrid的每一行都连接到{{1}的所有行但是那个太详尽并且必然会很快爆炸。

然而dtEvents equi-join 一起使用虚拟列执行,这导致计算所有距离(除了它在一个时间,因此记忆效率高)。也就是说,在您的代码中,对于每个by = .EACHI,所有可能的距离仍然使用dtGrid计算;因此它不会像预期的那样扩展。

策略然后您同意可接受的改进是限制将dtEvents的每一行加入{{1}所会产生的行数}} 的。

dtGrid来自dtEvents(x_i, y_i)来自dtGrid,例如(a_j, b_j)dtEvents。然后,1 <= i <= nrow(dtGrid)表示需要提取满足1 <= j <= nrow(dtEvents)的所有i = 1。这只能在以下情况下发生:

j

这有助于大幅减少搜索空间,因为(x1 - a_j)^2 + (y1 - b_j)^2 < 1中的每一行都不会在(x1 - a_j)^2 < 1 AND (y1 - b_j)^2 < 1 中查看所有行,而只需将这些行提取到其中,

dtEvents

此约束可以直接转换为非equi 连接,并与之前的dtGrid结合使用。所需的唯一额外步骤是构建列a_j - 1 <= x1 <= a_j + 1 AND b_j - 1 <= y1 <= b_j + 1 # where '1' is the radius ,如下所示:

by = .EACHI

a_j-1, a_j+1, b_j-1, b_j+1构造非equi连接所需的所有列(因为foo1 <- function(dt1, dt2) { dt2[, `:=`(xm=x-1, xp=x+1, ym=y-1, yp=y+1)] ## (1) tmp = dt2[dt1, on=.(xm<=x, xp>=x, ym<=y, yp>=y), .(sum((i.x-x)^2+(i.y-y)^2<1)), by=.EACHI, allow=TRUE, nomatch=0L ][, c("xp", "yp") := NULL] ## (2) tmp[] } 的公式中不允许使用表达式。

## (1)执行非equi连接,计算距离并检查on=中每行的受限制组合## (2)的所有距离 - 因此应< em>很多更快。

基准:

< 1

加速比分别为~10x,32x和53x。

请注意,即使dtGrid中的单行也不满足条件的# Here's your code (modified to ensure identical column names etc..): foo2 <- function(dt1, dt2) { ans = dt2[dt1, { val = Counter[(x - i.x)^2 + (y - i.y)^2 < 1^2]; .(xm=i.x, ym=i.y, V1=sum(val)) }, by=.EACHI][, "DummyJoin" := NULL] ans[] } # on grid size of 100: system.time(ans1 <- foo1(dtGrid, dtEvents)) # 0.166s system.time(ans2 <- foo2(dtGrid, dtEvents)) # 1.626s # on grid size of 200: system.time(ans1 <- foo1(dtGrid, dtEvents)) # 0.983s system.time(ans2 <- foo2(dtGrid, dtEvents)) # 31.038s # on grid size of 300: system.time(ans1 <- foo1(dtGrid, dtEvents)) # 2.847s system.time(ans2 <- foo2(dtGrid, dtEvents)) # 151.32s identical(ans1[V1 != 0]L, ans2[V1 != 0L]) # TRUE for all of them 中的行将不会出现在结果中(由于dtGrid)。如果您想要这些行,您还必须添加dtEvents列之一..并检查nomatch=0L(=无匹配)。

这就是我们必须删除所有 0计数以获得相同= xm/xp/ym/yp的原因。

HTH

PS:查看历史记录,了解实现整个连接的另一个变体,然后计算距离并生成计数。