Langage R et naissances par commune 2020

Cet article technique inaugure une série consacrée à la flexibilité du langage statistique R (la série sera donc fournie !)

Je vais vous montrer à partir de cas concrets et pratiques, comment gagner du temps, récupérer facilement des ressources sur le web et les mettre en forme pour les adapter à vos besoins de traitement et d’analyse. Ici, vous ne trouverez point d’exemples hors-sol ou de tutoriels traduits hâtivement de l’américain. Ma connaissance de R s’est nourrie de la nécessité de résoudre des défis du quotidien, dans un métier – statisticien – que j’exerce depuis 30 ans.

Je privilégie la syntaxe R « moderne », issue du cadre Tidyverse créé par Hadley Wickham. Cette façon de programmer est élégante et didactique, cohérente et facile à assimiler.

Commençons avec des données Insee : l’institut statistique français vient de mettre en ligne une série de près de 10 années sur les naissances domiciliées, à la commune, en géographie 2020. Ces données sont proposées au format xlsx ou csv.

1 - Acquisition des données Insee d'État civil

Notre script R va cibler le fichier excel dont les multiples onglets nous intéressent. Ce fichier est zippé, et l’on récupère facilement son URL sur la page de présentation :
# début du script
library(tidyverse)
url_ec <- "https://www.insee.fr/fr/statistiques/fichier/1893255/base_naissances_2019.zip"
La première étape consiste à télécharger et dézipper le fichier de données :
library(curl)
xlsfile <- str_replace(basename(url_ec), ".zip", ".xlsx") # "base_naissances_2019.xlsx"
curl_download(url_ec, tempzip <- tempfile())              # télécharge le zip
xlsfile <- unzip(zipfile = tempzip, files = xlsfile)      # extrait le xlsx
La variable xlsfile permet de localiser le fichier téléchargé sur le disque, une rapide inspection de son contenu montre que le 1er onglet comprend des données communales, le second des données par arrondissement municipal. Commençons par cibler le 1er, notant au passage que le tableau de données en tant que tel commence à la ligne 5.
library(readxl)
tb_com <- read_excel(xlsfile, sheet = 1, skip = 5)

Voici les données chargées dans tb_com (ici les 20 premières lignes) :

Comme on le voit, cette table présente 4 variables caractères et 10 variables numériques.

Ces variables sont toutes en majuscules, et j’aime à suivre les bonnes pratiques des bases de données : noms de variables alphanumériques (sans accent ni blanc), et en minuscules. Le package janitor offre quelques belles fonctions utilitaires, dont clean_names() qui sécurise efficacement les libellés de colonnes.
library(janitor)
tb_com <- tb_com %>% clean_names()
# nettoie les noms de colonne => minuscules, sans blanc ni accent

2) Agrégation par département

Je voudrais maintenant regrouper ces naissances par département. Comment sommer ces 10 colonnes numériques sans trop me fatiguer ? 

La syntaxe chaînée de tidyverse / dplyr nous invite à décomposer les deux opérations logiques nécessaires : le choix du critère de regroupement (dep), puis la liste des variables à sommer. Nul besoin d’énumérer les 10 variables numériques concernées, les possibilités offertes par la syntaxe dplyr vont simplifier l’écriture, dès lors que l’on veut sommer toutes les colonnes numériques. Plus simple qu’en SQL, n’est-il pas ?
tb_dep <- tb_com %>% group_by(dep) %>%
          summarise_if(is.numeric, sum, na.rm = TRUE)

# introduction d'un filtrage préalable sur une région (Bretagne, de code '53')
tb_dep <- tb_com %>% filter(reg == '53') %>% 
          group_by(dep) %>%
          summarise_if(is.numeric, sum, na.rm = TRUE)

Voici le résultat de la dernière requête, filtrée sur la région Bretagne :

J’entends déjà les bons élèves : « depuis la version dplyr 1.0, summarise_if est dépréciée, il faut utiliser across à la place ». Que nenni ! Le big boss Hadley Wickham me l’a personnellement confirmé (car nous sommes intimes) : « there’s no rush to stop using …_if(); it’ll be around for at least several more years before we even begin to consider if it should be deprecated ».

Il faut dire que, pour l’instant, summarise(across…) est 5 fois plus lent que summarise_if, mais cela devrait s’améliorer très bientôt avec dplyr 1.1…

Maintenant, pour celles ou ceux qui y tiennent, on peut en effet écrire :
tb_dep <- tb_com %>% group_by(dep) %>%
          summarise(across(where(is.numeric), sum, na.rm = TRUE))
Mais c’est moins joli/intuitif, nous réserverons across pour des cas d’usage plus pointus.

3 - Ajout des données par arrondissement municipal

Le fichier excel d’origine comporte deux onglets, et le second détaille les naissances par arrondissement des communes de Paris, Lyon et Marseille. Nous allons ajouter ces lignes à notre table communale, en prenant soin d’ajouter une colonne catégorielle pour distinguer 3 types de territoire : communes simples (C), communes divisées en arrondissements (D), arrondissements (A).

Voici les 3 phases :
# ajout d'une nouvelle colonne type_terr
tb_com <- tb_com %>% mutate(type_terr = 
                            if_else(codgeo %in% c('75056','13055','69123'), 'D', 'C'), 
                            .after = 'codgeo')

# lecture des données par arrondissement
tb_arm <- read_excel(xlsfile, sheet = 2, skip = 5) %>%  
          clean_names() %>% 
          mutate(type_terr = 'A', .after = 'codgeo')

# concaténation
tb_com <- tb_com %>% bind_rows(tb_arm) %>%
          arrange(codgeo)  # tri par codgeo
En fonction des besoins, je pourrai constituer la liste des communes de France, ou la liste des communes/arrondissements municipaux, chacune formant une partition du territoire national :
# table des communes
tb_com %>% filter(type_terr %in% c('C','D'))

# table des communes/arm
tb_com %>% filter(type_terr %in% c('C','A'))

4 - Transposition/simplification : mise en évidence de l'axe temporel

Notre table avec 10 colonnes numériques ne répond pas aux critères des statisticiens exigeants que nous sommes. Ces 10 colonnes, on le voit bien, relèvent de la même notion, du même indicateur, seule l’année les différencie. 

Une table maniable comprend un nombre de colonnes réduit et stable dans le temps. L’ajout d’un nouveau millésime de données ne doit pas en perturber la structure. On va donc chercher à introduire une nouvelle colonne annee, pour ne garder qu’une seule variable numérique naisd.

Pour restructurer cette table, nous avons le choix entre 2 fonctions de pivot : pivot_wider et pivot_longer. La seconde est appropriée ici car, comme son nom l’indique, il s’agit d’augmenter le nombre de lignes.
tb_com_tr <- tb_com %>% select(-libgeo) %>%  # suppression de libgeo pour alléger
             pivot_longer(cols = starts_with('naisd'), 
                          names_to = "annee", values_to = "naisd") 

# par défaut, la nouvelle variable annee reprend les noms de colonnes numériques
# il nous faut la corriger en remplaçant la racine naisd par 20, ainsi naisd19 => 2019
tb_com_tr <- tb_com_tr %>% mutate(annee = str_replace(annee, "naisd", "20"))

Voilà qui nous amène à une table de 350 000 lignes (ici les 20 premières) :

Cette table peut être complétée par d’autres indicateurs numériques, comme les décès ou la population, elle restera temporelle, sans multiplication abusive du nombre de colonnes. Par ailleurs, il est simple de l’agréger, par exemple par région :
tb_reg <- tb_com_tr %>% filter(type_terr %in% c('C','D')) %>%
group_by(reg, annee) %>%
summarise(naisd = sum(naisd, na.rm = TRUE))
Ou par départements de Bretagne pour en faire un graphique d’évolution :
tb_dep53 <- tb_com_tr %>% filter(reg == '53') %>%
            group_by(dep, annee) %>%
            summarise(naisd = sum(naisd, na.rm = TRUE))

ggplot(tb_dep53 %>% mutate(annee=as.numeric(annee))) +
       aes(x = annee, y = naisd, colour = dep) +
       geom_line(size = 1.2) +
       labs(title = "Évolution des naissances domiciliées en Bretagne", 
            color = "Dépts", subtitle="source : Insee", x = "", y = "" ) +
       theme_minimal() +
       expand_limits(y = 0) +
       scale_x_continuous(breaks = seq(2010, 2019, 1)) +
       scale_y_continuous(breaks = seq(0, 14000, 2000))

5 - Export du résultat

Il ne nous reste plus qu’à stocker le résultat de ce traitement. Une base de données serait une destination appropriée, pour en faciliter le partage et la jointure avec d’autres jeux de données ou référentiels géographiques. Je vous montrerai comment procéder dans un article ultérieur. 

Voyons ici comment exporter en csv ou en xlsx. Une bonne règle de nommage pour les fichiers finaux consiste à mentionner le thème du jeu de données (ici etatcivil) et le niveau géographique de détail, avec son millésime (ici comarm2020) :
# export du résultat
# par exemple en xlsx
library(openxlsx)
write.xlsx(tb_com_tr, file = 'etatcivil_comarm2020.xlsx', 
           sheetName = "comarm2020", overwrite = TRUE)
# ou en csv
write.csv(tb_com_tr, file = "etatcivil_comarm2020.csv", 
          row.names = FALSE)

A partir de là, il est possible de cartographier ses données avec par exemple l’excellent outil France découverte ! Et même de produire une belle animation temporelle. L’Insee les a mis en ligne aussi sur Statistiques locales.

Enfin, n’oublions pas le petit nettoyage final pour libérer mémoire et espace disque :
rm('tb_arm','tb_com','tb_dep','tb_reg')
unlink(c(tempzip,xlsfile))

N’hésitez pas à laisser un commentaire, j’y répondrai volontiers. Je propose également des transferts de connaissance et d’expertise sur mesure (R/tidyverse, certification RStudio en cours), à l’issue desquels vous aurez gagné en autonomie et idées d’explorations nouvelles !

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée.