Другая возможность:
library(data.table)
library(lubridate)
setDT(df)
df[ , ceil_start := ceiling_date(start, "hour", change_on_boundary = TRUE)]
df[ , {
if(difftime(ceil_start, start, units = "min") > dur) {
.SD[ , .(start, dur)]
} else {
end <- start + dur * 60
time <- c(start,
seq(from = ceil_start,
to = floor_date(end, "hour"),
by = "hour"),
end)
.(start = head(time, -1), dur = `units<-`(diff(time), "mins"))
}
},
by = id]
# id start dur
# 1: 1 2018-01-01 12:15:31 44.48333 mins
# 2: 1 2018-01-01 13:00:00 60.00000 mins
# 3: 1 2018-01-01 14:00:00 60.00000 mins
# 4: 1 2018-01-01 15:00:00 60.00000 mins
# 5: 1 2018-01-01 16:00:00 60.00000 mins
# 6: 1 2018-01-01 17:00:00 30.51667 mins
# 7: 2 2018-01-02 23:43:00 17.00000 mins
# 8: 2 2018-01-03 00:00:00 60.00000 mins
# 9: 2 2018-01-03 01:00:00 43.00000 mins
# 10: 3 2018-01-03 11:00:11 45.00000 mins
# 11: 4 2018-01-03 11:35:00 25.00000 mins
# 12: 4 2018-01-03 12:00:00 10.00000 mins
# 13: 5 2018-01-03 00:00:00 60.00000 mins
# 14: 5 2018-01-03 01:00:00 0.00000 mins
Объяснение
Преобразование data.frame
в data.table
(setDT
).Округлить время начала до ближайшего часа (ceiling_date(start, "hour", ...)
. Используйте change_on_boundary = TRUE
для упрощения обработки времени без минут и секунд (не в данных, но проверено).
Для обработки случаев, когда время окончания (начало+ длительность) совпадает с временем начала (например, id = 3), проверьте, больше ли разница между округленным временем и временем начала, чем длительностью (if(difftime(ceil_start, start, units = "min") > dur))
). Если это так, просто выберите столбцы начала и продолжительности (.SD[ , .(start, dur)
).
Для других случаев (else
) рассчитайте время окончания: end <- start + dur * 60
. Создайте последовательность от времени начала с округлением в большую сторону ('ceil_start') до времени окончания, округленного в меньшую сторону, с часовым приращением (seq(from = ceil_start, to = floor_date(end, "hour"), by = "hour")
). Объединить со временем начала и окончания. Вернуть все времена, кроме последнего (head(time, -1)
и вычислить разницу между временными шагами в минутах (`units<-`(diff(time), "mins")
).
Для времен с H: M: S = 00:00:00 и длительностью, кратной 60 мин, например, id = 5, текущее решение выдает строку с длительностью 0 минут за последний час.более элегантное решение, быстрый и грязный способНеобходимо удалить такие строки с длительностью = 0.
Данные
Обратите внимание, что я добавил случай, не включенный в исходные данные, id = 4 (см. также myкомментарий выше ) и id = 5.
df <- data.frame(id = 1:5,
start = as.POSIXct(c("2018-01-01 12:15:31",
"2018-01-02 23:43:00",
"2018-01-03 11:00:11",
"2018-01-03 11:35:00",
"2018-01-03 00:00:00")),
dur = c(315, 120, 45, 35, 60))