Как рассчитать разницу во времени с предыдущей строкой data.frame по группе

Проблема, которую я пытаюсь решить, заключается в том, что у меня есть фрейм данных с отсортированной переменной POSIXct в нем. Каждая строка разделена на категории, и я хочу получить разницу во времени между каждой строкой для каждого уровня и добавить эти данные обратно в новую переменную. Воспроизводимая проблема приведена ниже. Приведенная ниже функция предназначена только для создания выборочных данных со случайным временем для цели этого вопроса.

random.time <- function(N, start, end) {
  st <- as.POSIXct(start)
  en <- as.POSIXct(end)
  dt <- as.numeric(difftime(en, st, unit="sec"))
  ev <- sort(runif(N, 0, dt))
  rt <- st + ev
  return(rt)
}

Код для моделирования проблемы выглядит следующим образом:

set.seed(123)
category <- sample(LETTERS[1:5], 20, replace=TRUE)
randtime <- random.time(20, '2015/06/01 08:00:00', '2015/06/01 18:00:00')
df <- data.frame(category, randtime)

Ожидаемый результирующий фрейм данных выглядит следующим образом:

>category randtime timediff (secs)
>A  2015-06-01 09:05:00 0
>A  2015-06-01 09:06:30 90
>A  2015-06-01 09:10:00 210
>B  2015-06-01 10:18:58 0
>B  2015-06-01 10:19:58 60
>C  2015-06-01 08:14:00 0
>C  2015-06-01 08:16:30 150

Каждая подгруппа в выводе будет иметь первую строку со значением timediff, равным 0, поскольку предыдущей строки нет. Я смог сгруппировать по категориям и вызвать следующую функцию для вычисления различий, но не смог заставить ее сопоставить окончательный результат для всех групп категорий.

getTimeDiff <- function(x) {
  no_rows <- nrow(x)
  if(no_rows > 1) {
    for(i in 2:no_rows) {
      t <- x[i, "randtime"] - x[i-1, "randtime"]
    }
  }
}

Я занимаюсь этим уже два дня, и мне не повезло, поэтому буду очень признателен за любую помощь. Спасибо.


person Mntester    schedule 07.10.2015    source источник


Ответы (2)


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

library(dplyr)
df %>%
  arrange(category, randtime) %>%
  group_by(category) %>%
  mutate(diff = randtime - lag(randtime),
         diff_secs = as.numeric(diff, units = 'secs'))

#   category            randtime             diff   diff_secs
#     (fctr)              (time)           (dfft)       (dbl)
# 1        A 2015-06-01 11:10:54         NA hours          NA
# 2        A 2015-06-01 15:35:04   4.402785 hours   15850.027
# 3        A 2015-06-01 17:01:22   1.438395 hours    5178.222
# 4        B 2015-06-01 08:14:46         NA hours          NA
# 5        B 2015-06-01 16:53:43 518.955379 hours 1868239.364
# 6        B 2015-06-01 17:37:48  44.090950 hours  158727.420

Вы также можете добавить replace(is.na(.), 0) в цепочку.

person JasonAizkalns    schedule 07.10.2015
comment
Спасибо за быстрый ответ. Частично это решает проблему, так как при необходимости можно организовать вывод. Однако lag () возвращает время предыдущей строки как значение timediff, а не фактическую разницу. - person Mntester; 07.10.2015

В базе R вы можете использовать:

# creating an ordered data.frame
df <- data.frame(category, randtime)
df <- df[order(df$category, df$randtime),]

# calculating the timedifference
# option 1:
df$tdiff <- unlist(tapply(df$randtime, INDEX = df$category,
                          FUN = function(x) c(0, `units<-`(diff(x), "secs"))))
# option 2:
df$tdiff <- unlist(tapply(df$randtime, INDEX = df$category,
                          FUN = function(x) c(0, diff(as.numeric(x)))))

который дает:

> df
   category            randtime      tdiff
6         A 2015-06-01 11:10:54     0.0000
15        A 2015-06-01 15:35:04 15850.0271
18        A 2015-06-01 17:01:22  5178.2223
1         B 2015-06-01 08:14:46     0.0000
17        B 2015-06-01 16:53:43 31137.3227
19        B 2015-06-01 17:37:48  2645.4570
3         C 2015-06-01 10:09:50     0.0000
7         C 2015-06-01 12:46:40  9409.9693
9         C 2015-06-01 13:56:29  4188.4578
10        C 2015-06-01 14:24:18  1669.1326
12        C 2015-06-01 14:54:25  1807.1447
14        C 2015-06-01 15:05:07   641.7068
2         D 2015-06-01 09:28:16     0.0000
13        D 2015-06-01 14:55:40 19644.8313
4         E 2015-06-01 10:18:58     0.0000
5         E 2015-06-01 10:53:29  2071.2223
8         E 2015-06-01 13:26:26  9176.6263
11        E 2015-06-01 14:33:25  4019.0319
16        E 2015-06-01 15:57:16  5031.4183
20        E 2015-06-01 17:56:33  7156.8849

Если вам нужны минуты или часы, вы можете использовать "mins" или "hours" вместо "secs".


Альтернатива с пакетом data.table:

library(data.table)
# creating an ordered/keyed data.table
dt <- data.table(category, randtime, key = c("category", "randtime"))

# calculating the timedifference
# option 1:
dt[, tdiff := difftime(randtime, shift(randtime, fill=randtime[1L]), units="secs"), by=category]
# option 2:
dt[, tdiff := c(0, `units<-`(diff(randtime), "secs")), by = category]
# option 3:
dt[ , test := c(0, diff(as.numeric(randtime))), category]

что приводит к:

> dt
    category            randtime           tdiff
 1:        A 2015-06-01 11:10:54     0.0000 secs
 2:        A 2015-06-01 15:35:04 15850.0271 secs
 3:        A 2015-06-01 17:01:22  5178.2223 secs
 4:        B 2015-06-01 08:14:46     0.0000 secs
 5:        B 2015-06-01 16:53:43 31137.3227 secs
 6:        B 2015-06-01 17:37:48  2645.4570 secs
 7:        C 2015-06-01 10:09:50     0.0000 secs
 8:        C 2015-06-01 12:46:40  9409.9693 secs
 9:        C 2015-06-01 13:56:29  4188.4578 secs
10:        C 2015-06-01 14:24:18  1669.1326 secs
11:        C 2015-06-01 14:54:25  1807.1447 secs
12:        C 2015-06-01 15:05:07   641.7068 secs
13:        D 2015-06-01 09:28:16     0.0000 secs
14:        D 2015-06-01 14:55:40 19644.8313 secs
15:        E 2015-06-01 10:18:58     0.0000 secs
16:        E 2015-06-01 10:53:29  2071.2223 secs
17:        E 2015-06-01 13:26:26  9176.6263 secs
18:        E 2015-06-01 14:33:25  4019.0319 secs
19:        E 2015-06-01 15:57:16  5031.4183 secs
20:        E 2015-06-01 17:56:33  7156.8849 secs
person Jaap    schedule 07.10.2015
comment
Да, оба этих решения отлично работают. Большое спасибо всем вам. - person Mntester; 07.10.2015
comment
@Mntester расширил ответ базовым решением R - person Jaap; 14.01.2016
comment
Хотел бы я получить более одного голоса за, такое красивое, чистое data.table решение! Обратите внимание: используйте first_removed <- dt[dt[, -.I[1], by = category]$V1], чтобы при необходимости удалить первую строку для каждой категории из итоговой таблицы data.table (в моем случае я использую разницу как функцию, поэтому мне не нужны нули). - person Bar; 23.07.2016
comment
@Bar Другой вариант удаления первого наблюдения по группе - dt[, tail(.SD, -1), by = category], но для очень больших наборов данных метод .I выигрывает по производительности (см. Здесь для контрольный показатель). - person Jaap; 23.07.2016
comment
Мне это легче понять dt[ , test:=c(0, diff(as.numeric(randtime))), category]. Не знаю, как кто-то мог придумать этот <-units() трюк. Я впечатлен, но грустно, что вам пришлось прибегнуть к этому. - person geneorama; 16.06.2018
comment
@geneorama Согласен с этим. Я соответствующим образом обновил свой ответ. - person Jaap; 16.06.2018
comment
@Jaap Большое спасибо Jaap, как мы могли бы это использовать, если у нас есть еще одна биномиальная переменная для by для обработки (вместе с категорией) - это статус переключения для столбца времени, и нам нужно будет суммировать общее время с помощью этого переключателя только столбец - сначала различаются последовательные переключатели, а затем складываются кумулятивно. - person Keyshov Borate; 29.06.2018
comment
@KeyshovBorate Я не совсем понимаю, что вы имеете в виду. Может, разместите как новый вопрос? Вы можете вернуться к этому. Также не забудьте включить воспроизводимый пример - person Jaap; 30.06.2018
comment
@Jaap Конечно, вот оно, спасибо! stackoverflow.com/questions/51118608/ - person Keyshov Borate; 01.07.2018