【问题标题】:R data.table Multiple Conditions JoinR data.table 多个条件连接
【发布时间】:2016-11-12 19:44:23
【问题描述】:

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

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

我真的很喜欢能够在val 语句中添加多个条件的灵活性(例如,如果我想添加一堆 AND 和 OR 组合,我可以这样做),所以我想保留它功能。

有没有办法“正确”使用 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]
)

【问题讨论】:

  • 弗兰克:被指控有罪。你是绝对正确的。它应该说 X+1 x X+1 网格...我希望所有事件点都适合网格,所以我必须包括 0 X 和 Y 网格点。也就是说,我试图解决的问题受此更改的影响最小......事件的数量和网格大小有些随意,除了它们都相当大。谢谢指正。
  • 对于它的价值,如果您能够在每个维度上独立地将标准从单位圆切换到 +/-1,它可以更快:system.time(dtEvents[, { L = lapply(.SD, function(x) rep(as.integer(floor(x)), each=4L)); .( x = L[[1]] + 0:1, y = L[[2]] + rep(0:1, each=2L) ) }, .SDcols = x:y][, .N, by=x:y])。这就是我最初认为你在做的事情。

标签: r join data.table


【解决方案1】:

非常有趣的问题.. 并且很好地使用了by = .EACHI!这是使用 NEW non-equi joins from the current development version, v1.9.7 的另一种方法。

问题:您使用by=.EACHI 是完全合理的,因为另一种选择是执行交叉连接(dtGrid 的每一行都连接到dtEvents 的所有行)但那是 太详尽了,肯定会很快爆炸。

但是by = .EACHI 是与 equi-join 一起使用 dummy 列 执行的,这会导致计算所有距离(除了它一次只计算一个,因此内存有效)。也就是说,在您的代码中,对于每个dtGrid,仍然使用dtEvents 计算所有可能的距离;因此它的扩展性不如预期。

策略:那么您会同意一个可接受的改进是限制将dtGrid 的每一行连接到dtEvents 所产生的行数 .

(x_i, y_i) 来自dtGrid(a_j, b_j) 来自dtEvents,比如1 &lt;= i &lt;= nrow(dtGrid)1 &lt;= j &lt;= nrow(dtEvents)。然后,i = 1 意味着,所有满足(x1 - a_j)^2 + (y1 - b_j)^2 &lt; 1j 都需要被提取。只有在以下情况下才会发生这种情况:

(x1 - a_j)^2 < 1 AND (y1 - b_j)^2 < 1

这有助于显着减少搜索空间,因为我们不必为dtGrid 中的每一行查看dtEvents 中的所有 行,我们只需提取那些行,

a_j - 1 <= x1 <= a_j + 1 AND b_j - 1 <= y1 <= b_j + 1
# where '1' is the radius

这个约束可以直接翻译成一个non-equi连接,和之前一样与by = .EACHI结合。唯一需要的额外步骤是构造列a_j-1, a_j+1, b_j-1, b_j+1,如下所示:

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) 构造非等连接所需的所有列(因为on= 的公式中还不允许使用表达式。

## (2) 执行非 equi 连接,计算距离并检查 &lt; 1 中每一行的受限组合集上的所有距离 dtGrid - 因此应该是 much 更快。​​

基准测试:

# 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

加速分别约为 10 倍、32 倍和 53 倍。

注意dtGrid 中即使dtEvents 中的单行也不满足条件的行将不会出现在结果中(由于nomatch=0L)。如果您想要这些行,您还必须添加 xm/xp/ym/yp 列之一。并检查它们是否有 NA(= 不匹配)。

这就是我们必须删除 所有 0 个计数以获得相同 = TRUE 的原因。

HTH

PS:查看历史以了解另一种变体,其中整个连接被具体化,然后计算距离并生成计数。

【讨论】:

  • 谢谢阿伦!非常聪明的解决方案!我很感激您实际上提供了这两种解决方案,因为我从这两种方法中都学到了技术。我还想探索添加多个条件(例如,添加另一列并仅在等于某个值时求和),因此我将在这些解决方案中使用选项。
猜你喜欢
  • 2015-04-29
  • 2014-03-02
  • 2013-10-03
  • 1970-01-01
  • 1970-01-01
  • 2016-07-20
  • 2020-09-16
  • 1970-01-01
  • 2014-03-24
相关资源
最近更新 更多