给定起始日期时间和结束日期时间,为每个跨越的12小时轮班生成行索引
我有一个包含唯一起始和结束日期时间的数据框:
x <- data.frame(id = c("A", "B"),
start = c("2026-02-01 15:30:00", "2026-02-05 19:00:00"),
end = c("2026-02-02 09:30:00", "2026-02-06 20:00:00"))
而白班的工作时段是从早上6点到下午6点(06:00:00 - 17:59:59),夜班则从晚上6点到次日早上6点(18:00:00 - 05:59:59)。我如何把这张表纵向扩展,以便为每一个跨越的班次生成一行?我想要一个表格,新增一列,列出被覆盖的每个班次的实际开始时间:
y <- data.frame(id = c("A", "A", "A", "B", "B", "B"),
shift_start = c("2026-02-01 15:30:00", "2026-02-01 18:00:00",
"2026-02-02 06:00:00", "2026-02-05 19:00:00",
"2026-02-06 06:00:00", "2026-02-06 18:00:00"))
使用 seq() 时,我可以按设定的时长(例如12小时)进行迭代,但当每个ID的起始时间不同,而班次时间(例如6AM、6PM)固定时,该如何实现?
解决方案
也许还有更简洁的做法,但有一种实现方式可以使用 lubridate 和 dplyr,来自 tidyverse 构造每个较长班次内的班次变更序列。
library(tidyverse)
# helper function to find shift start from input time
shift_back <- \(time) {floor_date(time - hours(6), "12 hours") + hours(6)}
x |>
mutate(across(start:end, ymd_hms)) |>
reframe(shift_began = seq.POSIXt(
shift_back(start), shift_back(end + hours(11) + minutes(59), "12 hours"),
.by = c(id, start, end)) |>
mutate(start_adj = pmax(start, shift_began),
end_adj = pmin(end, shift_began + hours(12))) |>
select(-shift_began) |>
filter(start_adj < end_adj)
结果为:
id start end start_adj end_adj
1 A 2026-02-01 15:30:00 2026-02-02 09:30:00 2026-02-01 15:30:00 2026-02-01 18:00:00
2 A 2026-02-01 15:30:00 2026-02-02 09:30:00 2026-02-01 18:00:00 2026-02-02 06:00:00
3 A 2026-02-01 15:30:00 2026-02-02 09:30:00 2026-02-02 06:00:00 2026-02-02 09:30:00
4 B 2026-02-05 19:00:00 2026-02-06 20:00:00 2026-02-05 19:00:00 2026-02-06 06:00:00
5 B 2026-02-05 19:00:00 2026-02-06 20:00:00 2026-02-06 06:00:00 2026-02-06 18:00:00
6 B 2026-02-05 19:00:00 2026-02-06 20:00:00 2026-02-06 18:00:00 2026-02-06 20:00:00
我们可以把这个输入ggplot2,以获得可视化的确认:
... |>
ggplot() +
ggarrow::geom_arrow_segment(
aes(start_adj, xend = end_adj, y = id, yend = id)) +
scale_x_datetime(breaks = seq.POSIXt(
ymd_h(2026010106), ymd_h(2026123118), "12 hours"),
date_labels = "%m/%d\n%I%p")
我认为 @Tobo的 join方法在大多数情况下更优雅,也更有意义。(也就是说,我也能想象某些离奇的边缘情况会让它变得笨拙,比如追踪跨越100年事件的每分钟区间——那时你可能要创建5000万个桶,而让它们按需即时生成可能更简单。)
下面给出该思路在dplyr中的一个实现。注意,overlaps() 这个用于非等值连接的辅助工具在这里非常有用,因为它能在一步中把所有可能重叠的班次范围拉取进来。我在这里指定 bounds = (),以确保恰好在班次变更点开始或结束的班次不会把相邻的班次也算进来。
x$start <- as.POSIXct(x$start)
x$end <- as.POSIXct(x$end)
shift_start <- seq(
from = as.POSIXct(paste(as.Date(min(x$start)-1), "06:00:00")),
to = as.POSIXct(paste(as.Date(max(x$end)+1), "18:00:00")),
by = "12 hours"
)
x |>
left_join(data.frame(shift_start, shift_end = shift_start + hours(12)),
join_by(overlaps(start, end, shift_start, shift_end,
bounds = "()")))
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。
