查找 R 中两个时间戳之间的重叠以分配班次

Find the overlap between two timestamps in R to assign shifts

问题

目前,我有一个大型机组人员时间表数据集,其中包含开始时间和结束时间,我的目标是确定一名员工是否在上夜班。夜班定义为 01:00:00 和 05:59:59 之间轮班的任何部分。我看过诸如 %overlaps% 之类的函数,但这些函数似乎只对时间戳不起作用。一些示例数据(UTC-tz):

library(lubridate)
df <- data.frame(start = ymd_hms(c("2018-09-19 23:30:00", "2018-09-19 17:00:00", "2018-09-22 04:30:00")),
                 end = ymd_hms(c('2018-09-20 07:05:00', "2018-09-19 21:00:00", "2018-09-22 12:00:00")))

解决方案

理想情况下,我希望获得以下输出,其中包含一个布尔变量,指示该员工是否上夜班:

               start                 end    night.shift
2018-09-19 23:30:00 | 2018-09-20 07:05:00 |  TRUE
2018-09-19 17:00:00 | 2018-09-19 21:00:00 |  FALSE
2018-09-22 04:30:00 | 2018-09-22 12:00:00 |  TRUE

提前致谢!

这是 super 简陋且未优化的,但它有效(而且很有趣)。如果可能,您需要对其进行矢量化。

library(lubridate)
df <- data.frame(start = ymd_hms(c("2018-09-19 23:30:00", "2018-09-19 17:00:00", "2018-09-22 04:30:00")),
                 end = ymd_hms(c('2018-09-20 07:05:00', "2018-09-19 21:00:00", "2018-09-22 12:00:00")))
night <- interval( hms::as_hms(3600), hms::as_hms(21599), tz = "UTC")
print(night)

for(i in 1:3) {
    s = df$start[i]
    f = df$end[i]
    start_seconds = hms::as_hms(60*60*hour(s) + 60*minute(s) + second(s))
    end_seconds = hms::as_hms(60*60*hour(f) + 60*minute(f) + second(f))
    
    interval <- interval(start_seconds, end_seconds, tz = "UTC")
    
    t <- int_overlaps(night, interval)
    print(t)
    
    }

您可以使用 interval()%--% 创建一个 Interval 对象,并使用 int_overlaps() 测试两个区间是否重叠。

library(dplyr)
library(lubridate)

df %>%
  mutate(
    night.shift = int_overlaps(
      (date(start) + hms("01:00:00")) %--% (date(start) + hms("05:59:59")),
      start %--% end
    ) | int_overlaps(
      (date(end) + hms("01:00:00")) %--% (date(end) + hms("05:59:59")),
      start %--% end
    )
  )

另一种方法是使用 %within% 检查 date-time 对象是否在一个区间内。

df %>%
  rowwise() %>%
  mutate(
    night.shift = any(outer(date(c(start, end)), hms(c("01:00:00", "05:59:59")), `+`) %within% (start %--% end))
  ) %>%
  ungroup()

输出
# # A tibble: 4 × 3
#   start               end                 night.shift
#   <dttm>              <dttm>              <lgl>      
# 1 2018-09-19 23:30:00 2018-09-20 07:05:00 TRUE       
# 2 2018-09-19 17:00:00 2018-09-19 21:00:00 FALSE      
# 3 2018-09-22 04:30:00 2018-09-22 12:00:00 TRUE       
# 4 2018-09-22 04:30:00 2018-09-23 00:30:00 TRUE

参考

Utilities for creation and manipulation of Interval objects

使用seq.POSIXt

transform(df, night.shift=mapply(\(x, y) any(
  as.POSIXct(outer(as.Date(c(x, y)), c('01:00:00', '05:59:59'), paste), tz='GMT') %in% 
    seq.POSIXt(x, y, by='sec')), 
  start, end))
#                 start                 end night.shift
# 1 2018-09-19 23:30:00 2018-09-20 07:05:00        TRUE
# 2 2018-09-19 17:00:00 2018-09-19 21:00:00       FALSE
# 3 2018-09-22 04:30:00 2018-09-22 12:00:00        TRUE

或者,data.table 包中的 %inrange% 几乎快两倍。

library(data.table)

transform(df, night.shift=mapply(\(x, y) any(
  as.POSIXct(outer(as.Date(c(x, y)), c('01:00:00', '05:59:59'), paste), tz='GMT') %inrange% 
    c(x, y)), 
  start, end))
#                 start                 end night.shift
# 1 2018-09-19 23:30:00 2018-09-20 07:05:00        TRUE
# 2 2018-09-19 17:00:00 2018-09-19 21:00:00       FALSE
# 3 2018-09-22 04:30:00 2018-09-22 12:00:00        TRUE