首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >一种跨越多个依赖条件的有效索引/加入data.table的停止检测算法

一种跨越多个依赖条件的有效索引/加入data.table的停止检测算法
EN

Stack Overflow用户
提问于 2021-04-21 18:08:13
回答 1查看 138关注 0票数 3

编辑:可用的真实数据集这里

感谢……

王,瑞,陈方林,陈振宇,李天星,哈拉里,燕姿,夏周,德罗本-泽夫,安德鲁·坎贝尔。StudentLife:评估使用智能手机的大学生的心理健康、学业成绩和行为趋势。关于普适计算的ACM会议论文集。2014年。

解释

我正在运行一项模拟研究,根据相对简单的标准对位置数据(lat/lon坐标)执行停止检测。

如果在A之后至少有180秒的时间戳存在另一个位置(B),且A和B之间的所有位置距离A小于或等于80米,则该位置(A)为停止。

我试图减少数据,使它仍然工作,但不需要实际的坐标。

代码语言:javascript
运行
复制
data <- data.table(id = c(1, 2, 3, 4, 5, 6, 7, 8, 9, 10),
                   latlon = c(0, 50, 80, 90, 90, 100, 190, 110, 110, 110),
                   time = c(0, 60, 120, 180, 240, 300, 360, 420, 480, 520))

id 1不是停止,因为时差大于180 (id 5)的第一个位置的距离为90。

id 2是一个停止,因为它本身与时差大于180 (id 6)的第一个位置之间的所有位置的距离都小于80 (0、30、40、40、50)。

id 6不是一个停止,因为即使id 10 >180个时间差,id 7之间的距离也大于80。

id 8不是一个停止,因为之后至少180秒之后没有位置。

最终,我需要能够贪婪地分配“停止id”,例如,如果我发现id 2有通过id 7满足距离要求的点,那么id 2:7的位置的停止id为2。

矩阵与循环

如果我运行这个:

代码语言:javascript
运行
复制
nrows <- nrow(data)

latlon_dist <- outer(data$latlon, data$latlon, `-`)
latlon_dist[upper.tri(latlon_dist)] <- NA
time_window <- outer(data$time, data$time, `-`)
time_window[upper.tri(time_window)] <- NA

foo <- function(x){
  mindist <- min(which(x[, 1] > 80), nrows)
  if (mindist >= min(which(x[, 2] > 180), nrows + 1)) mindist else NA
}

bar <- array(c(latlon_dist, time_window),
  dim = c(nrows, nrows, 2))


apply(bar, 2, foo)

它提供给我阈值> NA 7 7 NA NA NA NA NA NA NA,我可以在一个for循环中使用它来适当地设置停止id。

代码语言:javascript
运行
复制
threshholds <- apply(bar, 2, foo) - 1

previous_threshhold <- 0
for (i in seq_along(threshholds)) {
  current_threshhold <- threshholds[i]
  
  if (!is.na(current_threshhold) && current_threshhold > previous_threshhold) {
    data[i:current_threshhold, stop_id := i]
    previous_threshhold <- current_threshhold
  }
}

在这一点上,这是我能够保证准确性的唯一途径。我所尝试过的其他一切都是正确的,结果却发现它的行为与这种情况不一样。但正如你所想象的,这是非常低效的,在我的模拟研究中,它运行了116,000次。

我的假设是,处理这一问题的最佳方法是在data.table中加入非equi。

当数据集中的行数使数组内存太重时,我目前正在运行的另一个实现的功能更好。我不会翻译这个来处理数据,但是它在这里,以防它给任何人带来任何想法。我已经将它固定在一个while循环中,这样当它已经为多个点分配了一个stop_id时,它就可以跳过一些迭代。如果点1:7都属于stop_id 1,它们不被认为是候选停止本身,我们只是在第8点再次进行测试。从技术上讲,它返回一个不同的解决方案,但是“足够接近”的停止在这个过程的后面被合并,所以最终结果不太可能有很大的不同。

对于循环,没有矩阵

代码语言:javascript
运行
复制
stopFinder <- function(dt){
  
  nrows <- nrow(dt)
  
  if (nrows < 20000){
    return(quickStopFinder(dt))
  }
  i <- 1
  remove_indices <- 0
  while (i < nrows) {
    this_ends  <- dt[!remove_indices,
                     Position(
                       geodist_vec(rep(longitude[1], .N),
                                   rep(latitude[1], .N),
                                   longitude,
                                   latitude,
                                   paired = TRUE),
                       f = function(x) x > 80,
                       nomatch = .N + 1) ] + i - 1
    # A) Do some number of points occur within the distance?
    # B) If so, is it at least three minutes out?
    if (this_ends > (i + 1) && dt[c(i, (this_ends - 1)), timestamp[.N] > time_window[1]]) {
      # Last index is the one before the distance is broken
      last_index_of_stop <- this_ends - 1
      
      # Next run, we will remove all prior considerations
      remove_indices <- c(1:last_index_of_stop)
      
      # Set the point itself
      dt[i,
         `:=`(candidate_stop = TRUE,
              stop_id = id,
              within_stop = TRUE)]
      # Set the attached points
      dt[(i + 1):last_index_of_stop,
         `:=`(within_stop = TRUE,
              stop_id = i)]
      
      # Start iterating again on the point that broke the distance
      i <- this_ends
    } else {
      # If no stop, move on and leave out this point
      remove_indices <- c(1:i)
      i <- i + 1
    }
  }
  dt[]
}

quickStopFinder或多或少是我在开始时共享的实现,它是内存密集型和缓慢的,但比stopFinder稍慢。

以前,我有类似的东西作为基础,但它需要很多后续步骤,并不总是给我我想要的结果,但我会为后人添加它。

代码语言:javascript
运行
复制
  res <- dt[dt,
            on = .(timestamp >= timestamp_dup,
                   timestamp <= time_window)]
  res[, dist := geodist_vec(x1 = longitude,
                            y1 = latitude,
                            x2 = i.longitude,
                            y2 = i.latitude,
                            paired = TRUE,
                            measure = "haversine")]
  res[, candidate_stop := all(dist <= 80), i.id]

新的和真实的数据

使用实际数据中的示例编辑:

这可以用joins来处理这种情况,但是增长太快了。当数据很小时,速度很快。

代码语言:javascript
运行
复制
sm2 <- read.csv(file = "http://daniellemc.cool/sm.csv", row.names = NULL)
sm <- copy(sm2)
setDT(sm)
sm <- sm[, .(timestamp, longitude, latitude, id)]
sm[, timestamp := as.POSIXct(timestamp)]
sm[, id2 := id]

# This is problematic on my data because of how quickly it grows.
test <- sm[sm, on = .(id >= id)]
test[, i.id2 := NULL]
setnames(test, c("time.2", "longitude.2", "latitude.2", "id.1",
                 "id.2", "time.1", "longitude.1", "latitude.1"))


# Time and distance differences calculated between each pair
test[, distdiff := geodist_vec(longitude.1, latitude.1,
                               longitude.2, latitude.2,
                               paired = TRUE)]
test[, timediff := time.2 - time.1]

# Include the next distance to make sure there's at least one within distance and 
# over 180 timediff.
test[, nextdistdiff := shift(distdiff, -1), id.1]

# Are all distances within 180 sec within 80, and is the next following also < 80
test[, dist_met := FALSE]
test[timediff < 180, dist_met := all(distdiff < 80 & nextdistdiff < 80), id.1]
test[, dist_met := any(dist_met), id.1]

# Test how many occur consecutively 
# This keeps us from having > 80 dist but then coming back within 80
test[, consecutive := FALSE]
test[distdiff < 80, consecutive :=  c(TRUE, cummin(diff(id.2) == 1) == 1), id.1]
test[consecutive == TRUE & dist_met == TRUE, stop_id := min(id.1), id.2]
test[test[consecutive == TRUE & dist_met == TRUE], stop_id := i.stop_id, on = .(id.1 = id.2)]
test <- unique(test[, .(stop_id, id.1)])

# Join it back to the data.
sm[test, stop_id := stop_id, on = .(id = id.1)]
EN

回答 1

Stack Overflow用户

发布于 2021-04-26 10:22:22

使用data.table的非赤道联接功能,您可以将数据连接到自身,同时避免笛卡尔产品太昂贵。

由于data.table只允许><=,所以连接首先在矩形区域完成,然后再过滤出适当的距离。根据您提供的实际数据,这使得计算量减少了7倍。

代码语言:javascript
运行
复制
library(data.table)
library(geosphere)

data <- copy(sm)

minduration <- 180
maxdistance <- 80

data[,latmin := destPoint(cbind(longitude,latitude),b = 180, d=maxdistance)[,2]]
data[,latmax := destPoint(cbind(longitude,latitude),b = 0 , d=maxdistance)[,2]]

data[,lonmin := destPoint(cbind(longitude,latitude),b = 270, d=maxdistance)[,1]]
data[,lonmax := destPoint(cbind(longitude,latitude),b = 90, d=maxdistance)[,1]]

data[,timestampmin := timestamp+minduration]

# Cross product with space and time windows
cross <- data[data,.(i.id,x.id,i.latitude,i.longitude,x.latitude,x.longitude,dist = distGeo(cbind(x.longitude,x.latitude),cbind(i.longitude,i.latitude)) ,i.timestamp,x.timestamp) 
              ,on=.(timestamp>timestampmin,
                    longitude >= lonmin,
                    longitude<=lonmax,
                    latitude >= latmin,
                    latitude <= latmax)][
                    dist<maxdistance]

# Summarizing the results
cross[,.(keep=cumsum(fifelse(diff(x.id-i.id)==1,1,NA_integer_))),by=i.id][
      !is.na(keep),.(startid = i.id,nextid = i.id+keep)][
      !(startid %in% nextid)][
      ,.(maxid=max(nextid)),by=startid][
      ,.(stopid = min(startid)),by=maxid]

     maxid stopid
  1:     6      1
  2:    18     10
  3:    26     22
  4:    33     28
  5:    48     40
 ---             
162:  4273   4269
163:  4276   4274
164:  4295   4294
165:  4303   4301
166:  4306   4305
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/67201324

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档