我已经设计了一个解决方案,可以从两个独立数据表的多列中查找值,并添加一个基于列的新值计算(多个条件比较)。代码如下。它涉及在计算两个表的值时使用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]
)
答案 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:查看历史记录,了解实现整个连接的另一个变体,然后计算距离并生成计数。