Aller au contenu principal

Introduction à R · L2 · Section 2/6

Manipulation de données

Progression

Points d’expérience : XPSérie de jours consécutifs : · —Progression du module : — / —compris

#Manipulation de données : dplyr puis data.table

Les data frames sont le cœur de R. Cette séance apprend à les importer, les nettoyer et les transformer, d'abord avec la grammaire lisible du tidyverse, puis avec la syntaxe dense de data.table lorsque la volumétrie commande.

#Prérequis et objectifs

  • Prérequis : la section Prise en main (projet RStudio, premiers pas avec les vecteurs et data frames de l'introduction).
  • Importer un CSV avec readr, contrôler les types détectés, repérer les valeurs manquantes.
  • Enchaîner les verbes dplyr : filter, select, mutate, group_by, summarise, arrange.
  • Réécrire la même analyse en syntaxe data.table et choisir l'outil selon le volume.

#Le jeu de données fil rouge

Toute la séance utilise des notes d'étudiants, dix lignes, trois filières. Créez donnees/notes.csv dans votre projet :

csvcsv

1etudiant,filiere,note,heures_revision2Alice,info,15.0,83Bob,info,9.0,24Chloé,info,16.5,115David,maths,11.0,56Emma,maths,13.5,67Farid,maths,8.0,18Gabriel,eco,14.0,79Hélène,eco,17.0,1210Iris,eco,10.5,311Jules,eco,12.0,5

#Importer et inspecter

readr::read_csv lit le fichier et devine les types ; il affiche la spécification retenue, à vérifier systématiquement (un code postal devenu entier, une date restée texte : classiques).

rr

1library(readr)2notes <- read_csv("donnees/notes.csv")3 4# Contrôles systématiques après import5nrow(notes)                    # 10 lignes attendues6colnames(notes)                # les 4 colonnes attendues7summary(notes$note)            # min 8.0, médiane 13.25, max 17.08sum(is.na(notes$note))         # 0 : aucune note manquante

read_csv renvoie une tibble : un data frame à l'affichage amélioré (types affichés, troncature des longues tables). Toutes les fonctions dplyr s'y appliquent.

#Les verbes de dplyr

Six verbes couvrent l'essentiel, et le pipe |> les enchaîne :

rr

1library(dplyr)2 3notes |>4  filter(!is.na(note)) |>                     # garder les lignes valides5  group_by(filiere) |>                        # former les groupes6  summarise(7    moyenne    = mean(note),                  # un résumé par groupe8    effectif   = n(),9    ecart_type = sd(note)10  ) |>11  arrange(desc(moyenne))                      # tri décroissant

Résultat attendu :

texttext

1  filiere moyenne effectif ecart_type21 info     13.500        3      3.9732 eco      13.375        4      2.8143 maths    10.833        3      2.75

Lecture du pipeline, de haut en bas : on retire les notes manquantes, on regroupe par filière, on calcule trois indicateurs par groupe, on trie. Comparer à la requête SQL équivalente (GROUP BY filiere avec HAVING si filtre) : les verbes portent les mêmes noms que les clauses.

Les autres verbes utiles immédiatement :

  • select(etudiant, note) : garder des colonnes ;
  • mutate(rapport = note / heures_revision) : créer une colonne ;
  • slice_max(note, n = 3) : les trois meilleures lignes.
rr

1notes |>2  mutate(efficacite = round(note / heures_revision, 2)) |>3  slice_max(efficacite, n = 3) |>4  select(etudiant, filiere, efficacite)5# Attendu, 3 lignes triées par efficacité décroissante : Emma 2.25, Chloé 1.5, Hélène 1.42

#Remettre en forme avec tidyr

Deux formats coexistent pour les mêmes données : long (une observation par ligne) et large (une variable par colonne). ggplot2 et dplyr veulent le format long ; les tableurs produisent souvent du large. Les deux pivots de tidyr font la navette :

rr

1library(tidyr)2 3# Exemple : notes par semestre en large4notes_large <- tribble(5  ~etudiant, ~s1, ~s2,6  "Alice",   15,  16,7  "Bob",      9,  108)9 10notes_large |>11  pivot_longer(cols = s1:s2, names_to = "semestre", values_to = "note")12# Attendu : 4 lignes ; Alice/s1/15, Alice/s2/16, Bob/s1/9, Bob/s2/10

#data.table : la même chose, plus vite

Au-delà du million de lignes, data.table offre une syntaxe distinctive [i, j, by] : i filtre les lignes, j calcule, by groupe. Concise, elle devient très rapide sur les gros volumes.

rr

1library(data.table)2dt <- as.data.table(notes)3 4dt[!is.na(note),5   .(moyenne = mean(note), effectif = .N),6   by = filiere][order(-moyenne)]7# Même résultat que le pipeline dplyr précédent

Traduction : lignes !is.na(note) ; calculer moyenne et effectif (.N compte les lignes du groupe) ; par filiere ; puis tri décroissant. Le chaînage se fait par crochets successifs plutôt que par pipe.

Quel volume motive le passage ? Ordre de grandeur : en dessous de quelques centaines de milliers de lignes, dplyr est largement assez rapide et sa lisibilité gagne ; au-delà, ou pour agrégations répétées, data.table prend l'avantage. Mesurez plutôt que croire :

rr

1# Comparaison sur données simulées2library(data.table)3simul <- data.table(x = rnorm(2e6), g = sample(letters[1:10], 2e6, replace = TRUE))4 5microbenchmark::microbenchmark(6  dplyr    = dplyr::summarise(dplyr::group_by(as.data.frame(simul), g), m = mean(x)),7  datatable = simul[, .(m = mean(x)), by = g],8  times = 109)

#Exercice : pipeline complète

Sur notes.csv, écrivez une pipeline qui répond à : « pour chaque filière, la moyenne des notes des étudiants ayant révisé au moins 4 heures, avec l'effectif, triée par moyenne décroissante ». Écrivez ensuite la même analyse en data.table.

Contraintes : excluez les notes manquantes ; vérifiez vos effectifs à la main avant d'exécuter.

#Atelier

  1. Ajoutez une colonne mention sur le jeu complet : « TB » au-delà de 14, « B » au-delà de 12, « AB » au-delà de 10, « AJ » sinon (case_when de dplyr). Vérifiez : Alice B, Chloé TB, Hélène TB, Farid AJ.
  2. Comptez les étudiants par tranches de révision (cut(heures_revision, breaks = c(0, 4, 8, 12))) et croisez avec la mention dans un tableau (count, ou table).
  3. Documentez la pipeline dans un notebook Quarto : texte entre chaque bloc, export HTML. Le rapport doit être rejouable de bout en bout par un camarade.