润滑-查找间隔和日期之间的重叠时间

use*_*rLL 6 r lubridate

我有一个以日期时间格式转换开始和结束的数据框,如下所示:

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")
)
Run Code Online (Sandbox Code Playgroud)

我还有另一个带有公众假期日期的数据框,如下所示:

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

我想用新的列更新shift_time df,以记录在公共假日发生的轮班小时数-即,我要计算轮班间隔与适用的任何公共假日之间的重叠(以小时为单位)。在上面的示例中,新变量的期望值为0、0、4。

有没有办法做到这一点而无需创建很多新变量(例如,difftimes,interval,匹配日期)?

Bri*_*ian 7

有内置功能,lubridate::int_overlaps但是仅返回逻辑值,而不是它们重叠的时间。幸运的是,该intersection函数具有用于Interval对象的方法。唯一的窍门是,如果没有重叠,则返回length- NA,而不是length- 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)
}
Run Code Online (Sandbox Code Playgroud)

这将构造作为重叠的间隔,然后提取其长度(以秒为单位)。如果为NA,则将其更改为零,然后返回。as.duration只是给我们漂亮的印刷。现在,您只需要给它两个间隔:

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

int_overlaps_numeric(int1, int2)
Run Code Online (Sandbox Code Playgroud)
"1.99299597740173s"
Run Code Online (Sandbox Code Playgroud)

因此,您需要将所有假期划分为间隔,并将所有班次划分为间隔。大概您想将这些重叠与数据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()
}
Run Code Online (Sandbox Code Playgroud)

此函数采用两个间隔向量。对于第一个向量的每个元素,它计算与第二个向量的每个元素的重叠,然后将它们相加。然后将其从列表转换回向量,并将其重新分类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)
  )
Run Code Online (Sandbox Code Playgroud)
                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)
Run Code Online (Sandbox Code Playgroud)

如果您真的反对创建任何新的中间变量,请执行以下操作:

shift_time %>% 
  mutate(
    holiday_hours = check_shift_against_holidays(
      ymd_hms(started_at) %--% ymd_hms(ended_at), 
      holiday_intervals
      )
  )
Run Code Online (Sandbox Code Playgroud)
                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)
Run Code Online (Sandbox Code Playgroud)

  • @Brian,现在您可以使用 `lubridate::as.duration(lubridate::intersect(interval_1, Interval_2))` 而不是创建 `int_overlaps_numeric()` 函数。 (3认同)