Lubridate - 寻找区间和日期之间的重叠时间

9

我有一个数据框,其中包含起始时间和结束时间,格式为 datetime:

shift_time <- data.frame(
  started_at = c("2019-09-01 02:00:00 AEST", "2019-09-02 05:00:00 AEST", "2019-11-04 20:00:00 AEDT"),
  ended_at = c("2019-09-01 11:30:00 AEST", "2019-09-02 19:00:00 AEST", "2019-11-05 04:00:00 AEDT")
)

我有另一个数据框包含公共假期日期,格式如下:

public_holidays <- data.frame(
  hol_name = c('Cup Day', 'Christmas'),
  date = c("2019-11-05", "2019-12-25")
)

我希望更新shift_time数据框,并增加一个新列记录在公共假日上发生的班次小时数,即计算班次区间与适用的任何公共假日期间的重叠时间(以小时为单位)。在上述示例中,新变量的预期值将为0、0、4。
是否有一种不涉及创建大量新变量(例如difftimes、intervals、匹配日期)的方法来实现这一点?
1个回答

16

有内置的lubridate::int_overlaps函数,但它只返回一个逻辑值,而不是它们重叠的时间长度。幸运的是,intersection函数有一个针对Interval对象的方法。唯一的问题是如果没有重叠,它会返回长度为NA而不是长度为0。因此我们可以像这样封装它:

library(lubridate)

int_overlaps_numeric <- function (int1, int2) {
  stopifnot(c(is.interval(int1), is.interval(int2)))

  x <- intersect(int1, int2)@.Data
  x[is.na(x)] <- 0
  as.duration(x)
}

这个过程构建了重叠的时间间隔,然后提取其长度(以秒为单位)。如果长度是NA,则将它更改为零,并返回。as.duration只是为了美观打印。现在你只需要给它两个时间间隔即可:

int1 <- as.interval(5, Sys.time())
int2 <- as.interval(5, Sys.time()+3)

int_overlaps_numeric(int1, int2)
"1.99299597740173s"
所以你需要将所有的假期和班次都转换成区间。假设你想将这些重叠数据与 shift_time 数据帧中的其他数据关联起来,因此我们将使用 dplyr 在其中完成所有工作。但是,你需要将每个班次与所有假日向量进行比较,因此我们应该再添加另一个帮助函数(使用purrr::map)。
library(dplyr)
library(purrr)

check_shift_against_holidays <- function(shift, holidays) {
  map(shift, ~sum(int_overlaps_numeric(.x, holidays))) %>% 
    unlist() %>% 
    as.duration()
}
该函数需要接收两个区间向量参数。对于第一个向量的每个元素,它会计算与第二个向量的所有元素的重叠数量,然后相加。然后将结果从列表转换回向量,并重新分类为duration以便于美观打印。这里的注意点是,如果holidays向量中有任何重叠,那么这些小时数将被重复计算。
                               # days(1) since the holiday lasts all day
holiday_intervals <- as.interval(days(1), ymd(public_holidays$date))

shift_time %>% 
  mutate(
    shift = interval(ymd_hms(started_at), ymd_hms(ended_at)),
    holiday_hours = check_shift_against_holidays(shift, holiday_intervals)
  )
                started_at                 ended_at                                            shift     holiday_hours
1 2019-09-01 02:00:00 AEST 2019-09-01 11:30:00 AEST 2019-09-01 02:00:00 UTC--2019-09-01 11:30:00 UTC                0s
2 2019-09-02 05:00:00 AEST 2019-09-02 19:00:00 AEST 2019-09-02 05:00:00 UTC--2019-09-02 19:00:00 UTC                0s
3 2019-11-04 20:00:00 AEDT 2019-11-05 04:00:00 AEDT 2019-11-04 20:00:00 UTC--2019-11-05 04:00:00 UTC 14400s (~4 hours)

如果您真的反对创建任何新的中间变量:

shift_time %>% 
  mutate(
    holiday_hours = check_shift_against_holidays(
      ymd_hms(started_at) %--% ymd_hms(ended_at), 
      holiday_intervals
      )
  )
                started_at                 ended_at     holiday_hours
1 2019-09-01 02:00:00 AEST 2019-09-01 11:30:00 AEST                0s
2 2019-09-02 05:00:00 AEST 2019-09-02 19:00:00 AEST                0s
3 2019-11-04 20:00:00 AEDT 2019-11-05 04:00:00 AEDT 14400s (~4 hours)

抱歉,如果我早知道如何操作,我会更早地授予奖励的。 - userLL
1
很高兴能够帮到你!我实际上是在lubridate的GitHub上发布了这个问题。 - Brian
3
@Brian,现在你可以直接使用lubridate::as.duration(lubridate::intersect(interval_1, interval_2))来代替创建int_overlaps_numeric()函数。 - Ashirwad

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接