« À la bonne heure »… avec R !

Tags : Actualités

Un jour où l’autre, vous aurez à gérer des dates avec R. Et ça, que vous soyez analyste débutant, statisticien de haut vol ou data scientist chevronné. Alors, autant s’y coller dès aujourd’hui, vous ne croyez pas ?

Et pour plonger dans la complexité des formats de date, rien de mieux qu’un dataset trouvé dans la nature… Alors, importons le jeu de données que nous allons utiliser en exemple : celui de la Fête de la Science 2017, disponible sur Open Agenda.

library(tidyverse)
date <- read_csv2("https://openagenda.com/agendas/26805973/events.csv")

Bien, jetons un oeil un instant à toutes les colonnes de dates que nous avons ici.

date <- data%>%
  select(`Dernière mise à jour`,`Résumé horaires - FR`, `Horaires détaillés - FR`,
`Horaires ISO`, `Première date`, `Première ouverture`, `Première fermeture`)

as.data.frame(date)[1,]
      Dernière mise à jour Résumé horaires - FR
1 2017-05-23T13:20:15.000Z     2 octobre, 10h00
          Horaires détaillés - FR
1 lundi 2 octobre - 10h00 à 16h00
                                         Horaires ISO Première date
1 2017-10-02T10:00:00+02:00-2017-10-02T16:00:00+02:00    2017-10-02
  Première ouverture Première fermeture
1           10:00:00           16:00:00

Bon, ça commence bien… nous voilà avec pas moins de 6 formats différents de dates : « 2017-05-23T13:20:15.000Z », « 2 octobre, 10h00 », « lundi 2 octobre – 10h00 à 16h00 », « 2017-10-02T10:00:00+02:00-2017-10-02T16:00:00+02:00 », « 2017-10-02 », et « 10:00:00 ». Et c’est sans compter sur les déclinaisons anglaises. Bref, un petit casse-tête à prendre en main… car oui, malgré la norme ISO8601, donnant « AAAA-MM-JJ » comme format international de référence, tout le monde ne s’est pas mis à la page.

En R, il existe trois formats de date : Date, que vous obtenez avec Sys.Date(). POSIXct, renvoyé par Sys.time(), et POSIXlt. La différence entre ces deux derniers ? POSIXct est un format qui compte les secondes depuis l’heure Unix, soit le 1er janvier 1970 à 00:00:00. De son côté, POSIXlt contient « seulement » une liste de dates, sous forme de caractères. À noter, également, que le format Date n’est pas précis à la seconde, mais à la journée… comme un date, au final 🙂

En {base}

Commençons donc par le commencement : {base}. Comment convertir tous ces éléments avec R base ? Pour formater une chaîne de caractères en date, vous aurez besoin de la fonction format()… et d’une bonne mémoire. Les arguments de format() sont :

  • Une chaîne de caractères contenant la date
  • Une chaîne décrivant sous forme d’expression régulière le format de la date

Et c’est là qu’il va vous falloir une bonne mémoire : chaque élément de votre date se décrit à l’aide d’un caractère précédé de %. Par exemple, un jour en chiffres se code %d, un jour en toutes lettres %A, un mois en chiffre %m, en lettre %B, et ainsi de suite — pour connaître toutes les abréviations, direction <a href="https://stat.ethz.ch/R-manual/R-devel/library/base/html/strptime.html" target="_blank" rel="noopener noreferrer">help(strptime)</a>.

À noter que format() vous renvoie une chaîne de caractères, qu’il vous faudra passer dans as.Date() : histoire de taper un peu moins de code, faites appel à strptime(), qui convertit automatiquement une chaîne de caractères au format POSIXlt.

Avec {lubridate}

Comme toujours, Hadley Wickham vient à la rescousse avec {lubridate} ! À l’heure où nous écrivons ces lignes, ce package n’est pas inclus dans le package {tidyverse}. Pourquoi ? « Because you only need it when you’re working with dates/times », avance Hadley dans son ouvrage R for data science : il vous faudra donc le charger explicitement.

Comment marche tout ça ? Pour simplifier la gestion des dates avec R, ce package contient des fonctions adaptées à l’ordre des éléments de votre date, indépendamment du format d’écriture. « Encore plein de fonctions à apprendre par coeur !  » vous dites-vous ? Eh non, c’est là que le package a été bien pensé : chaque fonction se présente sous la forme ymd_hms, où y fait référence à l’année, m au mois, d au jour, h à l’heure, m à la minute et s à la seconde. En clair, si votre chaîne de caractères est « AN/MOIS/JOUR », voire « AN.MOIS.JOUR » ou même (soyons fou) « AN__MOIS__JOUR », vous utiliserez ymd() à chaque fois. Le petit plus ? Que votre année soit au format 2017 ou 17, que votre mois soit en 02, Feb ou February, la fonction ne bouge pas. Ainsi donc, il est facile de deviner les fonctions à la volée, en connaissant uniquement le principe de construction.

Bon, de retour à notre jeu de données. Nous allons comparer comment convertir trois formats de notre dataset, avec {base} et avec {lubridate}.

Première date

Jetons un oeil au format de date de la colonne « Dernière Modification » : « 2017-05-23T13:20:15.000Z ».

# Le fuseau horaire local
tz <- Sys.timezone()

date1 <- "2017-05-23T13:20:15.000Z"
# En {base}
strptime(date1, tz = tz, format = "%Y-%m-%dT%H:%M:%OSZ")
#Avec lubridate
ymd_hms(date1, tz = tz)

Ici, rien de bien compliqué : on transforme une date qui est déjà au format ISO. Vous pourriez d’ailleurs très bien utiliser directement as.POSIXlt("2017-05-23T13:20:15.000Z") sur cette chaine de caractères.

Deuxième format

Direction maintenant « 2 octobre, 10h00 ».

Ici, pas d’année. Nous avons donc deux solutions :

  • utiliser `strptime()` directement, et obtenir l’année présente
  • coller l’année manuellement.

Avec {lubridate}, vous devrez choisir la deuxième option — toutes les fonctions de conversions commencent par y. Et pour une bonne raison : souhaitez-vous vraiment laisser R décider à votre place de l’année à choisir ?

date2 <- "2 octobre, 10h00"
# En {base}
strptime(date2, tz = tz, format = "%d %B, %Hh%M")
# Avec lubridate
ydm_hm(paste(year(today()),date2), tz = tz, locale = "fr_FR")

Et là, discrètement, nous vous avons introduit deux fonctions {lubridate}  :today(), qui vous renvoie la date d’aujourd’hui, dans l’idée d’un Sys.date. Son équivalent Sys.time() étant now(). year(), qui extrait l’année d’un élément. À savoir qu’il existe aussi month(), week(), day(), hour(), minute() et second().

Troisième format

Au tour de « lundi 2 octobre – 10h00 à 16h00 »

Ici, au-delà d’une notion de date, nous avons une durée. Bien, avec un peu de manip, nous allons pouvoir tirer tout ça au clair, et connaître la différence temporelle entre ces deux éléments !

Note : même chose que plus haut, nous devons fournir l’année manuellement.

date3 <- "lundi 2 octobre - 10h00 à 16h00"
list_date3 <- strsplit(date3, split = "-|à")
debut <- paste(year(today()), list_date3[[1]][1], list_date3[[1]][2], sep ="")
fin <- paste(year(today()), list_date3[[1]][1], list_date3[[1]][3], sep ="")

# En {base}
debut_base <- strptime(debut, tz = tz, format = "%A %d %B   %Hh%M")
fin_base <- strptime(fin, tz = tz, format = "%A %d %B   %Hh%M")
fin_base - debut_base 
> Time difference of 6 hours

# Avec {lubridate}
debut_lubridate <- ydm_hm(debut, tz = tz)
fin_lubridate <- ydm_hm(fin, tz = tz)
fin_lubridate - debut_lubridate
> Time difference of 6 hours

Un peu plus loin avec {lubridate}

Nous vous entendons déjà poser la question : pourquoi s’être ennuyé à coder un package complet alors que la conversion se fait en {base} ? Eh bien, lecteur, parce que {lubridate} est bien plus qu’un simple convertisseur. En effet, il vous permet aussi et surtout de travailler avec des périodes, sans avoir à vous inquiéter des spécificités propres aux séries temporelles (comme les années bissextiles et autres bizarreries du temps qui sont autant de noeuds au cerveau en {base}).

En effet, le package d’Hadley Wickham vous permet de manipuler facilement des périodes et des durées. La différence entre ces deux formats ? La durée est « mathématiquement exacte », c’est-à-dire qu’un an compte 365 jours, indépendamment de si l’année est bissextiles ou non. Une période, quant-à-elle est plus fidèle au calendrier, et se construit assez simplement : years(), months(), et ainsi de suite.

debut_lubridate + hours(2)
> "2017-10-02 12:00:00 CEST"
debut_lubridate + hours(6) == fin_lubridate
> TRUE

Autre possibilité de {lubridate} : jouer avec les timezones. Pour cela, vous devrez faire appel à la fonction with_tz(), qui prendra en paramètre un élément date, et une time zone. Idéal pour synchroniser votre horloge à celle de vos collègues à l’autre bout de la planète !

horloge <- now()
horloge
> "2017-06-27 15:03:23 CEST"
with_tz(horloge, "America/New_York")
> "2017-06-27 09:03:23 EDT"

Alors, promis, vous n’avez plus plus aucune raison de ne plus être à la bonne heuRe 😉 ?


À propos de l'auteur

Colin Fay

Colin Fay

Data scientist & R Hacker


Commentaires


À lire également