web-dev-qa-db-fra.com

Comment calculer le décalage horaire avec la ligne précédente d'un data.frame par groupe

Le problème que j'essaye de résoudre est que j'ai une trame de données avec une variable POSIXct triée dedans. Chaque ligne est catégorisée et je veux obtenir les différences de temps entre chaque ligne pour chaque niveau et ajouter ces données dans une nouvelle variable. Le problème reproductible est le suivant. La fonction ci-dessous est juste pour créer des échantillons de données avec des temps aléatoires aux fins de cette question.

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)
}

Le code pour simuler le problème est le suivant:

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)

La trame de données résultante attendue est la suivante:

>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

Chaque sous-groupe dans la sortie aura la première ligne avec la valeur timediff de 0 car il n'y a pas de ligne précédente. J'ai pu regrouper par catégorie et appeler la fonction suivante pour calculer les différences, mais je n'ai pas pu obtenir la sortie finale pour tous les groupes de catégories.

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"]
    }
  }
}

J'y suis depuis deux jours sans chance, donc j'apprécierais grandement toute aide. Merci.

18
Mntester

Essaye ça:

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

Vous pouvez également ajouter replace(is.na(.), 0) à la chaîne.

18
JasonAizkalns

Dans la base R, vous pouvez utiliser:

# 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)))))

qui donne:

> 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

Si vous voulez des minutes ou des heures, vous pouvez utiliser "mins" ou "hours" au lieu de "secs".


Une alternative avec le data.table paquet:

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]

ce qui se traduit par:

> 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
17
Jaap