Formation R perfectionnement Martin Chevalier (Insee)


Cette page comporte l’ensemble des cas pratiques de la formation R perfectionnement donnée à la Drees les 16 et 17 avril 2018 accompagnés de leur correction.

Les supports de cette formation ont été conçus sous RStudio avec R Markdown et compilés le 10/04/2018. Certains éléments de mise en forme du site compagnon sont repris de l’ouvrage R packages de Hadley Wickham.

Ces supports seront durablement disponibles à l’adresse http:// t.slmc.fr/perf et leur code source sur github. L’ensemble est librement réutilisable sous © 2016-2018 Martin Chevalier CC BY-NC-SA 3.0.

Savoir utiliser les fonctions *apply(), do.call() et Reduce()

Le package microbenchmark est souvent utilisé dans cette partie et les suivantes pour mesurer la performance des des solutions testées :

install.packages("microbenchmark")
library(microbenchmark)

 

Cas pratique 1 Fonctions et environnements

Tout ce qui se passe dans R correspond à un appel de fonction. Comprendre le fonctionnement des fonctions et savoir en créer soi-même est donc crucial.

  1. Utilisez les guillemets simples inversés (AltGr + 7 sur le clavier azerty) pour afficher le code associé au signe +. Utilisez-le comme une fonction classique avec la syntaxe nomFonction(parametre1, parametre2).

     

  2. Définissez la fonction monCalcul(x, puissance) qui pour un vecteur numérique x quelconque :

    1. calcule sa somme ;

    2. met la somme à la puissance puissance.

    Dans un second temps, donnez à puissance la valeur 2 par défaut et faites en sorte que la fonction prenne en charge les vecteurs x présentant des valeurs manquantes NA.

     

  3. Modifier la fonction pour ajouter une étape de vérification du type de x : prévoyez un message d’erreur si x est de type character ou factor.

     

  4. Quelle est la valeur de l’objet T ? Comment expliquez-vous que cet objet soit défini alors que vous ne l’avez pas vous-même créé (vous pouvez utiliser la fonction getAnywhere() pour répondre) ?

     

  5. Soumettez le code T <- 3. Que vaut désormais l’objet T ? Pourquoi R n’accède-t-il plus à la valeur stockée par défaut (vous pouvez utiliser la fonction search() pour répondre) ? Comment accéder désormais à la valeur par défaut ? Que retenez-vous quant à l’utilisation de T et de F en lieu et place de TRUE et FALSE dans un code ?

     

 

Cas pratique 2 apply() : Appliquer une fonction selon les dimensions d’une matrice

  1. Créez une matrice e1 de 3 lignes et de 5 colonnes en utilisant la fonction runif(). Ses valeurs sont-elles identiques si vous la générez une seconde fois ? Comment faire pour que cela soit le cas ?

     

  2. Utilisez la fonction apply() pour calculer la somme des termes de chaque ligne de e1. Comment auriez-vous pu faire autrement ? Utilisez la fonction microbenchmark() pour comparer ces deux solutions.

     

  3. Utilisez la fonction apply() pour centrer-réduire toutes les colonnes de e1 (i.e. leur soustraire leur moyenne puis les diviser par leur écart-type).

     

 

Cas pratique 3 lapply() et sapply() : Appliquer une fonction aux éléments d’un vecteur, d’une liste ou aux colonnes d’un data.frame

  1. On définit le vecteur f1 par f1 <- 5:15. Utilisez la fonction sapply() pour calculer la somme cumulée des éléments de f1. Quelle(s) alternative(s) envisageriez-vous ? Utilisez la fonction microbenchmark() pour comparer ces solutions.

     

  2. On définit la liste f2 par

    f2 <- list(
      sample.int(26, 10, replace = TRUE)
      , sample.int(26, 100, replace = TRUE)
      , sample.int(26, 1000, replace = TRUE)
    )

Utilisez les fonctions lapply() ou sapply() pour :

  • retrouver la longueur de chacun des éléments de f2 ;
  • extraire les 5 premiers éléments de chacun des éléments de f2 ;
  • remplacer chaque élément de f2 par le vecteur de lettres (en minuscules) dont il représente les positions dans l’alphabet.

     

  1. On définit le data.frame f3 par

    set.seed(1)
    f3 <- data.frame(
      id = letters[1:20]
      , by = rep(letters[1:5], times = 4)
      , matrix(runif(100), ncol = 5)
      , stringsAsFactors = FALSE
    )

    Utilisez les fonctions lapply() ou sapply() pour :

  • déterminer le type de chacune des variables de f3;
  • calculer la moyenne de toutes les variables numériques de f3;
  • convertir toutes les variables de type character en facteurs.
     

 

Cas pratique 4 tapply() : Appliquer une fonction selon les modalités d’un vecteur

  1. On définit le vecteur g1 <- sample(20) et le vecteur g2 <- rep(c("H", "F"), times = 10). Utilisez tapply() pour calculer la moyenne de g1 selon les groupes définis par g2.

     

  2. On repart du data.frame f3 du cas pratique précédent. Utilisez tapply() pour calculer le total de la variable X1 selon les modalités de la variable by.

     

  3. Combinez la fonction split() avec sapply() pour obtenir le même résultat. Comment calculeriez-vous le total de toutes les variables numériques de f3 selon les modalités de la variable by ?

     

 

Cas pratique 5 do.call() : Appliquer une fonction simultanément à l’ensemble des éléments d’une liste

De nombreuses fonctions peuvent porter sur un nombre indéterminé d’éléments : c(), sum(), rbind(), etc. Pour les appliquer à l’ensemble des éléments d’une liste sans avoir à tous les écrire un à un, il suffit d’utiliser do.call().

  1. On définit la liste h1 <- list(1:5, 6:10, 11:15). Que se passe-t-il si vous soumettez sum(h1) ? Utilisez do.call() pour sommer l’ensemble des éléments de h1. Comparez avec le résultat de sapply(h1, sum).

     

  2. Réunissez les éléments de h1 en un seul vecteur avec la fonction base::c(). Comparez avec le résultat de lapply(h1, base::c)

     

  3. On définit la liste de matrices h2

    h2 <- list(
      matrix(1:6, nrow = 2)
      , matrix(7:12, nrow = 2)
      , matrix(13:18, nrow = 2)
    )
    Utilisez do.call() avec les fonctions rbind() et cbind() pour concaténer l’ensemble des éléments de h2 en ligne ou en colonne respectivement.

     

 

Cas pratique 6 Reduce() : Appliquer une fonction successivement à l’ensemble des éléments d’une liste

De nombreuses fonctions portent sur deux éléments précisément : opérations arithmétiques, merge(), etc. Pour les appliquer successivement à l’ensemble des éléments d’une liste, il suffit d’utiliser Reduce().

  1. On repart de la liste h1 du cas pratique précédent. Observez le résultat de Reduce(`+`, h1) : comment le comprenez-vous ? Comparez avec sapply(h1, sum).

     

  2. On définit la liste de data.frame i1

    i1 <- list(
      data.frame(id = letters[1:4], var1 = 1:4, stringsAsFactors = FALSE)
      , data.frame(id = letters[2:5], var2 = 5:8, stringsAsFactors = FALSE)
      , data.frame(id = letters[3:6], var3 = 9:12, stringsAsFactors = FALSE)
    )
    Utilisez Reduce() pour fusionner l’ensemble des éléments de i1 selon la variable id. Comment ajusteriez-vous ce code pour pouvoir utiliser l’option all = TRUE de la fonction merge() ?

     

Travailler efficacement sur des données avec base R

L’ensemble des cas pratiques qui suivent portent sur les données de l’enquête Emploi en continu stockées dans le fichier eect4.rds. La fonction readRDS() permet de le charger en mémoire :

setwd("Y:/Documentation/R/R_perfectionnement/donnees")
eec <- readRDS("eect4.rds")
str(eec)
## 'data.frame':    34913 obs. of  19 variables:
##  $ IDENT    : chr  "G0A56JP6" "G0A56JP6" "G0A56JR6" "G0A56JS6" ...
##  $ TRIM     : chr  "4" "4" "4" "4" ...
##  $ NOI      : chr  "01" "02" "01" "01" ...
##  $ REG      : chr  "11" "11" "11" "11" ...
##  $ AGE      : chr  "66" "29" "27" "29" ...
##  $ SEXE     : chr  "2" "1" "2" "2" ...
##  $ CSE      : chr  "56" "81" "38" "37" ...
##  $ DIP11    : chr  "71" "42" "10" "11" ...
##  $ ACTEU    : chr  "1" "2" "1" "1" ...
##  $ SALRED   : int  596 NA 2700 2666 11967 NA 2000 2800 2333 3500 ...
##  $ STC      : chr  "2" NA "2" "2" ...
##  $ TAM1D    : chr  NA NA NA NA ...
##  $ AIDREF   : chr  NA "5" NA NA ...
##  $ TPP      : chr  "1" NA "1" "1" ...
##  $ NBAGENF  : chr  "0" "0" "0" "0" ...
##  $ DUHAB    : chr  "7" NA "7" "7" ...
##  $ PUB3FP   : chr  "4" NA "4" "4" ...
##  $ NAIA     : chr  "1946" "1983" "1985" "1983" ...
##  $ EXTRI1613: num  1777 1777 2045 1898 1754 ...

 

Cas pratique 7 Sélection d’observations

  1. Utilisez l’opérateur [ pour sélectionner l’individu appartenant au ménage dont l’IDENT est "GFO5NVUE" et dont le numéro d’ordre NOI est "02".

     

  2. Cherchez de la documentation sur la fonction subset(). Comparez ses performances avec celles de l’opérateur [ à l’aide de la fonction microbenchmark().

     

  3. Concaténez les valeurs des variables IDENT et NOI et utilisez le résultat comme noms de ligne. Retrouvez alors l’individu de la question a à l’aide de son nom de ligne. Comparez les performances de cette méthode avec celles de l’opérateur [.

     

 

Cas pratique 8 Création de variables

On souhaite recoder la variable AGE en trois modalités : 15-30 ans, 31-60 ans et plus de 60 ans. On part du code suivant :

for(i in 1:nrow(eec)) eec$trage1[i] <- if(as.numeric(eec$AGE[i]) < 31) "15-30" else if(as.numeric(eec$AGE[i]) < 61) "31-60" else "61 et +"
  1. Mesurez le temps d’exécution du code proposé. Que pensez-vous de cette syntaxe ?

     

  2. On propose une seconde version du code :

    eec$trage2 <- ifelse(as.numeric(eec$AGE) < 31, "15-30", ifelse(
      as.numeric(eec$AGE) < 61, "31-60", "61 et +"
    ))

    Vérifiez que le résultat est identique à la proposition initiale (par exemple avec identical()ou all.equal()) et mesurez le temps temps d’exécution de cette deuxième version. Êtes-vous surpris du gain de performances ?

     

  3. Comment recoderiez-vous la proposition précédente pour ne plus faire appel à la fonction ifelse() ? Cela est-il susceptible d’améliorer les performances ? Mettez en oeuvre cette solution et mesurez-en les performances.

     

  4. Combien de fois appelez-vous la fonction as.numeric() dans le code qui précède ? Proposez une dernière version qui minimise les opérations effectuées par R et synthétisez le gain en termes de performances.

     

 

Cas pratique 9 Agrégation par groupes : salaire moyen par région

On cherche à calculer le plus efficacement possible le salaire moyen (variable SALRED) par région (variable REG), d’abord sans pondérer puis en pondérant par le poids de sondage EXTRI1613.

  1. Comparez les performances des fonctions aggregate(), by(), sapply() (avec split()) et tapply() pour le calcul du salaire moyen non-pondéré par région.

     

  2. Quelles pistes envisageriez-vous pour améliorer encore les performances dans ce type de situation ?

     

  3. Utilisez la fonction sapply() pour calculer le salaire moyen pondéré (par le poids de sondage EXTRI1613) par région. Y parvenez-vous également avec tapply() ?

     

 

Cas pratique 10 Fusion de tables : nombre d’individus au chômage par ménage

L’objectif de ce cas pratique est de créer, dans la table eec, une variable indiquant pour chaque individu le nombre d’individus au chômage dans son ménage. La position sur le marché du travail est codée par la variable ACTEU (ACTEU == "2" correspond au chômage) et l’identifiant du ménage est la variable IDENT (les individus d’un même ménage ont la même valeur pour la variable IDENT).

  1. Utilisez la fonction tapply() pour déterminer le nombre de personnes au chômage dans chaque ménage et stockez cette information dans un objet appelé nbcho. Quelles sont ses caractéristiques ?

     

  2. Utilisez la fonction merge() pour refusionner le résultat de la question précédente avec la table eec et créer la variable nbcho.

     

  3. Utilisez habilement les noms de vecteur et l’opérateur [ pour reproduire plus efficacement le résultat de la question b (toujours en repartant de nbcho). Vérifiez que la variable créée est bien identique.

     

  4. Comparez la syntaxe et les performances des méthodes mises en oeuvre aux question b. et c.

     

Travailler efficacement sur des données avec dplyr

Les cas pratiques de cette partie reposent sur le package dplyr :

install.packages("dplyr")
library(dplyr)

Plusieurs vignettes sont disponibles sur la page de documentation du package. Rstudio a également conçu un aide-mémoire (cheatsheet) traduit en français. N’hésitez pas à vous référer à ces documents pour répondre aux cas pratiques de cette partie.

 

Cas pratique 11 Sélection d’observations, de variables et tris

  1. Utilisez le verbe filter() pour afficher les observations des femmes (SEXE == "2") actives occupées (ACTEU == "1") en Île-de-France (REG == "11"). Utilisez la syntaxe classique puis celle faisant appel à l’opérateur pipe %>%.

     

  2. Utilisez le verbe select() pour supprimer toutes les variables créées à la sous-partie précédente (trage1, trage2, trage3, trage4, nbcho1, nbcho2). Pensez à consulter les exemples de l’aide de select() pour l’utiliser au mieux.

     

  3. Utilisez le verbe arrange() pour trier la table par région et identifiant de ménage croissants puis par numéro d’ordre dans le ménage décroissants.

     

 

Cas pratique 12 Création de variables

  1. Utilisez le verbe mutate() pour effectuer le recodage en classes d’âge présenté lors de la partie précédente.

     

  2. Comparez l’ergonomie et les performances de mutate() avec la méthode la plus efficace de base R.

     

 

Cas pratique 13 Agrégation par groupes : salaire moyen par région

Comme dans le cas pratique correspondant de la partie précédente, l’objectif est d’estimer le salaire moyen par région, d’abord sans pondération puis pondéré par le poids de sondage EXTRI1613.

  1. Utilisez la fonction summarise() pour calculer le salaire moyen non-pondéré et pondéré pour l’ensemble de la France métropolitaine. Intercalez ensuite la fonction group_by() pour ventiler ces calculs par région.

     

  2. Comparez l’ergonomie et les performances des fonctions de dplyr avec la méthode la plus efficace de base R.

     

 

Cas pratique 14 Fusion de tables : recodage de la PCS

La variable CSE de la table eec code la Profession et catégorie socio-professionnelle (PCS) au niveau 3 de la nomenclature Insee (cf. le site de l’Insee). On souhaite passer du niveau 3 au niveau 2. Le fichier pcs2003_c_n4_n1.dbf est la table de passage entre l’ensemble des niveaux de la nomenclature (de 1 à 4).

  1. Utilisez le package foreign et la fonction read.dbf() pour lire le fichier pcs2003_c_n4_n1.dbf.

     

  2. Utilisez le verbe distinct() pour restreindre la table aux observations distinctes pour les niveaux N2 et N3. Créez également la variable CSE, version caractère de N3.

     

  3. Utilisez le verbe left_join() pour fusionner la table eec avec la table de passage des PCS de niveau 3 à niveau 2.

     

  4. Une solution purement vectorielle en base R n’aurait-elle pas également été possible ? Comparez les performances de dplyr avec cette solution alternative.

     

Travailler efficacement sur des données avec data.table

Les cas pratiques de cette partie reposent sur le package data.table :

install.packages("data.table")
library(data.table)

On crée le data.table correspondant au data.frame eec :

eec_dt <- data.table(eec)

Plusieurs vignettes sont disponibles sur la page de documentation du package. N’hésitez pas à vous référer à ces documents pour répondre aux cas pratiques de cette partie.

 

Cas pratique 15 Sélection d’observations et tris

  1. Utilisez l’argument i de [ pour afficher les observations des femmes (SEXE == "2") actives occupées (ACTEU == "1") en Île-de-France (REG == "11"). Quelle différence constatez-vous avec une sélection dans un data.frame ?

     

  2. Utilisez la fonction setkey() pour faire de SEXE, ACTEU et REG des clés pour eec_dt et utilisez-les pour reproduire la sélection de la question précédente. Comparez alors l’ergonomie et les performances d’une sélection d’observations :

    1. avec une clause logique dans un data.frame ;
    2. avec une clause logique en utilisant le verbe filter() de dplyr ;
    3. avec une clause logique dans un data.table ;
    4. avec un jeu de clés dans un data.table.

     

  3. Utilisez la fonction order() (comme dans un data.frame) pour trier la table eec_dt par région et identifiant de ménage croissants puis par numéro d’ordre dans le ménage décroissants. Comparez les performances de base R, arrange() de dplyr et data.table.

     

 

Cas pratique 16 Agrégation par groupes : salaire moyen par région

Comme dans le cas pratiques correspondants des parties précédentes, on cherche à calculer le salaire moyen par région non-pondéré puis pondéré par le poids de sondage EXTRI1613.

  1. Utilisez les arguments j et by de [ pour calculer le salaire non-pondéré et pondéré d’abord sur l’ensemble de la France métropolitaine, puis par région. Comparez l’utilisation de by et keyby : pourquoi les résultats sont-ils ici identiques à votre avis ?

     

  2. Comparez l’ergonomie et les performances de la solution en base R, avec dplyr et data.table.

     

 

Cas pratique 17 Fusion de tables : nombre d’individus au chômage par ménage

Comme dans le cas pratique 13, on cherche ici à associer à chaque individu le nombre d’individus au chômage (ACTEU == "2") dans son ménage (individus avec la même valeur pour la variable IDENT).

  1. Utilisez les arguments j et by de [ pour calculer le nombre d’individus au chômage par ménage. Utilisez la structure j := pour automatiquement refusionner ce résultat avec la table de départ.

     

  2. Comment mèneriez vous ce traitement dans la logique de dplyr ? Comparez l’ergonomie et les performances de la solution en base R, dplyr et data.table.

     

Réaliser des graphiques avec R

Les cas pratiques de cette partie reposent sur l’utilisation du package ggplot2 :

install.packages("ggplot2")
library(ggplot2)

 

Cas pratique 18 Graphiques à partir de la table mpg

L’objectif de ce cas pratique est de reproduire les graphiques du support ainsi que ceux présentés par H. Wickham dans le chapitre 2 de son ouvrage ggplot2: Elegant Graphics for Data Analysis.

Dans les deux cas, la table utilisée est mpg (table d’exemple du package ggplot2) : après avoir chargé ggplot2, utilisez la fonction data() pour “rapatrier” la table mpg dans l’environnement global et tapez ? mpg pour obtenir une description détaillée de ses variables. En particulier :

  1. Utilisez les mots-clés colour, shape et size pour faire varier la représentation des points avec geom_point(). Pour chacun des mots-clés, comparez ce qu’il se passe quand vous utilisez une variable de type numérique ou une variable de type caractère ou facteur.
  2. Comparez l’utilisation du mot-clé colour dans la fonction aes() et en dehors de la fonction aes().
  3. Testez les différents types de représentation possibles en les adaptant à la nature des données à représenter. Pour chaque fonction geom_*(), recherchez dans l’aide les paramètres qui lui sont spécifiques et testez des valeurs différentes de leurs valeurs par défaut.
  4. Expérimentez les différentes possibilités de facetting.
  5. Sauvegarder un graphique dans un objet R. Utilisez ggsave() pour exporter un graphique en .png et .pdf.
  6. Affichez le code d’une fonction geom_*() et utilisez ces informations pour reconstituer manuellement l’instruction layer() correspondante.
  7. Tentez de reproduire un graphique standard de ggplot2 avec les fonctions du package graphics.

 

Cas pratique 19 Graphiques à partir de la table diamonds

La table diamonds est le second fichier de démonstration classique de ggplot2 : utilisez data() pour le “rapatrier” dans l’environnement global et tapez ? diamonds pour obtenir une description détaillée de ses variables.

  1. Représentez la relation entre poids du diamant (carat) et prix (price). Utilisez le paramètre alpha pour limiter la saturation du graphique par le très grand nombre de points. Ajoutez une droite de régression linéaire au graphique.

     

  2. Représentez l’influence de la couleur (color) sur le prix de plusieurs manières.

     

  3. Représentez la ventilation des diamants selon la qualité de leur taille (cut) et leur clarté (clarity).

     

  4. Vérifiez graphiquement si la relation entre poids et prix ne varie pas en fonction de la qualité de la taille (cut) et la clarté du diamant (clarity).

     

 

Cas pratique 20 Graphiques à partir de la table raisin

Le fichier raisin.rds comporte des informations sur la maturation du raisin dans des exploitations viticoles de Saône-et-Loire sur la période 2000-2012.

  1. Chargez ce fichier en mémoire avec la fonction readRDS() et analysez les caractéristiques des variables de ce fichier (modalités, distributions, etc.).

     

  2. Analysez la fréquence des différents cépages en fonction du temps.

     

  3. Analysez graphiquement la relation entre sucres et acidite_totale. Utilisez d’autres variables pour tenter de rendre compte de cette distribution.