Преобразование времени начала и общей продолжительности в истекшее время в час - PullRequest
0 голосов
/ 12 февраля 2019

У меня есть данные о времени начала ('startTime', переменная даты-времени, POSIXct) и длительности в минутах ('duration_minutes'):

df <- data.frame(id = c(1, 2, 3),
                 startTime = as.POSIXct(c("2018-01-01 12:15:31",
                                          "2018-01-02 23:43:00",
                                          "2018-01-03 11:00:11")), 
                 duration_minutes = c(315, 120, 45))

Я хочу преобразовать время началаи длительность до истекшего времени в час, для каждого часа, от часа времени начала до последнего часа в конце продолжительности:

df_result <- data.frame(id = c(1, 1, 1, 1, 1, 1, 2, 2, 2, 3),
                        startTime = c("2018-01-01 12:15:31","2018-01-01 13:00:00",
                                "2018-01-01 14:00:00","2018-01-01 15:00:00",
                                "2018-01-01 16:00:00","2018-01-01 17:00:00",

                                "2018-01-02 23:43:00","2018-01-03 00:00:00",
                                "2018-01-03 01:00:00",

                                "2018-01-03 11:00:11"),
                        duration_minutes = c(44.48, 60, 60, 60, 60, 30.5, 17, 60, 43, 45))

Пожалуйста, посоветуйте возможное решение.

Ответы [ 2 ]

0 голосов
/ 13 февраля 2019

Другая возможность:

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))
0 голосов
/ 13 февраля 2019

Попробуйте это:

library(data.table)
library(lubridate)
library(magrittr)

df <-
  setDT(df)[, start_ceiling := ceiling_date(startTime, "hour", change_on_boundary = TRUE)] %>%
  .[, `:=` (
    reps = ifelse(
      startTime + (duration_minutes * 60) <= start_ceiling, 1, pmax(2, floor(duration_minutes / 60) + 1)
    ),
    initial_diff = as.numeric(difftime(start_ceiling[1], startTime[1], units = "mins"))
  ), by = id] %>%
  .[, df[df[, rep(.I, reps)]]] %>%
  .[, startTime := pmax(startTime, floor_date(startTime, "hour") + hours(0:(.N - 1))), by = id] %>%
  .[reps > 1, duration_minutes := c(initial_diff[.N], 
                                    rep(60, reps[.N] - 2),
                                    (duration_minutes[.N] - initial_diff[.N]) %% 60), by = id] %>%
  .[!(duration_minutes == 0 & reps > 1), ] %>%
  .[, c("reps", "start_ceiling", "initial_diff") := NULL]

Я проверил это со всеми собранными нами сценариями, и вот результат:

    id           startTime duration_minutes
 1:  1 2018-01-01 12:15:31         44.48333
 2:  1 2018-01-01 13:00:00         60.00000
 3:  1 2018-01-01 14:00:00         60.00000
 4:  1 2018-01-01 15:00:00         60.00000
 5:  1 2018-01-01 16:00:00         60.00000
 6:  1 2018-01-01 17:00:00         30.51667
 7:  2 2018-01-02 23:43:00         17.00000
 8:  2 2018-01-03 00:00:00         60.00000
 9:  2 2018-01-03 01:00:00         43.00000
10:  3 2018-01-03 11:00:11         45.00000
11:  4 2018-01-04 10:00:00         60.00000
12:  4 2018-01-04 11:00:00          5.00000
13:  5 2018-01-05 00:00:00         60.00000
14:  6 2018-01-06 11:35:00         25.00000
15:  6 2018-01-06 12:00:00         10.00000
16:  7 2018-01-07 00:00:00         60.00000
17:  7 2018-01-07 01:00:00         60.00000

Используемые данные:

df <- data.frame(
  id = c(1, 2, 3, 4, 5, 6, 7),
  startTime = as.POSIXct(
    c(
      "2018-01-01 12:15:31",
      "2018-01-02 23:43:00",
      "2018-01-03 11:00:11",
      "2018-01-04 10:00:00",
      "2018-01-05 00:00:00",
      "2018-01-06 11:35:00",
      "2018-01-07 00:00:00"
    )
  ),
  duration_minutes = c(315, 120, 45, 65, 60, 35, 120)
)

df

  id           startTime duration_minutes
1  1 2018-01-01 12:15:31              315
2  2 2018-01-02 23:43:00              120
3  3 2018-01-03 11:00:11               45
4  4 2018-01-04 10:00:00               65
5  5 2018-01-05 00:00:00               60
6  6 2018-01-06 11:35:00               35
7  7 2018-01-07 00:00:00              120
...