【问题标题】:How can I efficiently aggregate with time and distance windows with data.table?如何使用 data.table 有效地聚合时间和距离窗口?
【发布时间】:2021-03-09 21:11:38
【问题描述】:

我正在一项模拟研究中使用 GPS 数据,这意味着我所有的小问题都会再次困扰我。

更大的问题之一是停止检测算法的实现。第一步是生成候选停靠点,在这里我确定在时间和空间上靠近的坐标。

这里有一些玩具数据:

library(data.table)
gcdist <- function(lat1, lon1, lat2, lon2){
  sqrt((12430*abs(lat1 - lat2)/180)^2 +
         ((24901*abs(lon1 - lon2)/360) * cos((lat1+lat2)/2))^2) 
}

time <- rev(Sys.time() - seq(60, 30*60, 60))
lat <- cumsum(c(55, rnorm(29, 0, sd  = .003)))
lon <- cumsum(c(5.2, rnorm(29, 0, sd  = .003)))
id <- 1:30

dt <-data.table(id, time, time_dup = time, lon, lat)
dt[, time_window := time + 60*3]
setkey(dt, time, time_window)

我目前正在使用 data.table 中的 foverlap。 Data.table 友好的解决方案是必须的,因为整个数据集接近 8GB。

这不足以在一个合适的时间线上运行我的模拟研究,而且它也不是很精确。因为我只在三分钟的窗口内查看然后停下来,我必须想办法处理它们的组合。

# Current setup
setkey(dt, time, time_window)
temp <- foverlaps(dt, dt, by.y = c("time", "time_window"), by.x = c("time", "time_dup"), type = "within")
temp[, dist := gcdist(lat, lon, i.lat, i.lon)]
temp[dist < .5, within_stop  := TRUE]
temp[, candidate_stop := all(dist < .5), id]
setkey(temp, id)
setkey(dt, id)
dt[temp, candidate_stop := i.candidate_stop]
setkey(temp, i.id)
dt[temp, within_stop := i.within_stop]

这更接近我应该做的,但对于 8gb 的数据来说太麻烦了。

current_stop <- dt[1, ]  

for (i in seq_len(nrow(dt))) {
  
  dist <- gcdist(current_stop[, lat], current_stop[, lon],
       dt[i, lat], dt[i, lon])
  if(abs(dist) > .5) current_stop <- dt[i]
  dt[i:nrow(dt), stop_id := current_stop[, id]]
}

我有一种预感,在 data.table 的滚动连接或 frollapply 中有适合我的东西。我一直在阅读这些页面并玩弄示例,但我无法让它发挥作用。我想我应该能够滚动应用距离窗口函数,直到距离大于某个截止值,然后重新启动,但如果我能弄清楚如何,我会打败我。

【问题讨论】:

    标签: r data.table gps rolling-computation


    【解决方案1】:

    我相信这应该会有所帮助,尽管您的 within_stop 变量可能存在一些问题。

    dt[dt,
       on = .(time >= time, time_dup <= time_window),
       c("within_stop", "candidate_stop") := {
         within_stop = gcdist(lat, lon, i.lat, i.lon) < 0.5
         .(within_stop = first(within_stop), ## I am least sure about this. 
              candidate_stop = all(within_stop))
       },
       by = .EACHI]
    
    dt
    

    在我的机器上,这个数据集的速度大约是它的两倍。我提到within_stop,因为它可能在每个加入组中有所不同。也就是说,有时它都是真的,而其他时候它可能是混合的。如果有多个匹配项,我无法确定 dt[dt, :=] 的默认值,尽管我认为它可能是 first

    为了让它稍微快一点,您可能可以使用带有 的编译代码,在其中您可以通过短路实现gcdist(),以便它只返回一个布尔值。这样,您就不必分配距离向量和是否在您的公差范围内的布尔向量。这似乎可行,尽管您当前的数据集对我来说性能提升很小。

    bool gcdist_short_circuit(NumericVector lat, NumericVector lon,double i_lat, double i_lon) {
    
      bool out = TRUE;
    
      for (int i = 0; i < lat.size(); i++) {
        if (sqrt(pow(12430*abs(lat[i] - i_lat)/180, 2) + pow((24901*abs(lon[i] - i_lon)/360) * cos((lat[i]+i_lat)/2),2)) >= 0.5) {
           out = FALSE;
           break;
         }
       }
      return(out);
    }
    

    【讨论】:

    • 这不是一个循环的 data.table::between (作为“i”中的过滤器)可以变得更简单和更布尔?
    • @rferrisx 我认为循环的between 可以工作,但与此相比,循环的需要会降低性能。我想。我不在电脑前,但是一个显式循环会让你对每个循环进行子集化和实现内存。而使用by = .EACHI 将为每次迭代回收内存,仅预先实现匹配的整数向量。绝对是一个有趣的想法,我认为它在很大程度上等同于非 equi 连接提供的功能。
    • 由于我提供了一个糟糕的示例集,这种情况变得很复杂。当实际数据非常粗糙时,当然总是很困难。更准确的需求表述是“within_stop”指的是它是否落入任何值为TRUE的candidate_stop的时间窗口内。
    • 我不关注。那么,如果我们将其初始化为FALSE,然后在匹配时在连接上更新? dt[, within_stop := FALSE]; dt[dt_join, on = key, within_stop := TRUE]?如果这听起来像正确的轨道,我可以更新答案。
    猜你喜欢
    • 2016-08-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-15
    • 2018-08-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多