【问题标题】:Find nearest range for a time that does not overlap existing ranges查找不与现有范围重叠的时间的最近范围
【发布时间】:2020-06-29 19:48:43
【问题描述】:

我有一个data.table'd1',带有一个日期时间变量'fdate':

d1 = data.table(fdate = as.POSIXct("2007-06-23 23:54:54"))

我还有另一个 data.table 'd2',有两个日期时间变量,'start' 和 'stop':

d2 = data.table(start = as.POSIXct(c("2007-06-23 00:00:00",  "2007-06-24 00:00:00")),
                stop = as.POSIXct(c("2007-06-23 07:00:00", "2007-06-24 07:00:00")))
#         start                stop
# 1: 2007-06-23 2007-06-23 07:00:00
# 2: 2007-06-24 2007-06-24 07:00:00 # The range closest to "2007-06-23 23:54:54"

我知道如何使用foverlaps 函数来查找重叠范围,即“fdate”在“开始”和“停止”之间的位置。问题是如何找到不包含在任何开始/停止范围内的“fdate”的最近范围?

在示例中,'fdate' 应链接到第 2 行中的范围。这是因为 fdate 在范围 1 之后 +24 小时,但在范围 2 之前仅 4 分钟。

执行此操作的最有效方法是什么?

【问题讨论】:

    标签: r datetime data.table


    【解决方案1】:

    这是一个使用滚动连接的选项:

    mDT <- melt(d2[, rn := .I], id.vars="rn")
    d1[, rownum := 
        mDT[.SD, on=.(value=fdate), roll="nearest", rn]]
    

    【讨论】:

    • IIUC,OP 希望找到 包含在任何开始/停止范围中的“fdate”的最近范围。即使位于 inside 范围内,此答案也会找到最近的端点,例如 "2007-06-23 06:54:54"
    • @Uwe 我假设 OP 知道如何使用 foverlaps 来找到那些重叠的,因此也知道那些没有重叠的。如果我记错了,我也会添加这部分来创建 d1。
    【解决方案2】:

    在这种情况下,将fdate 与每个日期范围之间的距离定义为fdatestartend 之间的绝对距离,以较短者为准,并找到距离最小的日期范围fdate.

    fdate = as.POSIXct("2007-06-23 23:54:54")
    start1 = as.POSIXct("2007-06-23 00:00:00")
    stop1 = as.POSIXct("2007-06-23 07:00:00")
    start2 = as.POSIXct("2007-06-24 00:00:00")
    stop2 = as.POSIXct("2007-06-24 07:00:00")
    
    which.min(c(min(abs(fdate - start1), abs(fdate - stop1)), 
                min(abs(fdate - start2), abs(fdate - start2))))
    
    [1] 2
    

    【讨论】:

      【解决方案3】:

      OP 已要求

      找到不包含在任何fdate 中的最近范围 start/stop范围

      如果fdate 包含在一个范围内,有两种可能的解释:

      • 找到下一个最近的不同范围,或
      • 返回NA

      以下方法可用于获得两种解释的答案。

      找到下一个最近的不同范围

      1. 将行号添加到d2,因为这是必填信息
      2. 做一个反向滚动连接,找到最近的范围start,它晚于或等于每个fdate,计算时间差
      3. 做一个前向滚动连接找到最接近或等于每个fdate的范围stop,计算时间差
      4. rbind() 两个子集,因此每个 fdate 有两行
      5. 对于每个fdate,选择时差最小或行数较小的范围,以防出现平局
      6. fdate 排序结果

      实现为

      d2[, rn2 := .I]
      result <- rbind(
        d2[d1, on = .(start = fdate), roll = -Inf, 
           .(rn2 = x.rn2, fdate = i.fdate, delta = x.start - i.fdate)],
        d2[d1, on = .(stop = fdate),  roll = +Inf, 
           .(rn2 = x.rn2, fdate = i.fdate, delta = i.fdate - x.stop)]
      )[order(rn2), .(rn2 = rn2[which.min(delta)]), keyby = fdate]
      
      result 
      
                       fdate rn2
      1: 2007-06-22 23:00:00   1
      2: 2007-06-23 06:00:00   2
      3: 2007-06-23 08:00:00   1
      4: 2007-06-23 15:30:00   1
      5: 2007-06-23 23:00:00   2
      6: 2007-06-24 06:00:00   1
      7: 2007-06-24 08:00:00   2
      

      请注意,该用例已扩展为使用fdate 在两个范围之外、内部和之间测试各种配置(请参阅数据部分)。

      结果在可视化时更容易验证:

      library(ggplot2)
      ggplot(result) +
        aes(x = fdate, y = factor(rn2), label = seq.int(nrow(d1)), colour = factor(rn2)) + 
        geom_point() +
        geom_text(nudge_y = 0.1) +
        geom_segment(aes(yend = rn2, x = start, xend = stop, label = NULL), data = d2, size = 2) +
        theme_bw() +
        theme(legend.position = "none") +
        ylab("matching d2 row number")
      

      • 点 1、3、5 和 7 不包含在任何范围内,并且已分配到各自最近的范围 1 或 2。
      • 点 4 不包含在任何范围内,但正好位于范围 1 的 stop 和范围 2 的 start 之间的中间,导致距离平局。如果出现平局,order(rn2) 可确保以可预测的方式选择较低的范围。
      • 点 2 包含在范围 1 中,并分配给下一个不同范围 2。
      • 点 6 包含在范围 2 中,并分配给下一个不同范围 1。

      或者,返回NA

      对于第二种解释,NA 分配给包含在一个范围内的fdate。为了识别这些情况,使用data.table%inrange% 运算符,它比foverlaps() 更易于使用:

      result[fdate %inrange% d2[, .(start, stop)], rn2 := NA_integer_][]
      
                       fdate rn2
      1: 2007-06-22 23:00:00   1
      2: 2007-06-23 06:00:00  NA
      3: 2007-06-23 08:00:00   1
      4: 2007-06-23 15:30:00   1
      5: 2007-06-23 23:00:00   2
      6: 2007-06-24 06:00:00  NA
      7: 2007-06-24 08:00:00   2
      

      剧情

      表明点 2 和 6 不再分配给任何范围。

      数据

      library(data.table)
      d1 <- data.table(fdate = as.POSIXct(c("2007-06-22 23:00:00", 
                                            "2007-06-23 06:00:00",
                                            "2007-06-23 08:00:00",
                                            "2007-06-23 15:30:00",
                                            "2007-06-23 23:00:00",
                                            "2007-06-24 06:00:00", 
                                            "2007-06-24 08:00:00")))
      d2 <- data.table(start = as.POSIXct(c("2007-06-23 00:00:00", "2007-06-24 00:00:00")),
                       stop  = as.POSIXct(c("2007-06-23 07:00:00", "2007-06-24 07:00:00")))
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2019-01-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-02-21
        • 2011-12-22
        • 2020-12-25
        相关资源
        最近更新 更多