---
title: "Introduction aux données d'IdFM"
author: "Etienne Côme, Thomas Bapaume"
format: html
---

## Les données

Nous allons utiliser les données en open data mises à disposition par IdFM, l'autorité organisatrice des transports en île de france. Les différents jeux de données sont disponnibles sur le portail open data d'IdFM:

[https://data.iledefrance-mobilites.fr/pages/home/](https://data.iledefrance-mobilites.fr/pages/home/)

Nous allons en particulier nous intéresser aux données historiques sur les validations du réseau ferré francilien. Ce notebook est là pour vous aider à prendre en main ces données et discuter de certaines difficultés liées à leur traitement et nettoyage.


## Quelques librairies utiles


```{r,comment=FALSE,warning=FALSE,message=FALSE}
# import de données
library(readr)
library(tools)
# manipulation de données
library(dplyr)
library(tidyr)
# travail sur les dates
library(lubridate)
# travail sur les chaines de caractères
library(stringr)
# visualisation
library(ggplot2)
#library(ggspatial)
# données spatiales
library(sf)
```


## Téléchargement et lecture des données


Les schémas des jeux de données des validations historiques diffèrent subtilement entre 2022 et les années précédentes, et sont les suivant : 


```{r}
schema= cols(
  JOUR = col_date(format="%d/%m/%Y"),
  CODE_STIF_TRNS = col_character(),
  CODE_STIF_RES = col_character(),
  CODE_STIF_ARRET = col_character(),
  LIBELLE_ARRET = col_character(),
  ID_REFA_LDA = col_character(),
  CATEGORIE_TITRE = col_character(),
  NB_VALD = col_character()
)
schema2022= cols(
  JOUR = col_date(format="%Y-%m-%d"),
  CODE_STIF_TRNS = col_character(),
  CODE_STIF_RES = col_character(),
  CODE_STIF_ARRET = col_character(),
  LIBELLE_ARRET = col_character(),
  ID_REFA_LDA = col_character(),
  CATEGORIE_TITRE = col_character(),
  NB_VALD = col_double()
)
```




Nous avons donc pour chaque journées, pour chaque lieux d'arrêt et chaque catégories de titre, un nombre de validation. La documentation complète sur les données et la définition des variables est disponnible a cette adresse : 
[https://eu.ftp.opendatasoft.com/stif/Validations/Donnees_de_validation.pdf](https://eu.ftp.opendatasoft.com/stif/Validations/Donnees_de_validation.pdf). Les variables CODE_STIF_TRNS,RES,ARRET correspondent respectivement à une codification des transporteurs (ratp,sncf,optiles,...), des réseaux (métros,rer,train de banlieu,...) et des arrêts. La variable ID_REF_LDA correspond à un code de "zone d'arrêts". (i.e Zone monomodale basé avant tout sur une cohérence commerciale (et géographique) : connue du public sous la même appellation commerciale.)


Avant 2022, lorsqu'un comptage est inférieur à 5, il est remplacé par la chaine de caractères "moins de 5", à partir de 2022 il est simplement remplacé par la valeur 5. Les codes suivants permettent de télécharger les données et de les concaténer. Les codes effectuant les téléchargement ont été commentés.




```{r,comment=FALSE,warning=FALSE,message=FALSE,cache=TRUE}
url="https://data.iledefrance-mobilites.fr/api/explore/v2.1/catalog/datasets/histo-validations-reseau-ferre/exports/csv?lang=fr&timezone=Europe%2FBerlin&use_labels=true&csv_separator=%3B"
#download.file(url,"./data-raw/histo-validations-reseau-ferre.csv")
files = read_delim("./data-raw/histo-validations-reseau-ferre.csv",delim=";")
colnames(files)=c("year","url")

# ## DECOMENTER pour télécharger les données brutes 
# lapply(1:nrow(files), \(i){
#   download.file(files$url[i],paste0("./data-raw/",files$year[i],".zip"), mode = "wb")
#   unzip(paste0("./data-raw/",files$year[i],".zip"),exdir = "./data-raw")
# })

directories=list.dirs("./data-raw")

data.list=lapply(directories[-1],\(dir){
  files=list.files(dir,full.names = TRUE)
  cf = files[grepl("NB_FER",files)]
  if(file_ext(cf[1])=="csv"){
    cdf.list = lapply(cf,\(f){read_delim(f,delim=";",col_types = schema)})
  }else{
    cdf.list = lapply(cf,\(f){read_delim(f,delim="\t",col_types = schema)})
  }
  do.call(dplyr::bind_rows,cdf.list)
})

#url2022="https://data.iledefrance-mobilites.fr/api/explore/v2.1/catalog/datasets/validations-reseau-ferre-nombre-validations-par-jour-1er-semestre/exports/csv?lang=fr&timezone=Europe%2FBerlin&use_labels=true&csv_separator=%3B"
#download.file(url2022,"./data-raw/2022_S1_NB_FER.csv")
data2022=read_delim("./data-raw/2022_S1_NB_FER.csv",delim=";",col_types=schema2022)

data.val = do.call(dplyr::bind_rows,data.list) |>
  mutate(NB_VALD=ifelse(NB_VALD=="Moins de 5",5,NB_VALD),NB_VALD=as.numeric(NB_VALD))|>
  bind_rows(data2022)
```

A ce point d'avancement, nous disposons de données brutes homogénéisées et concaténées dans une unique data.frame.

## Choix d'une échelle d'aggregation et nettoyage


Pour travailler, nous avons fait le choix de nous placer à l'échelle de la zone d'arrêt, du jour et de ne considérer pour le moment que le volume total de validation. Nous commençons donc par faire une aggregation des données à cette échelle. Mais avant cela nous allons tout de même vérifier quelques éléments sur les données:


```{r}
table(data.val$ID_REFA_LDA) |> head()
sum(is.na(data.val$ID_REFA_LDA))
data.val |> filter(is.na(ID_REFA_LDA)) |> select(2:5) |> distinct()
sum(is.na(data.val$NB_VALD))
sum(is.na(data.val$JOUR))
```

La variable LDA présente 3 modalités problématiques "?","0" et "NA". Les deux premières correspondent à un problème de localisation et seront filtrées, la dernière correspond toujours aux données de la station Porte Dauphine et peut donc être corrigée. Les deux autres variables ne présentent pas de données manquantes ou de valeurs problématiques. Le code suivant va donc agréger les données et corriger les problèmes identifiés.



```{r,comment=FALSE,warning=FALSE,message=FALSE,cache=TRUE}
data.val.cl = data.val |>group_by(JOUR,ID_REFA_LDA) |>
  summarise(NB_VALD=sum(NB_VALD),NAME=first(LIBELLE_ARRET)) |>
  mutate(year=year(JOUR)) |>
  arrange(ID_REFA_LDA,JOUR) |> 
  ungroup() |>
  filter(ID_REFA_LDA!="?" & ID_REFA_LDA!=0) |> # supression des données sans localisation
  mutate(ID_REFA_LDA=ifelse(is.na(ID_REFA_LDA),474152,ID_REFA_LDA)) # correction des données de porte dauphine
```


Observons maintenant pour chaque stations la plage temporelle de données recueillie et ne conservons que les stations dont les plages sont différentes de celles attendues (du 1er Janvier 2015 au 30 Juin 2022) :

```{r,comment=FALSE,warning=FALSE,message=FALSE,cache=TRUE}
data.val.cl |> 
    group_by(ID_REFA_LDA,NAME) |> 
    summarise(dstart=first(JOUR),dend=last(JOUR)) |> 
    filter(dstart!="2015-01-01" | dend!="2022-06-30") |>
    arrange(NAME,dstart)
```

Nous pouvons voir qu'un certain nombre de stations ne sont plus observées à partir de 2017 et que d'autres ne sont observées qu'à partir de cette date. En effet, à cette date, le référentiel des zones d'arrêt a été mis a jour et certaines stations ont changées d'identifiant. 


```{r,comment=FALSE,warning=FALSE,message=FALSE,cache=TRUE}
# changement d'id
data.idchange=data.val.cl |> 
  group_by(ID_REFA_LDA,NAME) |> 
  summarise(tend=last(year),dend=last(JOUR),tstart=first(year),dstart=first(JOUR)) |> 
  filter(!(tstart==2015 & tend==2022)) |> 
  arrange(NAME)
```



```{r,comment=FALSE,warning=FALSE,message=FALSE,cache=TRUE}
oldids = data.idchange |> filter(tstart==2015,tend==2017) |> rename(OLD_ID=ID_REFA_LDA)
newids = data.idchange |> filter(tstart==2017,tend==2022) |> rename(NEW_ID=ID_REFA_LDA)
```

```{r,comment=FALSE,warning=FALSE,message=FALSE,cache=TRUE}
cname="AVENUE FOCH"
gg=data.val.cl |> filter(NAME==cname)
ggplot(gg)+geom_line(aes(color=ID_REFA_LDA,x=JOUR,y=NB_VALD),alpha=0.6)+
  scale_color_brewer(palette="Set2")+
  labs(title=paste("Changement d'identifiant de la station",cname),
       x="Date",y="Validation en nombre de passagers")+
  theme_bw()+
  theme(legend.position="bottom")
```

Après verification manuel de la cohérence, nous observons que ces changements correpondent à de simples changements d'identifiant dans un certain nombre de cas mais que certaines stations ont aussi des changement de périmètres en particuliers les stations : 
"AVRON", "BUZENVAL", "HAVRE-CAUMARTIN", "LE PELETIER", "LES HALLES", "CHATELET-LES-HALLES", "SAINT-LAZARE"  qui seront traitée individuellement à cause de changement de périmètre.

```{r,comment=FALSE,warning=FALSE,message=FALSE,cache=TRUE}
cname="SAINT-LAZARE"
gg=data.val.cl |> filter(NAME==cname)
ggplot(gg)+geom_line(aes(color=ID_REFA_LDA,x=JOUR,y=NB_VALD),alpha=0.6)+
  scale_color_brewer(palette="Set2")+
  labs(title=paste("Changement d'identifiant et de périmètre de la station",cname),
       x="Date",y="Validation en nombre de passagers")+
  theme_bw()+
  theme(legend.position="bottom")
```




```{r,comment=FALSE,warning=FALSE,message=FALSE,cache=TRUE}

upids = oldids |> 
  left_join(newids,by=c("NAME"="NAME")) |> 
  filter(!is.na(NEW_ID)) |> 
  filter(!NAME %in% c("AVRON", "BUZENVAL", "HAVRE-CAUMARTIN", "LE PELETIER", "LES HALLES", "CHATELET-LES-HALLES", "SAINT-LAZARE")) |>
  select(NEW_ID,OLD_ID)


# recodage des ids
data.val.cl2 = data.val.cl |> 
  left_join(upids,by=c("ID_REFA_LDA"="OLD_ID")) |> 
  mutate(ID_REFA_LDA=if_else(!is.na(NEW_ID),NEW_ID,ID_REFA_LDA)) |>
  select(-NEW_ID)

# verifications
check=duplicated(paste(data.val.cl2$JOUR,data.val.cl2$ID_REFA_LDA))
sum(check)
```

Pour les stations impactées, il faut traiter les stations au cas par cas. Les stations "LE PELLETIER" et "SAINT-LAZARE" ne peuvent être corrigées, chatelet les halles  peut être corrigés car avant 2017 la zone comprend chatelet les halles et les halles qui sont désagrégées en deux unités ensuite. Pour uniformiser sur toute la période de temps nous conservons la définition pré 2017.


```{r,comment=FALSE,warning=FALSE,message=FALSE,cache=TRUE}
data.val.cl3 = data.val.cl2 |> 
  filter(NAME!="LE PELLETIER",NAME!="SAINT-LAZARE") |> # pbr de cohérences dans ces séries
  mutate(ID_REFA_LDA= case_when(
   NAME=="AVRON" ~ "71697",
   NAME=="BUZENVAL"~ "479928",
   NAME=="HAVRE-CAUMARTIN" ~ "482368",
   TRUE ~ ID_REFA_LDA
  )) |>
  mutate(NAME=if_else(NAME=="CHATELET-LES HALLES","LES HALLES",NAME),
         ID_REFA_LDA=if_else(ID_REFA_LDA=="474141","73794",ID_REFA_LDA)) |>
  group_by(JOUR,NAME,ID_REFA_LDA) |>
  summarise(NB_VALD=sum(NB_VALD)) |>
  ungroup() |>
  mutate(LOG_VAL = log(NB_VALD),year=year(JOUR),yd=yday(JOUR),wd=wday(JOUR)) 
```


A ce stade des prétraitements, les données sont presque nettoyées. Pour finir, nous uniformisons les libellés et filtrons les series pour ne conserver que celles couvrant toute la période avec un nombre raisonale de validation moyenne par jour et de données manquantes. Ces filtrages sont bien sur dépendant du sujet à traiter et doivent être discutés. 

```{r,comment=FALSE,warning=FALSE,message=FALSE,cache=TRUE}
# uniformisation des noms
data.names=data.val.cl3 |> 
  count(ID_REFA_LDA,NAME) |> 
  group_by(ID_REFA_LDA) |> 
  arrange(desc(n)) |> 
  slice_head(n=1) 
data.val.cl4 = data.val.cl3 |> 
  select(-NAME) |> 
  left_join(data.names |> select(-n))

# filtrage : observées en 2015 et 2022 ?
id_clean_date = data.val.cl4 |> 
  group_by(ID_REFA_LDA,NAME) |> 
  summarise(tstart=first(year),tend=last(year),dend=last(JOUR)) |>
  filter(tstart==2015,tend==2022) |> 
  pull(ID_REFA_LDA)

# statistiques globales par station
st_glob_stats = data.val.cl4 |>  
  group_by(ID_REFA_LDA,NAME) |>
  summarise(M_VALD=mean(NB_VALD),NB_VALD = sum(NB_VALD),NB_J=n()) |> 
  arrange(desc(NB_VALD)) 

# filtrage : observée sur au moins 2000 journées avec plus de 500 validations en moyenne par jour ?
id_clean_data= st_glob_stats |> 
  filter(M_VALD>=500,NB_J>2000) |> 
  pull(ID_REFA_LDA)

# application du filtrage
data.val.ok = data.val.cl4 |> 
  filter(ID_REFA_LDA %in% intersect(id_clean_data,id_clean_date))

# statistiques glocables par stations
st_glob_stats = data.val.ok |>  
  group_by(ID_REFA_LDA,NAME) |>
  summarise(M_VALD=mean(NB_VALD),NB_VALD = sum(NB_VALD),NB_J=n()) |> 
  arrange(desc(NB_VALD)) 
st_glob_stats
```

Les données nettoyées et filtrées sont disponibles directement dans le fichier data.val.ok.RDS.

## Quelques premiers graphiques

Afin de mieux connaître nos données, nous allons les visualiser via des graphiques simples afin comprendre les différentes variables du jeu de données et leur dynamique. Les graphiques suivant sont réduit à une station pour réduire le temps de calcul. Ce premier boxplot nous résume les statistiques descriptives du volume de passager par années d'une station.

```{r,comment=FALSE,warning=FALSE,message=FALSE,cache=TRUE}
# Visualisation  de l'évolution d'une station durant la période étudier
box = data.val.cl4 |> filter(NAME %in% c("NATION" )) |> mutate(ANNEE = as.character(year))
ggplot(box, aes(ANNEE, y = NB_VALD)) + 
  geom_boxplot()+
  labs(x = 'Date' , y = 'Validation en nombre de passager', title = 'Distribution des volumes passagers par année pour la station de Nation') +
  theme_bw()

```
A ce stade on observe que l'années 2020 à connue une forte décrue du volume de passager. En utilisant d'autre fonctions de dessin, nommée "geom_", nous pouvons mettre en avant chacune des variables en fonction des autres. Le graphique suivant nous montre l'évolution du volume d'une station lors de l'année atypique de 2020 ou l'on peut observer l'impact des confinements sur les TC.

```{r,comment=FALSE,warning=FALSE,message=FALSE,cache=TRUE}
# Visualisation  de l'évolution d'une station durant la période étudier
see = data.val.cl4 |> filter(NAME == "NATION") |> filter(year == 2020)
ggplot(see) + 
  geom_line(aes(JOUR, NB_VALD))+
  labs(x = 'Date' , y = 'Validation en nombre de passager', title = 'Validation Par jour pour la Station de Nation en 2020') +
  theme_bw()

```
Il est possible de cumuler plusieurs graphiques en additionnant les fonctions de dessins "geom" pour synthétiser plusieurs conclusion. Par exemple le graphique suivant montre l'augmentation progressive du volume passagers suite au confinements  tout en affichant les différents confinement sur 2020 et 2021. 

```{r,comment=FALSE,warning=FALSE,message=FALSE,cache=TRUE}
# Visualisation  de l'évolution d'une station durant la période étudier
see = data.val.cl4 |> filter(NAME == "NATION") |> filter(year > 2019)
ggplot(see) + 
  geom_line(aes(JOUR, NB_VALD))+
  geom_smooth(aes(JOUR, NB_VALD), method= "lm")+
  labs(x = 'Date' , y = 'Validation en nombre de passager', title = 'Validation Par jour pour la Station de Nation en 2020') +
  theme_bw()

```
Attention pour l'usage de certaines fonctions, des paramètres peuvent biaiser le rendu. Dans le cadre du geom_smooth par exemple, le choix de la fonction doit être fait de manière pertienente et selon les données (ici la fonction "loess" peut être plus intéressante pour représenter la tendance des volumes de passagers.). Il est aussi possible d'utiliser des fonctions personalisées via des formules (ex : y ~ poly(x, 2), y ~ log(x))

Cependant, la taille du jeu de données impliquent que toutes les données ne peuvent pas toujours être afficher pour avoir un résultat observable. Ainsi le graphique suivant montre le volume de passager moyen  sous jeux de données pour les 15 stations avec le plus grand volume de passagers pour ne pas avoir à afficher l'intégralité des 732 stations.
```{r,comment=FALSE,warning=FALSE,message=FALSE,cache=TRUE}
#  créer de nouvelles variables
comp = data.val.cl4 |>
  filter(wd %in% c(2,3,4,5,6)) |> 
  group_by(NAME) |> 
  summarise(n = mean(NB_VALD)) |> 
  arrange(desc(n) ) |> 
  mutate(type_jour = "Ouvré")
weekend = data.val.cl4 |> 
  filter(wd %in% c(1,7)) |> 
  group_by(NAME) |> 
  summarise(n = mean(NB_VALD)) |> 
  arrange(desc(n) ) |> 
  mutate(type_jour = "Weekend")

comp  = bind_rows(comp,weekend)

biggest_station = comp |> slice(1:15) |> arrange(desc(n))  |> select(NAME)
df = comp |> filter(NAME %in% biggest_station$NAME)

ggplot(df)  +
  geom_col(aes(n, reorder(NAME, n, sum), fill = type_jour), position="dodge")+
  labs(x = 'Volume passager moyen' , y = '', title = 'Les 15 plus grandes stations en volume passager par type de jour') +
  scale_color_brewer(palette="Set2")+
  theme_bw()+ 
  theme(legend.position="bottom")
```

De plus, on peut aussi faire des études différenciées entre les hub des TC parisiens en utilisant des graphiques précédemment utilisé en ayant un focus sur un nombre limité de stations. 


```{r,comment=FALSE,warning=FALSE,message=FALSE,cache=TRUE}
# Visualisation  de l'évolution d'une station durant la période étudier
comp = data.val.cl4 |> filter(NAME %in% c("LA DEFENSE-GRANDE ARCHE","GARE DE LYON" )) |> filter(year == 2020)
ggplot(comp, aes(JOUR, NB_VALD, linetype = NAME, color=NAME)) + 
  geom_line()+
  scale_color_brewer(palette="Set2")+
  labs(x = 'Date' , y = 'Validation en nombre de passager', 
       title = 'Comparaison du volume passager entre Nation et Gare de Lyon en 2020') +
  theme_bw()+
  theme(legend.position="bottom")
```

Cependant, ces visualisations ne permettent pas de tenir compte de la composante spatiale des données.


## Données spatiales 

Pour comprendre les dynamiques spatiales et temporelles des flux de passagers au sein de la métropole parisienne, on peut utiliser les identifiants stations pour projeter sur une carte de l'Île de France nos données. Pour se faire, il faut utiliser un réferentiel IDFM des positions GPS des stations :
https://data.iledefrance-mobilites.fr/api/datasets/1.0/referentiel-arret-tc-idf/attachments/2021_idfm_referentiels_pdf/

La jointure est effectué grâce au code des stations en se limitant uniquement au réseau ferré. Les stations sont présentées sous la forme de shapefiles pour toutes les stations dont on extrait les centroids comme coordonées GPS.

A ce stade, chaque série est associée à des coordonées que l'on represente sur une carte via le package leaflet afin de proposer une carte intéractive de nos données.

```{r,comment=FALSE,warning=FALSE,message=FALSE,cache=TRUE}
# recuperation des données spatiales
url = "https://data.iledefrance-mobilites.fr/api/explore/v2.1/catalog/datasets/referentiel-arret-tc-idf/exports/csv?lang=fr&timezone=Europe%2FBerlin&use_labels=true&csv_separator=%3B"
#download.file(url,"./data-raw/referentiel-arret-tc-idf.csv")

ref_urls=read_delim("./data-raw/referentiel-arret-tc-idf.csv",delim=";")
#download.file(ref_urls |> filter(Fichier=="REF_LDA.zip") |> pull(`Lien de téléchargement`),destfile = "./data-raw/REF_LDA.zip")
#unzip("./data-raw/REF_LDA.zip",exdir = "./data-raw")

# lecture / filtrage / pré-traitement
type_arr = c("Station de métro","Station ferrée / Val")
ref.lda = read_sf("./data-raw/PL_LDA_25_01_2023.shp") |> 
  mutate(ID_REFA_LDA=as.character(id_refa)) |> 
  filter(type_arret %in% type_arr)
ref.lda.centroid = ref.lda |> st_centroid()

# carte interactive avec leaflet, carte simple suivante le type d'arret
library(leaflet)
factpal <- colorFactor("Set2", ref.lda$type_arret)
leaflet(data = ref.lda.centroid |> st_transform(4326)) %>% addTiles() %>%
  addCircles( label = ~ paste(nom,", ",type_arret, "\n id: ",ID_REFA_LDA),color=~factpal(type_arret),opacity = 1,radius =~ 50)

# carte avec symboles proprotionels ! a la racine carré
st.stats.sf = ref.lda.centroid |> st_transform(4326) |> left_join(st_glob_stats) |> filter(!is.na(M_VALD))
leaflet(data = st.stats.sf ) %>% addTiles() %>% 
  addProviderTiles(providers$CartoDB.Positron) %>% 
  setView(zoom = 12,lng=2.3510768,lat=48.8567879 ) |>
  addCircles( label = ~ paste(nom,", ",type_arret, "\n id: ",ID_REFA_LDA),
              color=~factpal(type_arret),opacity = 1,radius =~ sqrt(M_VALD*5),weight = 1,fillOpacity = 0.65)

```


## Autres données disponnibles 

Le données en open data d'IdFM ne se limites pas aux données de validations journalières, vous pouvez également analyser les données de profils journaliers :


```{r,comment=FALSE,warning=FALSE,message=FALSE,cache=TRUE}
profs2018 = read_delim("./data-raw/data-rf-2018/2018_S1_PROFIL_FER.txt", delim = "\t",locale = locale(decimal_mark = ",")) |>
  mutate(periode="1er Semestre 2018")
profs2021 = read_delim("./data-raw/data-rf-2021/2021_S1_PROFIL_FER.txt",delim="\t",locale = locale(decimal_mark = ","))|>
  mutate(periode="1er Semestre 2021")

profs = bind_rows(profs2018,profs2021)  |>
  filter(TRNC_HORR_60!="ND") |>
  separate(TRNC_HORR_60,c("Hdeb","Hfin"),"-") |>
  mutate(H=as.numeric(gsub("H","",Hdeb)))

gg=profs |> filter(ID_REFA_LDA==71379,CAT_JOUR%in%c("JOHV","SAHV"),CODE_STIF_RES=="110")
ggplot(gg)+
  geom_line(aes(x=H,y=pourc_validations,color=periode,group=periode))+
  scale_x_continuous(breaks=seq(0,23,by=2),minor_breaks = seq(1,23,by=2))+
  scale_color_brewer("Période :",palette = "Set2")+
  labs(title="Profils de validation journaliers",
       subtitle="de la station de métro Porte Maillot en 2018 et 2021, suivant le type de jours",
       x="Tranche horaire",
       y="Pourcentage de validations (%)")+
  facet_wrap(~CAT_JOUR)+
  theme_bw()+
  theme(legend.position="bottom")

```

Bien d'autres données sont disponibles sur la plateforme d'IdFM. Même les données de régularités de ce [pdf](https://www.iledefrance-mobilites.fr/medias/portail-idfm/94772b0d-de4c-4434-b515-4af1f6cb78cf_Ponctualite%CC%81_novembre_Ligne_Axes_SNCF_RATP_1122.pdf) si vous le souhaitez (voir le script ponctualite.R). 
Ces données sont utilisées à titre d'exemple par les récents articles de [franceinfo](https://www.francetvinfo.fr/economie/transports/infographies-metro-a-paris-visualisez-comment-l-offre-s-est-degradee-depuis-la-crise-sanitaire_5553441.html) et [du Monde](https://www.lemonde.fr/les-decodeurs/article/2023/01/27/la-frequence-des-metros-parisiens-de-retour-au-niveau-pre-covid-les-chiffres-montrent-que-la-ratp-en-est-loin_6159560_4355770.html)

N'hésitez pas à  explorer la plateforme et à poser des questions. 

A vous de jouer !

