Voilà déjà 2-3 ans que je recommande à qui veut m'entendre le package lubridate pour travailler avec des dates. En effet, travailler avec des dates, à la base, ce n'est pas évident. Des opérations a priori simples, comme ordonner les vecteurs, calculer des différences, arrondir des valeurs, peuvent devenir compliquées dès que le vecteur en question est de classe date. Cela avait d'ailleurs fait l'objet de mon tout premier billet de blog (émotion! nostalgie!) que j'avais rédigé du temps où je ne connaissais pas encore lubridate -ni ggplot2, du reste-. Autant vous dire qu'à cette époque, travailler avec des dates, c'était carrément la louse.

Heureusement, le progrès ayant parfois du bon, le tidyverse et lubridate sont passés par là: maintenant, travailler avec des dates, c'est fingers in the nose. D'autant que RStudio a publié récemment une cheatsheet lubridate, qui, comme toujours, est très bien construite et illustrée.

Allez hop, commençons donc par charger le package:

library(lubridate)

Transformer une chaîne de caractères en date

Avant toute autre chose, les fonctions du package lubridate facilitent de manière déconcertante la transformation d'un vecteur de classe chaîne de caractères en un vecteur de classe date.

Pour ce faire, il suffit d'indiquer quels sont les éléments renseignés dans la chaîne de caractères (et dans quel ordre). C'est le nom de la fonction qui remplit cet office.

Par exemple, je veux faire comprendre à R les éléments qui composent la chaîne de caractères "11 avril 2019": je vais donc lui indiquer que les éléments sont le jour (d comme day) le mois (m comme month) et l'année (y comme year):

"11 avril 2019"

## [1] "11 avril 2019"

class("11 avril 2019")

## [1] "character"

jourJ <- dmy("11 avril 2019")
class(jourJ)

## [1] "Date"

jourJ

## [1] "2019-04-11"

Vous avez vu? la transformation s'est faite sans problème (et à partir d'une date en français, s'il-vous-plaît!). Par ailleurs, la fonction dmy aurait très bien pu s'accomoder de mises en formes différentes (par exemple "11th of April, 2019" ou "11/04/2019"), pour aboutir au même résultat.

Sur ce même principe, le package lubridate comprend un grand nombre de fonctions capables de prendre en entrée dates et heures, selon des mises en formes variées. Note pour la suite: quand on a des vecteurs qui regroupent à la fois la date et l'heure (comme "11/04/2019 14h37" par exemple) on parle de "date-time".

Quelques exemples:

ymd("2019/04_11")

## [1] "2019-04-11"

ymd_hm("2019.04.11 14h37")

## [1] "2019-04-11 14:37:00 UTC"

ymd_hms("20190407143752")

## [1] "2019-04-07 14:37:52 UTC"

hms("14h37min52s")

## [1] "14H 37M 52S"

Récupérer les éléments des dates

Maintenant que l'on sait faire comprendre à R de quelle manière interpréter une chaîne de caractères en tant que date-time, on va pouvoir réaliser quelques opérations simples.

Pour commencer, on peut essayer d'isoler un des éléments de la date-time (juste l'année, ou juste le mois, ou juste l'heure, etc.).

Là encore, c'est à travers le nom de la fonction utilisée que l'on va spécifier quel est l'élément qui nous intéresse.

t <- ymd_hms("2019.04.11 14h37min52s")
date(t)

## [1] "2019-04-11"

hour(t)

## [1] 14

minute(t)

## [1] 37

second(t)

## [1] 52

Arrondir

On peut également arrondir une date, vers le haut (ceiling_date()), vers le bas (floor_date()), ou vers le plus proche (round_date()):

t <- ymd_hms("2019.04.11 14h37min52s")
ceiling_date(t,"hour")

## [1] "2019-04-11 15:00:00 UTC"

floor_date(t,"hour")

## [1] "2019-04-11 14:00:00 UTC"

round_date(t,"hour")

## [1] "2019-04-11 15:00:00 UTC"

Evidemment, on peut choisir à quelle unité se fait cet arrondi:

t <- ymd_hms("2019.04.11 14h37min52s")
round_date(t,"minute")

## [1] "2019-04-11 14:38:00 UTC"

round_date(t,"hour")

## [1] "2019-04-11 15:00:00 UTC"

round_date(t,"day")

## [1] "2019-04-12 UTC"

round_date(t,"month")

## [1] "2019-04-01 UTC"

round_date(t,"year")

## [1] "2019-01-01 UTC"

Périodes ou durées

t1 <- dmy("17/07/2018")
t2 <- dmy("17/04/2019")
diff <- t2-t1

L'objet diff nous renseigne sur la "différence de temps" entre t1 et t2. Il s'agit d'un objet de classe difftime (classe qui n'est pas spécifiquement liée à l'usage de lubridate).

Cette "différence de temps" peut être traitée de différentes manières par lubridate. On peut en effet considérer cette différence en terme de période ou en terme de durée.

On le spécifie de la manière suivante:

as.duration(diff)

## [1] "23673600s (~39.14 weeks)"

as.period(diff)

## [1] "274d 0H 0M 0S"

Bon, à ce stade, à part en terme d'affichage, on ne voit pas forcément bien la nuance entre les deux... L'idée, c'est que la durée correspond plutôt à une différence de temps "physique" (c'est à dire le nombre exact de secondes correspondant à un intervalle de temps) tandis que la période correspond à une différence de temps "sociale".

Par exemple, quand Patrick Bruel dit "On s'était dit rendez-vous dans 10 ans, même jour même heure, même pomme", on peut supposer qu'il parle d'un écoulement "social" du temps.

Ainsi, partant d'une prise de rendez-vous le 4/02/1991, on arriverait à un prochain rendez-vous le:

t0=dmy_h("4/02/1991 21h")
t0+years(10)

## [1] "2001-02-04 21:00:00 UTC"

soit, également, un 4/02. Alors que si on avait voulu que 10 ans stricto sensu se soient écoulés, cela aurait abouti à un rendez-vous quelques jours plus tôt, du fait des années bissextiles ayant eu lieu dans l'intervalle:

t0+dyears(10)

## [1] "2001-02-01 21:00:00 UTC"

Calculs arithmétiques avec des périodes ou durées

Dans la partie précédente, nous avons déjà vu qu'il était possible de réaliser des opérations arithmétiques sur des dates. Quelle que soit l'opération réalisée, il faut bien garder en tête la distinction entre période et durée! Les périodes correspondent au fonctions xxx() (par exemple days() ou months()) tandis que les durées correspondent aux fonctions dxxx() (par exemple ddays() ou dyears())

t1+months(9) # t1 + 9 mois

## [1] "2019-04-17"

t1+ddays(268) # t1 + exactement 268 jours

## [1] "2019-04-11"

ddays(268)/dweeks(1) # combien de semaines (exactement) pour 268 jours?

## [1] 38.28571

t2-dweeks(3) # t2 - (exactement) 3 semaines

## [1] "2019-03-27"

Notez que ces fonctions vous permettent également de créer des séries à intervalle de temps régulier:

t1+months(1:9)

## [1] "2018-08-17" "2018-09-17" "2018-10-17" "2018-11-17" "2018-12-17" "2019-01-17" "2019-02-17" "2019-03-17" "2019-04-17"

now()+minutes(seq(0,30,by=10))

## [1] "2018-09-10 11:29:33 CEST" "2018-09-10 11:39:33 CEST" "2018-09-10 11:49:33 CEST" "2018-09-10 11:59:33 CEST"

Intervalles de temps

Une autre manière d'envisager l'analyse d'un jeu de données comprenant des dates est de travailler sur des intervalles de temps.

Pour transformer deux dates en intervalle de temps avec lubridate, on a deux solutions (la fonction interval(), ou l'opérateur %--%. Dans les deux cas, on obtient le même résultat:

itv <- interval(t1,t2)
itv <- t1 %--% t2

itv

## [1] 2018-07-17 UTC--2019-04-17 UTC

Disposer d'un intervalle, cela permet de réaliser certaines opérations, comme (par exemple) déterminer si une date (ou une "date-time") donnée fait partie de l'intervalle:

Noel <- dmy("25/12/2018")
Noel %within% itv

## [1] TRUE

sitv=int_diff(t1+months(1:9))

Une des opérations à mon sens les plus utiles en lien avec ces intervalles, c'est ainsi de permettre de replacer l'occurrence d'un ou plusieurs événements dans des intervalles de temps:

sitv

## [1] 2018-08-17 UTC--2018-09-17 UTC 2018-09-17 UTC--2018-10-17 UTC 2018-10-17 UTC--2018-11-17 UTC 2018-11-17 UTC--2018-12-17 UTC 2018-12-17 UTC--2019-01-17 UTC 2019-01-17 UTC--2019-02-17 UTC 2019-02-17 UTC--2019-03-17 UTC 2019-03-17 UTC--2019-04-17 UTC

Noel %within% sitv 

## [1] FALSE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE

Conclusion

Je suis loin d'avoir couvert toutes les possibilités de lubridate (par exemple je n'ai rien dit sur les fuseaux horaires, sur les formats date et time qui correspondent à un nombre de jours), mais j'espère néanmoins vous avoir convaincu (si le besoin s'en faisait ressentir) qu'on ne saurait s'en passer pour travailler sur des données temporelles!