Introduction à R · L2 · Section 2/6
Manipulation de données
Progression
#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 :
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).
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 manquanteread_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 :
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écroissantRésultat attendu :
1 filiere moyenne effectif ecart_type21 info 13.500 3 3.9732 eco 13.375 4 2.8143 maths 10.833 3 2.75Lecture 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.
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 :
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.
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édentTraduction : 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 :
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
- Ajoutez une colonne
mentionsur le jeu complet : « TB » au-delà de 14, « B » au-delà de 12, « AB » au-delà de 10, « AJ » sinon (case_whende dplyr). Vérifiez : Alice B, Chloé TB, Hélène TB, Farid AJ. - 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, outable). - 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.