#> [1] 2 4 6 8 10
10 novembre 2025
Si vous venez de Python, R peut sembler déroutant au premier abord. Les deux langages ont des philosophies très différentes :
numpy) : on parcourt les éléments un par un avec des boucles for.Cette différence fondamentale influence toute la manière d’écrire du code en R. Là où vous écririez une boucle en Python, vous utiliserez souvent une opération vectorielle en R.
Exemple de différence d’approche
Imaginons que nous voulons doubler chaque élément d’une liste de nombres.
En Python (approche itérative) :
En R (approche vectorielle) :
L’opération * 2 s’applique automatiquement à tous les éléments du vecteur. C’est plus concis, plus lisible, et souvent plus rapide.
Une différence importante qui peut causer des erreurs : R utilise une indexation base 1 (le premier élément est à l’indice 1), contrairement à Python qui utilise une indexation base 0.
En Python, x[1] donnerait le deuxième élément (20). Soyez vigilants lors de la transition entre les deux langages.
R a été conçu spécifiquement pour les statistiques et l’analyse de données. C’est un langage :
Python est un langage généraliste qui s’est adapté à la data science. R est né pour la data science.
Pour travailler avec R, vous utiliserez typiquement RStudio, un environnement de développement intégré (IDE) qui offre :
Vous pouvez exécuter du code R de plusieurs manières :
.R) que vous exécutez ligne par ligne ou en entier.qmd ou .Rmd) qui mélange code et texte formatéPour créer une variable en R, on utilise l’opérateur <- (préféré) ou = (acceptable mais moins idiomatique).
x <- 5 # Recommandé en R
y = 10 # Fonctionne dans cette situation, mais pas équivalent en général
print(x)#> [1] 5
#> [1] 10
L’opérateur <- peut sembler étrange au début, mais c’est la convention en R. Il se tape rapidement avec le raccourci Alt + - (ou Option + - sur Mac) dans RStudio.
R possède un système d’aide très complet. Pour obtenir de l’aide sur une fonction :
Si vous ne connaissez pas le nom exact de la fonction, utilisez la recherche :
R possède quatre types de données primitifs principaux :
1. Numeric (nombres réels)
Par défaut, tous les nombres sont de type numeric (équivalent des float en Python).
2. Integer (nombres entiers)
Pour forcer un entier, on ajoute le suffixe L :
3. Character (chaînes de caractères)
Les chaînes se délimitent avec des guillemets simples ou doubles :
4. Logical (booléens)
Les valeurs logiques sont TRUE et FALSE (en majuscules, ou T et F en abrégé) :
Pour afficher le contenu d’une variable, plusieurs options :
Le code est lu bien plus souvent qu’il n’est écrit. Un style cohérent rend votre code :
Adopter de bonnes habitudes dès le début vous fera gagner énormément de temps par la suite.
En R, la convention la plus répandue est le snake_case : mots en minuscules séparés par des underscores (_).
# Bon : snake_case (recommandé)
temperature_celsius <- 25
nombre_etudiants <- 42
calculer_moyenne <- function(x) mean(x)
# Acceptable : camelCase (moins courant en R)
temperatureCelsius <- 25
# À éviter : points (confusion avec le système S3)
temperature.celsius <- 25
# À éviter : noms trop courts et peu explicites
temp <- 25
t <- 25Privilégiez des noms explicites plutôt que des abréviations obscures. Le code temperature_celsius est immédiatement compréhensible, contrairement à temp ou t.
Pour les constantes globales, utilisez les MAJUSCULES :
Un bon espacement améliore considérablement la lisibilité du code.
Mettez toujours des espaces autour des opérateurs d’assignation et arithmétiques :
Mettez une espace après chaque virgule, mais pas avant :
Mettez un espace après les mots-clés de contrôle (if, for, while), mais pas après les noms de fonctions :
#> [1] "positif"
#> [1] "positif"
L’indentation standard en R est de 2 espaces par niveau. N’utilisez jamais de tabulations, uniquement des espaces.
# Bon : indentation à 2 espaces
if (x > 0) {
if (y > 0) {
print("x et y sont positifs")
} else {
print("seul x est positif")
}
}#> [1] "x et y sont positifs"
#> [1] 2
#> [1] 4
#> [1] 6
#> [1] 8
#> [1] 10
La convention veut que l’accolade ouvrante { soit sur la même ligne que l’instruction, et l’accolade fermante } seule sur sa ligne.
RStudio peut réindenter automatiquement votre code : sélectionnez le code et appuyez sur Ctrl + I (ou Cmd + I sur Mac).
Limitez vos lignes à 80 caractères maximum pour une meilleure lisibilité. RStudio affiche une ligne verticale grise à 80 caractères pour vous guider.
Pour les longues instructions, découpez-les sur plusieurs lignes :
# Bon : découpage pour rester sous 80 caractères
resultat <- fonction_avec_nom_long(
premier_argument = valeur1,
deuxieme_argument = valeur2,
troisieme_argument = valeur3,
quatrieme_argument = valeur4
)
# Ou pour un long vecteur
vecteur <- c(
"premier", "deuxieme", "troisieme",
"quatrieme", "cinquieme", "sixieme"
)Les commentaires commencent par # suivi d’une espace.
Expliquez le pourquoi, pas le quoi. Le code lui-même devrait montrer ce qu’il fait.
Pour les commentaires courts, vous pouvez les placer en fin de ligne :
RStudio intègre plusieurs outils pour vous aider :
styler peut reformater automatiquement votre code selon les conventionslintr analyse votre code et signale les problèmes de stylePour installer ces packages (nous verrons les packages plus en détail plus tard) :
Pour une référence complète sur le style R, consultez le Style Guide de Hadley Wickham, qui fait référence dans la communauté R.
Le vecteur est la structure de données la plus importante en R. Comprendre les vecteurs est essentiel pour maîtriser le langage. Tous les autres objets en R (matrices, data frames, listes) sont construits à partir de vecteurs ou sont des extensions de ceux-ci.
Un vecteur en R est une collection ordonnée d’éléments du même type. C’est ce qu’on appelle un vecteur atomique.
c() : combineLa fonction la plus utilisée pour créer un vecteur est c() (pour “combine” ou “concatenate”) :
#> [1] 1 2 3 4 5
#> [1] "Alice" "Bob" "Charlie"
#> [1] TRUE FALSE TRUE TRUE
On peut aussi combiner des vecteurs entre eux :
: : séquences simplesPour créer une séquence d’entiers consécutifs, l’opérateur : est très pratique :
seq() : séquences avancéesPour plus de contrôle, utilisez seq() qui permet de spécifier le pas :
rep() : répétitionsPour répéter des valeurs :
#> [1] 5 5 5 5 5 5 5 5 5 5
#> [1] 1 2 3 1 2 3 1 2 3
#> [1] 1 1 1 2 2 2 3 3 3
#> [1] 9 9 8 8 8 7 7 7 7
Un vecteur ne peut contenir qu’un seul type de données. Si vous essayez de mélanger des types, R effectuera une coercition automatique vers le type le plus général.
Hiérarchie des types (du plus spécifique au plus général) : logical < integer < numeric < character
# Mélange de numeric et character → tout devient character
melange1 <- c(1, 2, "trois")
print(melange1)#> [1] "1" "2" "trois"
#> [1] "character"
# Mélange de logical et numeric → logical devient numeric (TRUE = 1, FALSE = 0)
melange2 <- c(TRUE, FALSE, 1, 2)
print(melange2)#> [1] 1 0 1 2
#> [1] "numeric"
# Mélange de logical et character → tout devient character
melange3 <- c(TRUE, "faux")
print(melange3)#> [1] "TRUE" "faux"
#> [1] "character"
Vous pouvez forcer la conversion avec les fonctions as.*() :
#> [1] "1" "2" "3"
#> [1] 1 2 3
#> [1] FALSE TRUE TRUE FALSE
Attention aux conversions impossibles :
impossible <- as.numeric(c("un", "deux", "trois"))
print(impossible) # Produit des NA avec un avertissement#> [1] NA NA NA
C’est ici que la puissance de R se révèle. Les opérations s’appliquent élément par élément automatiquement.
#> [1] 11 22 33 44 55
#> [1] 10 40 90 160 250
#> [1] 1 4 9 16 25
#> [1] 1.000000 1.414214 1.732051 2.000000 2.236068
Avec une constante, l’opération s’applique à tous les éléments :
Quand deux vecteurs de longueurs différentes sont combinés, R recycle le plus court :
#> [1] 11 22 13 24 15 26
R vous avertit si la longueur du plus long n’est pas un multiple du plus court :
#> [1] 11 22 13 24 15
Le recyclage est très utile pour appliquer une opération avec une constante, car un scalaire est en fait un vecteur de longueur 1 :
R possède de nombreuses fonctions qui opèrent sur des vecteurs entiers :
#> [1] 6
#> [1] 27
#> [1] 4.5
#> [1] 4
#> [1] 3.082207
#> [1] 9.5
#> [1] 1
#> [1] 9
#> [1] 1 9
#> [1] 1 2 3 5 7 9
#> [1] 9 7 5 3 2 1
Les comparaisons produisent des vecteurs logiques :
#> [1] FALSE TRUE FALSE TRUE FALSE
#> [1] FALSE TRUE FALSE FALSE FALSE
#> [1] FALSE TRUE TRUE FALSE FALSE
#> [1] 2
L’indexation est fondamentale en R. Il existe plusieurs façons d’accéder aux éléments d’un vecteur.
Rappel : l’indexation commence à 1 en R.
#> [1] 10
#> [1] 30
#> [1] 50
#> [1] 10 30 50
#> [1] 10 20 30
#> [1] 10 10 20 20 30
Les indices négatifs excluent des éléments :
#> [1] 20 30 40 50
#> [1] 10 20 30 40
#> [1] 10 30 50
#> [1] 40 50
Attention : on ne peut pas mélanger indices positifs et négatifs.
L’indexation logique est extrêmement puissante et très utilisée en R :
#> [1] FALSE TRUE FALSE TRUE FALSE TRUE FALSE
#> [1] 5 8 9
#> [1] 5 8 9
#> [1] 5 3 4
#> [1] 5 9
L’indexation logique est la méthode privilégiée pour filtrer des données en R.
On peut nommer les éléments d’un vecteur :
#> Paris Londres Berlin Rome
#> 15 12 10 18
#> Paris
#> 15
#> Paris Rome
#> 15 18
#> [1] "Paris" "Londres" "Berlin" "Rome"
#> Par Lon Ber Rom
#> 15 12 10 18
On peut modifier des éléments en combinant indexation et assignation :
#> [1] 1 2 99 4 5
#> [1] 11 2 99 4 55
#> [1] 11 2 0 4 0
R possède plusieurs valeurs spéciales pour représenter des situations particulières.
NA : Not Available (valeur manquante)NA représente une donnée manquante ou indisponible :
Les opérations avec NA propagent le manquant :
#> [1] NA
#> [1] NA
#> [1] 12
#> [1] 3
Tester la présence de NA :
#> [1] FALSE FALSE TRUE FALSE FALSE
#> [1] 1
#> [1] 1 2 4 5
#> [1] 1 2 4 5
#> attr(,"na.action")
#> [1] 3
#> attr(,"class")
#> [1] "omit"
NULL : l’absence d’objetNULL représente l’absence d’un objet (différent d’une valeur manquante) :
NaN : Not a Number (résultat indéfini)NaN apparaît lors d’opérations mathématiques indéfinies :
Inf et -Inf : infiniReprésentent l’infini mathématique :
#> [1] Inf
#> [1] -Inf
#> [1] TRUE
#> [1] FALSE
#> [1] Inf
#> [1] Inf
#> [1] NaN
Les vecteurs sont la structure fondamentale de R. Retenez :
c(), :, seq(), rep()NA, NULL, NaN, InfMaintenant que vous maîtrisez les vecteurs et leurs opérations, voyons les structures de contrôle qui vous permettront d’écrire des programmes plus complexes.
Si vous venez de Python, vous connaissez déjà les structures de contrôle (if/else, for, while). Elles existent aussi en R avec une syntaxe très similaire.
Cependant, en R, il est souvent préférable d’utiliser des opérations vectorielles plutôt que des boucles. C’est plus concis, plus lisible, et généralement plus rapide.
Quand utiliser les structures de contrôle ? - Dans les fonctions pour la logique conditionnelle - Quand la vectorisation n’est pas possible (dépendances entre itérations) - Pour des opérations complexes qui ne se prêtent pas à la vectorisation
Message clé : apprenez-les, mais cherchez d’abord une solution vectorielle !
Avec else :
else ifage <- 25
permis <- TRUE
if (age >= 18 & permis) {
print("Peut conduire")
} else {
print("Ne peut pas conduire")
}#> [1] "Peut conduire"
Opérateurs logiques : - & : ET (évalue tout) - | : OU (évalue tout) - && : ET (court-circuit, s’arrête au premier FALSE) - || : OU (court-circuit, s’arrête au premier TRUE)
Pour appliquer une condition à un vecteur entier, utilisez ifelse() :
# Approche non-vectorielle (lente, à éviter)
notes <- c(15, 8, 18, 12, 9)
resultats <- character(length(notes))
for (i in 1:length(notes)) {
if (notes[i] >= 10) {
resultats[i] <- "Admis"
} else {
resultats[i] <- "Refusé"
}
}
print(resultats)#> [1] "Admis" "Refusé" "Admis" "Admis" "Refusé"
# Approche vectorielle (rapide, recommandée)
notes <- c(15, 8, 18, 12, 9)
resultats <- ifelse(notes >= 10, "Admis", "Refusé")
print(resultats)#> [1] "Admis" "Refusé" "Admis" "Admis" "Refusé"
Syntaxe : ifelse(condition, valeur_si_vrai, valeur_si_faux)
Exemple plus complexe :
# Calculer les carrés de 1 à 5
carres <- numeric(5) # Pré-allouer le vecteur (important pour la performance)
for (i in 1:5) {
carres[i] <- i^2
}
print(carres)#> [1] 1 4 9 16 25
Note importante : pré-allouez toujours vos vecteurs avant une boucle ! Agrandir un vecteur à chaque itération est très lent.
Principe : essayez toujours de vectoriser d’abord. La boucle est une solution de secours.
Exemple où la boucle est justifiée (suite de Fibonacci) :
# Chaque élément dépend des précédents → vectorisation impossible
n <- 10
fib <- numeric(n)
fib[1] <- 1
fib[2] <- 1
for (i in 3:n) {
fib[i] <- fib[i - 1] + fib[i - 2] # Dépend des valeurs précédentes
}
print(fib)#> [1] 1 1 2 3 5 8 13 21 34 55
La boucle while continue tant qu’une condition est vraie :
# Compter jusqu'à 5
compteur <- 1
while (compteur <= 5) {
print(compteur)
compteur <- compteur + 1
}#> [1] 1
#> [1] 2
#> [1] 3
#> [1] 4
#> [1] 5
Attention : risque de boucle infinie si la condition n’est jamais fausse !
break : sortir d’une bouclenext : passer à l’itération suivante# Chercher le premier nombre divisible par 7 après 50
for (i in 51:100) {
if (i %% 7 == 0) {
print(paste("Trouvé :", i))
break
}
}#> [1] "Trouvé : 56"
On peut imbriquer des boucles, mais attention à la performance :
#> 1 x 1 = 1
#> 1 x 2 = 2
#> 1 x 3 = 3
#> 2 x 1 = 2
#> 2 x 2 = 4
#> 2 x 3 = 6
#> 3 x 1 = 3
#> 3 x 2 = 6
#> 3 x 3 = 9
Alternative vectorielle (souvent plus efficace) :
#> [,1] [,2] [,3]
#> [1,] 1 2 3
#> [2,] 2 4 6
#> [3,] 3 6 9
Les structures de contrôle en R sont similaires à Python :
if/else : conditions simples (scalaires)ifelse() : version vectorisée pour les vecteurs ⭐ Privilégiez celle-ci !for : boucle sur une séquence ou un vecteurwhile : boucle tant qu’une condition est vraiebreak : sortir d’une bouclenext : passer à l’itération suivantePhilosophie R : 1. ✅ Cherchez d’abord une solution vectorielle (plus rapide, plus lisible) 2. ✅ Utilisez ifelse() pour les conditions sur des vecteurs 3. ✅ Utilisez les boucles quand nécessaire (dépendances entre itérations) 4. ⚠️ Pré-allouez les vecteurs avant les boucles 5. ⚠️ Évitez les boucles imbriquées quand possible (cherchez des alternatives vectorielles)
Dans les sections suivantes, vous verrez de nombreux exemples où les opérations vectorielles remplacent avantageusement les boucles.
Les matrices en R ne sont rien d’autre que des vecteurs avec un attribut de dimension. Comprendre cette relation est essentiel : une matrice stocke ses données dans un vecteur, mais les organise en lignes et colonnes.
Les matrices sont moins centrales que les data frames pour l’analyse de données, mais elles introduisent l’indexation bidimensionnelle [i, j] qui est identique pour les data frames. C’est donc une excellente préparation.
Commençons par créer un vecteur et le transformer en matrice :
#> [1] 1 2 3 4 5 6 7 8 9 10 11 12
#> [,1] [,2] [,3] [,4]
#> [1,] 1 4 7 10
#> [2,] 2 5 8 11
#> [3,] 3 6 9 12
#> [1] "matrix" "array"
Les données sont stockées par colonne (ordre “column-major”) : le vecteur remplit d’abord la première colonne, puis la deuxième, etc.
matrix()La manière standard de créer une matrice est d’utiliser matrix() :
#> [,1] [,2] [,3] [,4]
#> [1,] 1 4 7 10
#> [2,] 2 5 8 11
#> [3,] 3 6 9 12
# On peut spécifier seulement nrow ou ncol, l'autre est déduit
M2 <- matrix(1:12, nrow = 3)
print(M2)#> [,1] [,2] [,3] [,4]
#> [1,] 1 4 7 10
#> [2,] 2 5 8 11
#> [3,] 3 6 9 12
# Par défaut, remplissage par colonne. On peut changer :
M3 <- matrix(1:12, nrow = 3, byrow = TRUE)
print(M3)#> [,1] [,2] [,3] [,4]
#> [1,] 1 2 3 4
#> [2,] 5 6 7 8
#> [3,] 9 10 11 12
rbind() et cbind()On peut construire des matrices en combinant des vecteurs :
# Combiner par lignes (row bind)
v1 <- c(1, 2, 3)
v2 <- c(4, 5, 6)
v3 <- c(7, 8, 9)
M_rbind <- rbind(v1, v2, v3)
print(M_rbind)#> [,1] [,2] [,3]
#> v1 1 2 3
#> v2 4 5 6
#> v3 7 8 9
#> v1 v2 v3
#> [1,] 1 4 7
#> [2,] 2 5 8
#> [3,] 3 6 9
C’est le point clé de cette section. L’indexation des matrices utilise la syntaxe [ligne, colonne], identique à celle des data frames.
#> [,1] [,2] [,3] [,4]
#> [1,] 1 4 7 10
#> [2,] 2 5 8 11
#> [3,] 3 6 9 12
#> [1] 8
#> [1] 2 5 8 11
#> [1] 7 8 9
On peut extraire des sous-ensembles :
M <- matrix(1:12, nrow = 3, ncol = 4)
# Plusieurs lignes et colonnes
M[c(1, 3), c(2, 4)] # Lignes 1 et 3, colonnes 2 et 4#> [,1] [,2]
#> [1,] 4 10
#> [2,] 6 12
#> [,1] [,2] [,3]
#> [1,] 4 7 10
#> [2,] 5 8 11
#> [,1] [,2] [,3] [,4]
#> [1,] 2 5 8 11
#> [2,] 3 6 9 12
#> [,1] [,2]
#> [1,] 4 10
#> [2,] 5 11
#> [3,] 6 12
Comme pour les vecteurs, on peut utiliser des conditions :
#> [,1] [,2] [,3] [,4]
#> [1,] 1 4 7 10
#> [2,] 2 5 99 11
#> [3,] 3 6 9 12
#> [,1] [,2] [,3] [,4]
#> [1,] 10 20 30 40
#> [2,] 2 5 99 11
#> [3,] 3 6 9 12
#> [,1] [,2] [,3] [,4]
#> [1,] 10 0 0 0
#> [2,] 2 5 0 0
#> [3,] 3 6 9 0
Comme pour les vecteurs, les opérations sont élément par élément :
Le recyclage s’applique aussi :
#> [1] 78
#> [1] 6.5
#> [1] 12
#> [1] 1
#> [1] 22 26 30
#> [1] 5.5 6.5 7.5
#> [1] 6 15 24 33
#> [1] 2 5 8 11
La fonction apply() permet d’appliquer une fonction sur les lignes ou colonnes :
M <- matrix(1:12, nrow = 3, ncol = 4)
# Appliquer sur les lignes (MARGIN = 1)
apply(M, 1, sum) # Équivalent à rowSums(M)#> [1] 22 26 30
#> [1] 10 11 12
#> [1] 6 15 24 33
#> [1] 2 5 8 11
# Fonction personnalisée
apply(M, 1, function(ligne) max(ligne) - min(ligne)) # Étendue de chaque ligne#> [1] 9 9 9
Nous n’aborderons pas l’algèbre linéaire dans ce cours, car notre objectif est l’analyse de données. Sachez simplement que R possède des opérateurs spécifiques pour les opérations matricielles (%*% pour la multiplication matricielle, t() pour la transposition, etc.), mais nous n’en aurons pas besoin.
Les matrices sont des vecteurs organisés en 2 dimensions :
matrix(), rbind(), cbind(), ou dim()[i, j], [i, ], [, j] ← Important pour les data frames !rowSums(), colMeans(), etc.apply() : appliquer une fonction sur lignes ou colonnesL’indexation bidimensionnelle que vous venez d’apprendre est exactement la même pour les data frames, qui sont l’objet central de l’analyse de données en R.
En statistiques, on distingue deux grands types de variables :
Les deux termes “variable qualitative” et “variable catégorielle” sont synonymes et sont utilisés indifféremment en statistique.
En R, les variables catégorielles sont représentées par un type d’objet spécifique : les facteurs (type factor).
Vous pourriez vous demander : “Pourquoi ne pas simplement utiliser des vecteurs de caractères ?” Plusieurs raisons :
lm(), glm(), etc.) traitent automatiquement les facteurs comme des variables catégoriellesfactor()La fonction factor() convertit un vecteur en facteur :
# À partir d'un vecteur de caractères
couleurs <- c("rouge", "bleu", "rouge", "vert", "bleu", "rouge")
f_couleurs <- factor(couleurs)
print(f_couleurs)#> [1] rouge bleu rouge vert bleu rouge
#> Levels: bleu rouge vert
#> [1] "factor"
Notez l’affichage : R indique les “Levels” (modalités en anglais).
as.factor()Une alternative équivalente :
Par défaut, les modalités sont triées par ordre alphabétique. On peut spécifier un ordre différent :
taille <- c("M", "S", "L", "M", "S", "XL")
f_taille <- factor(taille, levels = c("S", "M", "L", "XL"))
print(f_taille)#> [1] M S L M S XL
#> Levels: S M L XL
Cet ordre sera respecté dans les graphiques et les tableaux.
Si une valeur n’est pas dans les levels spécifiés, elle devient NA :
notes <- c("A", "B", "C", "D", "E")
f_notes <- factor(notes, levels = c("A", "B", "C", "D")) # E n'est pas autorisé
print(f_notes)#> [1] A B C D <NA>
#> Levels: A B C D
C’est le point clé pour comprendre les facteurs. Un facteur est composé de deux éléments distincts :
levels qui associe chaque code à une étiquette#> [1] rouge bleu vert bleu rouge
#> Levels: bleu rouge vert
#> [1] 2 1 3 1 2
#> [1] "bleu" "rouge" "vert"
Représentation conceptuelle :
Facteur couleurs :
Codes internes : [2, 1, 3, 1, 2]
↓ ↓ ↓ ↓ ↓
Table (levels) : ["bleu", "rouge", "vert"]
1 2 3
Affichage : rouge, bleu, vert, bleu, rouge
R stocke uniquement les entiers 1, 2, 3 en mémoire. Pour afficher les valeurs, il consulte la table de correspondance.
Les codes internes ne changent jamais lorsqu’on modifie les levels. Seule la table de correspondance change. C’est cette propriété qui permet de renommer et fusionner efficacement des modalités.
Plusieurs fonctions utiles :
couleurs <- factor(c("rouge", "bleu", "vert", "bleu", "rouge", "vert", "vert"))
# Les modalités (niveaux)
levels(couleurs)#> [1] "bleu" "rouge" "vert"
#> [1] 3
#> couleurs
#> bleu rouge vert
#> 2 2 3
#> bleu rouge vert
#> 2 2 3
Modifier les levels change les étiquettes mais pas les codes internes :
#> [1] H F H F H
#> Levels: F H
#> [1] "F" "H"
# Renommer les modalités (ordre : "F" puis "H" par ordre alphabétique)
levels(sexe) <- c("Femme", "Homme")
print(sexe)#> [1] Homme Femme Homme Femme Homme
#> Levels: Femme Homme
#> [1] 2 1 2 1 2
Attention à l’ordre : les nouveaux noms sont attribués dans l’ordre des anciens levels (alphabétique par défaut).
#> [1] "A" "B" "C"
#> [1] A Beta C A Beta
#> Levels: A Beta C
C’est une opération très courante en analyse de données : regrouper plusieurs catégories en une seule.
Lorsqu’on assigne le même nom à plusieurs levels, on fusionne ces modalités :
#> [1] 0 1 2 1 0 2 1
#> Levels: 0 1 2
#> [1] "0" "1" "2"
#> [1] "Codes internes avant :"
#> [1] 1 2 3 2 1 3 2
#> [1] 0 1 1 1 0 1 1
#> Levels: 0 1
#> [1] "Codes internes après :"
#> [1] 1 2 2 2 1 2 2
#> [1] "0" "1"
Observation cruciale : les codes internes restent 1, 2, 3… même si les modalités “1” et “2” ont maintenant la même étiquette. Les anciennes valeurs “1” et “2” sont maintenant indiscernables à l’affichage, mais R conserve leur distinction interne.
age_cat <- factor(c("18-25", "26-35", "36-45", "46-55", "56+", "18-25", "46-55"))
print(table(age_cat))#> age_cat
#> 18-25 26-35 36-45 46-55 56+
#> 2 1 1 2 1
# Regrouper en trois catégories
levels(age_cat) <- c("Jeune", "Jeune", "Adulte", "Adulte", "Senior")
print(age_cat)#> [1] Jeune Jeune Adulte Adulte Senior Jeune Adulte
#> Levels: Jeune Adulte Senior
#> age_cat
#> Jeune Adulte Senior
#> 3 3 1
#> [1] 1 1 2 2 3 1 2
Après une fusion, les anciens codes internes restent. Pour obtenir un facteur “propre” avec des codes consécutifs :
x <- factor(c("0", "1", "2", "1", "0", "2"))
levels(x)[c(2, 3)] <- "1"
# Codes internes : 1 2 3 2 1 3 (trois codes distincts)
as.integer(x)#> [1] 1 2 2 2 1 2
# Reconstruire le facteur pour obtenir des codes consécutifs
x_propre <- factor(x)
as.integer(x_propre) # Maintenant : 1 2 2 2 1 2 (deux codes distincts)#> [1] 1 2 2 2 1 2
# Alternative : droplevels() (utile après suppression de lignes)
x_propre2 <- droplevels(x)
as.integer(x_propre2)#> [1] 1 2 2 2 1 2
En pratique, cette différence importe peu pour l’analyse, mais c’est important de comprendre le mécanisme interne.
Attention : c’est une erreur très fréquente, même pour les utilisateurs expérimentés !
#> [1] 10 20 15 18 12
#> Levels: 10 12 15 18 20
# PIÈGE : as.numeric() retourne les codes internes, pas les valeurs !
as.numeric(notes) # Donne 1 4 3 2 5 (les codes), PAS 10 20 15 18 12#> [1] 1 5 3 4 2
# Solution : convertir d'abord en caractères, puis en numérique
as.numeric(as.character(notes)) # Correct : 10 20 15 18 12#> [1] 10 20 15 18 12
Règle à retenir : pour convertir un facteur en numérique, toujours passer par as.character() d’abord.
notes <- factor(c("10", "20", "15", "18", "12"))
# Les codes internes (dans l'ordre alphabétique des levels)
as.integer(notes) # 1, 4, 3, 2, 5#> [1] 1 5 3 4 2
#> [1] "10" "12" "15" "18" "20"
Pour les variables ordinales (catégories avec un ordre naturel), on utilise des facteurs ordonnés :
satisfaction <- c("Faible", "Élevée", "Moyenne", "Faible", "Élevée")
# Facteur ordonné
f_satisfaction <- factor(
satisfaction,
levels = c("Faible", "Moyenne", "Élevée"),
ordered = TRUE
)
print(f_satisfaction)#> [1] Faible Élevée Moyenne Faible Élevée
#> Levels: Faible < Moyenne < Élevée
#> [1] TRUE
x <- factor(c("A", "B", "C", "A", "B"))
x_filtre <- x[x != "C"] # Supprime les observations "C"
print(x_filtre)#> [1] A B A B
#> Levels: A B C
#> [1] "A" "B" "C"
# Supprimer les levels non utilisés
x_filtre <- droplevels(x_filtre)
levels(x_filtre) # Maintenant "C" a disparu#> [1] "A" "B"
Les facteurs représentent des variables catégorielles en R :
factor() ou as.factor()levels(), nlevels(), table()levels(f) <- nouveaux_noms (modifie la table, pas les codes)levels(f)[c(2,3)] <- "nouveau"factor() ou droplevels() pour “nettoyer”)as.numeric(as.character(f)), jamais as.numeric(f) directementordered = TRUELes facteurs sont essentiels pour travailler avec des données catégorielles, particulièrement dans les data frames que nous verrons bientôt.
Jusqu’à présent, nous avons travaillé avec des vecteurs qui ont une contrainte importante : tous les éléments doivent être du même type. Si vous mélangez des types, R effectue une coercition automatique.
Les listes lèvent cette contrainte. Une liste est un conteneur qui peut contenir des éléments de types différents, et même d’autres structures complexes (vecteurs, matrices, facteurs, ou même d’autres listes).
#> [1] "1" "deux" "TRUE"
#> [1] "character"
#> [[1]]
#> [1] 1
#>
#> [[2]]
#> [1] "deux"
#>
#> [[3]]
#> [1] TRUE
#> [1] "list"
Les listes sont extrêmement flexibles et sont à la base de nombreuses structures en R, notamment les data frames (qui sont des listes de vecteurs de même longueur).
list()La fonction list() crée une liste en combinant n’importe quels objets :
On peut nommer les éléments d’une liste pour un accès plus intuitif :
personne <- list(
nom = "Alice",
age = 30,
ville = "Paris",
scores = c(85, 92, 78)
)
print(personne)#> $nom
#> [1] "Alice"
#>
#> $age
#> [1] 30
#>
#> $ville
#> [1] "Paris"
#>
#> $scores
#> [1] 85 92 78
Les noms rendent la liste beaucoup plus lisible et facilitent l’accès aux éléments.
Les listes peuvent contenir n’importe quel type d’objet R :
donnees <- list(
vecteur = c(1, 2, 3, 4, 5),
matrice = matrix(1:6, nrow = 2),
facteur = factor(c("A", "B", "A")),
autre_liste = list(x = 10, y = 20)
)
print(donnees)#> $vecteur
#> [1] 1 2 3 4 5
#>
#> $matrice
#> [,1] [,2] [,3]
#> [1,] 1 3 5
#> [2,] 2 4 6
#>
#> $facteur
#> [1] A B A
#> Levels: A B
#>
#> $autre_liste
#> $autre_liste$x
#> [1] 10
#>
#> $autre_liste$y
#> [1] 20
C’est le point le plus important (et le plus source de confusion) concernant les listes. Il existe deux façons d’accéder aux éléments, avec des résultats très différents.
[[]] : extraction de l’élémentLes doubles crochets [[]] extraient l’élément lui-même (son contenu) :
[] : sous-listeLes crochets simples [] retournent toujours une sous-liste contenant les éléments sélectionnés :
Imaginez une liste comme un train avec des wagons :
ma_liste[1] : vous obtenez le wagon 1 (qui est toujours un wagon)ma_liste[[1]] : vous obtenez le contenu du wagon 1 (ce qu’il y a dedans)Cette différence a des implications importantes pour les opérations :
ma_liste <- list(
nombres = c(2, 4, 6)
)
# Ceci NE FONCTIONNE PAS : on ne peut pas multiplier une liste
# ma_liste[1] * 2 # Erreur !
# Ceci FONCTIONNE : on multiplie le vecteur extrait
ma_liste[[1]] * 2 # Donne c(4, 8, 12)#> [1] 4 8 12
Pour les listes nommées, il existe des méthodes d’accès plus pratiques.
$L’opérateur $ est un raccourci pour [[]] avec des noms :
#> [1] "Alice"
#> [1] 30
#> [1] "Alice"
#> [1] 30
$ est plus concis et plus lisible pour un usage interactif (console, scripts simples).
[[]] et nomsCependant, [[]] est indispensable en programmation (boucles, fonctions) car il permet d’utiliser des variables :
personne <- list(nom = "Alice", age = 30, ville = "Paris")
# Usage interactif : $ est pratique
personne$nom # "Alice"#> [1] "Alice"
# Usage programmatique : [[]] permet les noms dynamiques
champ <- "age"
personne[[champ]] # 30 - le nom vient d'une variable#> [1] 30
# Exemple pratique : extraire plusieurs champs en boucle
champs <- c("nom", "ville")
for (ch in champs) {
print(personne[[ch]])
}#> [1] "Alice"
#> [1] "Paris"
En résumé : $ pour l’usage quotidien, [[]] pour la programmation.
[] et nomsAvec [], on obtient toujours une sous-liste :
Pour supprimer un élément, on lui assigne NULL :
#> $nom
#> [1] "Alice"
#>
#> $age
#> [1] 30
Les listes peuvent contenir d’autres listes, créant des structures hiérarchiques :
entreprise <- list(
nom = "TechCorp",
employes = list(
employe1 = list(nom = "Alice", age = 30),
employe2 = list(nom = "Bob", age = 25)
),
siege = list(ville = "Paris", pays = "France")
)
print(entreprise)#> $nom
#> [1] "TechCorp"
#>
#> $employes
#> $employes$employe1
#> $employes$employe1$nom
#> [1] "Alice"
#>
#> $employes$employe1$age
#> [1] 30
#>
#>
#> $employes$employe2
#> $employes$employe2$nom
#> [1] "Bob"
#>
#> $employes$employe2$age
#> [1] 25
#>
#>
#>
#> $siege
#> $siege$ville
#> [1] "Paris"
#>
#> $siege$pays
#> [1] "France"
On enchaîne les opérateurs d’accès :
#> [1] 3
#> [1] "a" "b" "c"
#> $premier
#> [1] 1
#>
#> $deuxieme
#> [1] 2
#>
#> $troisieme
#> [1] 3
str()La fonction str() (structure) est très utile pour visualiser des listes complexes :
donnees <- list(
vecteur = c(1, 2, 3),
matrice = matrix(1:6, nrow = 2),
sous_liste = list(x = 10, y = 20)
)
str(donnees)#> List of 3
#> $ vecteur : num [1:3] 1 2 3
#> $ matrice : int [1:2, 1:3] 1 2 3 4 5 6
#> $ sous_liste:List of 2
#> ..$ x: num 10
#> ..$ y: num 20
str() montre la structure hiérarchique de manière compacte, c’est un outil essentiel pour explorer des objets complexes.
Une opération courante consiste à appliquer une fonction à chaque élément d’une liste.
lapply() : retourne une listelapply() (pour “list apply”) applique une fonction à chaque élément et retourne une liste :
sapply() : simplifie le résultatsapply() (pour “simplify apply”) fait la même chose mais essaie de simplifier le résultat en vecteur si possible :
nombres <- list(
a = c(1, 2, 3),
b = c(4, 5, 6),
c = c(7, 8, 9)
)
# Calculer la moyenne de chaque élément
moyennes <- sapply(nombres, mean)
print(moyennes)#> a b c
#> 2 5 8
#> [1] "numeric"
sapply() est souvent plus pratique car il retourne un vecteur quand c’est possible.
On peut définir des fonctions directement dans lapply() ou sapply() :
nombres <- list(
a = c(1, 2, 3, 4),
b = c(5, 6, 7, 8)
)
# Fonction anonyme : calculer l'étendue (max - min)
etendues <- sapply(nombres, function(x) max(x) - min(x))
print(etendues)#> a b
#> 3 3
unlist() “aplatit” une liste en vecteur :
#> $a
#> [1] 1 2 3
#>
#> $b
#> [1] 4 5 6
#>
#> $c
#> [1] 7 8 9
#> a1 a2 a3 b1 b2 b3 c1 c2 c3
#> 1 2 3 4 5 6 7 8 9
#> [1] "integer"
Attention : si la liste contient des types différents, la coercition habituelle s’applique :
#> a1 a2 a3 b1 b2
#> "1" "2" "3" "x" "y"
Maintenant que vous comprenez les listes, vous êtes prêt pour les data frames. Un data frame est une liste avec des contraintes particulières :
Cette structure permet de représenter des tableaux de données avec des colonnes de types différents, exactement ce dont on a besoin pour l’analyse de données.
# Un data frame est conceptuellement une liste de vecteurs
donnees <- list(
nom = c("Alice", "Bob", "Charlie"),
age = c(25, 30, 35),
ville = c("Paris", "Lyon", "Marseille")
)
# On peut le convertir en data frame
df <- as.data.frame(donnees)
print(df)#> nom age ville
#> 1 Alice 25 Paris
#> 2 Bob 30 Lyon
#> 3 Charlie 35 Marseille
#> [1] "data.frame"
#> [1] "data.frame"
#> [1] TRUE
Nous verrons les data frames en détail dans la section suivante.
Les listes sont des conteneurs flexibles pour des données hétérogènes :
list() peut contenir n’importe quel type d’objet[[i]] ou $nom : extrait l’élément (son contenu)[i] : retourne une sous-liste$ est le plus pratique (équivalent à [[""]])str() pour visualiser la structurelapply() retourne une liste, sapply() simplifie en vecteurunlist() pour aplatir en vecteurComprendre la différence entre [[]] et [] est essentiel, car vous la retrouverez constamment dans le travail avec les listes et les data frames.
Le data frame est la structure de données la plus importante pour l’analyse de données en R. C’est l’équivalent d’un tableau Excel ou d’une table de base de données : des lignes représentant des observations et des colonnes représentant des variables.
Maintenant que vous connaissez les listes, vous pouvez comprendre ce qu’est vraiment un data frame : c’est une liste de vecteurs de même longueur, où chaque vecteur représente une colonne.
# Un data frame = liste de vecteurs de même longueur
df <- data.frame(
nom = c("Alice", "Bob", "Charlie"),
age = c(25, 30, 35),
ville = c("Paris", "Lyon", "Marseille")
)
print(df)#> nom age ville
#> 1 Alice 25 Paris
#> 2 Bob 30 Lyon
#> 3 Charlie 35 Marseille
#> [1] TRUE
#> [1] "data.frame"
Propriétés clés : - Chaque colonne peut être d’un type différent (numeric, character, factor, logical…) - Toutes les colonnes ont la même longueur (même nombre de lignes) - Chaque colonne a un nom - Structure rectangulaire : lignes × colonnes
data.frame()etudiants <- data.frame(
nom = c("Alice", "Bob", "Charlie", "Diana"),
age = c(20, 22, 21, 23),
note = c(15.5, 12.0, 18.0, 14.5),
mention = c(TRUE, FALSE, TRUE, TRUE)
)
print(etudiants)#> nom age note mention
#> 1 Alice 20 15.5 TRUE
#> 2 Bob 22 12.0 FALSE
#> 3 Charlie 21 18.0 TRUE
#> 4 Diana 23 14.5 TRUE
# Depuis une liste
ma_liste <- list(
x = 1:5,
y = 6:10,
z = letters[1:5]
)
df_depuis_liste <- as.data.frame(ma_liste)
print(df_depuis_liste)#> x y z
#> 1 1 6 a
#> 2 2 7 b
#> 3 3 8 c
#> 4 4 9 d
#> 5 5 10 e
# Depuis une matrice
mat <- matrix(1:12, nrow = 4, ncol = 3)
df_depuis_matrice <- as.data.frame(mat)
print(df_depuis_matrice)#> V1 V2 V3
#> 1 1 5 9
#> 2 2 6 10
#> 3 3 7 11
#> 4 4 8 12
# Par défaut, les chaînes ne sont plus converties en facteurs (R >= 4.0)
df1 <- data.frame(
lettres = c("a", "b", "c")
)
class(df1$lettres) # "character" en R >= 4.0#> [1] "character"
# Pour forcer la conversion en facteur
df2 <- data.frame(
lettres = c("a", "b", "c"),
stringsAsFactors = TRUE
)
class(df2$lettres) # "factor"#> [1] "factor"
Note : en R < 4.0, stringsAsFactors = TRUE était le défaut (source de nombreux bugs). Depuis R 4.0, le défaut est FALSE, ce qui est plus sûr.
Avant de travailler avec des données, il faut les explorer. Voici les fonctions essentielles :
# Créons un exemple
iris_petit <- iris[1:10, ] # Sous-ensemble du jeu de données iris
# Structure du data frame
str(iris_petit)#> 'data.frame': 10 obs. of 5 variables:
#> $ Sepal.Length: num 5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9
#> $ Sepal.Width : num 3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1
#> $ Petal.Length: num 1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5
#> $ Petal.Width : num 0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1
#> $ Species : Factor w/ 3 levels "setosa","versicolor",..: 1 1 1 1 1 1 1 1 1 1
#> Sepal.Length Sepal.Width Petal.Length Petal.Width Species
#> Min. :4.400 Min. :2.900 Min. :1.30 Min. :0.10 setosa :10
#> 1st Qu.:4.625 1st Qu.:3.100 1st Qu.:1.40 1st Qu.:0.20 versicolor: 0
#> Median :4.900 Median :3.300 Median :1.40 Median :0.20 virginica : 0
#> Mean :4.860 Mean :3.310 Mean :1.45 Mean :0.22
#> 3rd Qu.:5.000 3rd Qu.:3.475 3rd Qu.:1.50 3rd Qu.:0.20
#> Max. :5.400 Max. :3.900 Max. :1.70 Max. :0.40
#> Sepal.Length Sepal.Width Petal.Length Petal.Width Species
#> 1 5.1 3.5 1.4 0.2 setosa
#> 2 4.9 3.0 1.4 0.2 setosa
#> 3 4.7 3.2 1.3 0.2 setosa
#> Sepal.Length Sepal.Width Petal.Length Petal.Width Species
#> 8 5.0 3.4 1.5 0.2 setosa
#> 9 4.4 2.9 1.4 0.2 setosa
#> 10 4.9 3.1 1.5 0.1 setosa
#> [1] 10 5
#> [1] 10
#> [1] 5
#> [1] "Sepal.Length" "Sepal.Width" "Petal.Length" "Petal.Width" "Species"
#> [1] "Sepal.Length" "Sepal.Width" "Petal.Length" "Petal.Width" "Species"
str() et summary() sont probablement les deux fonctions les plus utiles pour une première exploration.
Un data frame a une double nature : c’est à la fois une liste et une structure bidimensionnelle. On peut donc l’indexer de deux façons.
[ligne, colonne]etudiants <- data.frame(
nom = c("Alice", "Bob", "Charlie"),
age = c(20, 22, 21),
note = c(15.5, 12.0, 18.0)
)
# Un élément spécifique
etudiants[2, 3] # Ligne 2, colonne 3#> [1] 12
#> nom age note
#> 2 Bob 22 12
#> [1] 20 22 21
#> nom age note
#> 1 Alice 20 15.5
#> 3 Charlie 21 18.0
#> nom note
#> 1 Alice 15.5
#> 2 Bob 12.0
#> 3 Charlie 18.0
#> nom note
#> 1 Alice 15.5
#> 2 Bob 12.0
#> 3 Charlie 18.0
$, [[]], []Rappel : chaque colonne est un élément de la liste.
etudiants <- data.frame(
nom = c("Alice", "Bob", "Charlie"),
age = c(20, 22, 21),
note = c(15.5, 12.0, 18.0)
)
# Extraire une colonne avec $ (le plus courant)
etudiants$nom#> [1] "Alice" "Bob" "Charlie"
#> [1] 20 22 21
#> [1] 15.5 12.0 18.0
#> [1] 20 22 21
#> nom
#> 1 Alice
#> 2 Bob
#> 3 Charlie
#> nom note
#> 1 Alice 15.5
#> 2 Bob 12.0
#> 3 Charlie 18.0
Différence importante : - df$colonne et df[["colonne"]] → vecteur - df["colonne"] → data frame (avec une seule colonne)
L’indexation logique est très puissante pour filtrer les données :
etudiants <- data.frame(
nom = c("Alice", "Bob", "Charlie", "Diana"),
age = c(20, 22, 21, 23),
note = c(15.5, 12.0, 18.0, 14.5)
)
# Étudiants avec une note >= 15
etudiants[etudiants$note >= 15, ]#> nom age note
#> 1 Alice 20 15.5
#> 3 Charlie 21 18.0
#> nom age note
#> 2 Bob 22 12.0
#> 4 Diana 23 14.5
#> nom age note
#> 3 Charlie 21 18
subset()subset() offre une syntaxe plus lisible pour le filtrage :
etudiants <- data.frame(
nom = c("Alice", "Bob", "Charlie", "Diana"),
age = c(20, 22, 21, 23),
note = c(15.5, 12.0, 18.0, 14.5)
)
# Syntaxe plus lisible (pas besoin de répéter le nom du data frame)
subset(etudiants, note >= 15)#> nom age note
#> 1 Alice 20 15.5
#> 3 Charlie 21 18.0
#> nom age note
#> 3 Charlie 21 18
#> nom note
#> 1 Alice 15.5
#> 3 Charlie 18.0
#> nom note
#> 2 Bob 12.0
#> 3 Charlie 18.0
#> 4 Diana 14.5
subset() est pratique pour l’usage interactif, mais l’indexation directe [] est préférée en programmation (fonctions, scripts).
etudiants <- data.frame(
nom = c("Alice", "Bob", "Charlie"),
note = c(15.5, 12.0, 18.0)
)
# Ajouter une colonne avec $
etudiants$mention <- c(TRUE, FALSE, TRUE)
print(etudiants)#> nom note mention
#> 1 Alice 15.5 TRUE
#> 2 Bob 12.0 FALSE
#> 3 Charlie 18.0 TRUE
#> nom note mention classe
#> 1 Alice 15.5 TRUE A
#> 2 Bob 12.0 FALSE B
#> 3 Charlie 18.0 TRUE A
# Colonne calculée à partir d'autres colonnes
etudiants$note_sur_100 <- etudiants$note * 5
print(etudiants)#> nom note mention classe note_sur_100
#> 1 Alice 15.5 TRUE A 77.5
#> 2 Bob 12.0 FALSE B 60.0
#> 3 Charlie 18.0 TRUE A 90.0
rbind()etudiants <- data.frame(
nom = c("Alice", "Bob"),
age = c(20, 22)
)
# Ajouter une ligne
nouvelle_ligne <- data.frame(nom = "Charlie", age = 21)
etudiants <- rbind(etudiants, nouvelle_ligne)
print(etudiants)#> nom age
#> 1 Alice 20
#> 2 Bob 22
#> 3 Charlie 21
# Ajouter plusieurs lignes
nouvelles_lignes <- data.frame(
nom = c("Diana", "Eve"),
age = c(23, 19)
)
etudiants <- rbind(etudiants, nouvelles_lignes)
print(etudiants)#> nom age
#> 1 Alice 20
#> 2 Bob 22
#> 3 Charlie 21
#> 4 Diana 23
#> 5 Eve 19
Attention : les colonnes doivent avoir les mêmes noms et types compatibles.
etudiants <- data.frame(
nom = c("Alice", "Bob", "Charlie"),
age = c(20, 22, 21)
)
# Supprimer par position
etudiants <- etudiants[-2, ] # Supprime la ligne 2
print(etudiants)#> nom age
#> 1 Alice 20
#> 3 Charlie 21
#> nom age
#> 1 Alice 20
Pour trier un data frame, on utilise la fonction order() :
etudiants <- data.frame(
nom = c("Charlie", "Alice", "Bob"),
age = c(21, 20, 22),
note = c(18.0, 15.5, 12.0)
)
# Trier par âge (croissant)
etudiants[order(etudiants$age), ]#> nom age note
#> 2 Alice 20 15.5
#> 1 Charlie 21 18.0
#> 3 Bob 22 12.0
#> nom age note
#> 1 Charlie 21 18.0
#> 2 Alice 20 15.5
#> 3 Bob 22 12.0
#> nom age note
#> 1 Charlie 21 18.0
#> 2 Alice 20 15.5
#> 3 Bob 22 12.0
#> nom age note
#> 2 Alice 20 15.5
#> 1 Charlie 21 18.0
#> 3 Bob 22 12.0
Astuce : on peut sauvegarder le résultat pour modifier le data frame :
etudiants <- data.frame(
nom = c("Charlie", "Alice", "Bob"),
note = c(18.0, 15.5, 12.0)
)
etudiants <- etudiants[order(etudiants$note, decreasing = TRUE), ]
print(etudiants)#> nom note
#> 1 Charlie 18.0
#> 2 Alice 15.5
#> 3 Bob 12.0
La fusion de tables est une opération fondamentale en analyse de données. merge() permet de combiner deux data frames selon des colonnes communes.
# Table des étudiants
etudiants <- data.frame(
id = c(1, 2, 3),
nom = c("Alice", "Bob", "Charlie")
)
# Table des notes
notes <- data.frame(
id = c(1, 2, 4),
note = c(15, 12, 18)
)
# Fusion par défaut : garde seulement les lignes présentes dans les DEUX tables
merge(etudiants, notes, by = "id")#> id nom note
#> 1 1 Alice 15
#> 2 2 Bob 12
Notez que l’étudiant 3 (Charlie) et l’id 4 (sans nom) sont absents du résultat : seuls les ids 1 et 2 sont présents dans les deux tables.
etudiants <- data.frame(
id = c(1, 2, 3),
nom = c("Alice", "Bob", "Charlie")
)
notes <- data.frame(
id = c(1, 2, 4),
note = c(15, 12, 18)
)
# Inner join (par défaut) : intersection
merge(etudiants, notes, by = "id")#> id nom note
#> 1 1 Alice 15
#> 2 2 Bob 12
# Left join : garde toutes les lignes de la table de gauche
merge(etudiants, notes, by = "id", all.x = TRUE)#> id nom note
#> 1 1 Alice 15
#> 2 2 Bob 12
#> 3 3 Charlie NA
# Right join : garde toutes les lignes de la table de droite
merge(etudiants, notes, by = "id", all.y = TRUE)#> id nom note
#> 1 1 Alice 15
#> 2 2 Bob 12
#> 3 4 <NA> 18
# Full join : garde toutes les lignes des deux tables
merge(etudiants, notes, by = "id", all = TRUE)#> id nom note
#> 1 1 Alice 15
#> 2 2 Bob 12
#> 3 3 Charlie NA
#> 4 4 <NA> 18
ventes1 <- data.frame(
produit = c("A", "B", "A"),
region = c("Nord", "Nord", "Sud"),
q1 = c(10, 20, 15)
)
ventes2 <- data.frame(
produit = c("A", "B", "A"),
region = c("Nord", "Nord", "Sud"),
q2 = c(12, 22, 18)
)
# Fusion sur deux colonnes
merge(ventes1, ventes2, by = c("produit", "region"))#> produit region q1 q2
#> 1 A Nord 10 12
#> 2 A Sud 15 18
#> 3 B Nord 20 22
etudiants <- data.frame(
etudiant_id = c(1, 2, 3),
nom = c("Alice", "Bob", "Charlie")
)
notes <- data.frame(
id = c(1, 2, 4),
note = c(15, 12, 18)
)
# Spécifier les noms de colonnes différents
merge(etudiants, notes, by.x = "etudiant_id", by.y = "id")#> etudiant_id nom note
#> 1 1 Alice 15
#> 2 2 Bob 12
aggregate() permet de calculer des statistiques par groupe, une opération très courante en analyse de données.
# Jeu de données exemple
ventes <- data.frame(
produit = c("A", "B", "A", "B", "A", "B"),
region = c("Nord", "Nord", "Sud", "Sud", "Nord", "Sud"),
montant = c(100, 150, 120, 180, 110, 160)
)
# Montant moyen par produit
aggregate(montant ~ produit, data = ventes, FUN = mean)#> produit montant
#> 1 A 110.0000
#> 2 B 163.3333
#> region montant
#> 1 Nord 360
#> 2 Sud 460
La syntaxe variable ~ groupe se lit : “variable en fonction de groupe”.
ventes <- data.frame(
produit = c("A", "B", "A", "B", "A", "B"),
region = c("Nord", "Nord", "Sud", "Sud", "Nord", "Sud"),
montant = c(100, 150, 120, 180, 110, 160)
)
# Montant moyen par produit ET par région
aggregate(montant ~ produit + region, data = ventes, FUN = mean)#> produit region montant
#> 1 A Nord 105
#> 2 B Nord 150
#> 3 A Sud 120
#> 4 B Sud 170
ventes <- data.frame(
produit = c("A", "B", "A", "B", "A", "B"),
montant = c(100, 150, 120, 180, 110, 160)
)
# Étendue (max - min) par produit
aggregate(montant ~ produit, data = ventes, FUN = function(x) max(x) - min(x))#> produit montant
#> 1 A 20
#> 2 B 30
# Plusieurs statistiques à la fois
aggregate(montant ~ produit, data = ventes, FUN = function(x) {
c(moyenne = mean(x), mediane = median(x), ecart_type = sd(x))
})#> produit montant.moyenne montant.mediane montant.ecart_type
#> 1 A 110.00000 110.00000 10.00000
#> 2 B 163.33333 160.00000 15.27525
Note : il existe aussi tapply() qui fait des opérations similaires mais retourne un format moins pratique (array plutôt que data frame) pour l’analyse de données.
Attention : c’est un comportement de R qui peut causer des bugs subtils.
Lorsqu’on extrait une seule colonne d’un data frame avec [, ], R simplifie automatiquement le résultat en vecteur au lieu de conserver un data frame.
Ce comportement peut casser du code qui fonctionne avec plusieurs colonnes mais échoue avec une seule :
drop = FALSEPour toujours obtenir un data frame, utilisez drop = FALSE :
df <- data.frame(
a = 1:3,
b = 4:6,
c = 7:9
)
# Extraire une colonne EN GARDANT la structure data frame
resultat <- df[, "a", drop = FALSE]
class(resultat) # "data.frame"#> [1] "data.frame"
#> a
#> 1 1
#> 2 2
#> 3 3
# Maintenant la fonction fonctionne dans tous les cas
compter_lignes_robuste <- function(data, colonnes) {
sous_df <- data[, colonnes, drop = FALSE]
nrow(sous_df)
}
compter_lignes_robuste(df, "a") # 3 - fonctionne !#> [1] 3
#> [1] 3
df <- data.frame(
a = 1:3,
b = 4:6
)
# Ces méthodes ne simplifient jamais
df["a"] # Data frame (avec [])#> a
#> 1 1
#> 2 2
#> 3 3
#> [1] 1 2 3
#> [1] 1 2 3
Bonne pratique : en programmation (fonctions, scripts), toujours utiliser drop = FALSE quand le nombre de colonnes est variable.
Note : ce comportement peut être source d’erreurs. Il existe des alternatives modernes qui évitent ce problème et rendent le code plus prévisible.
Historiquement, les data frames pouvaient avoir des noms de lignes (attribut row.names). Cependant, c’est une mauvaise pratique pour plusieurs raisons :
Bonne pratique moderne : si vous avez des identifiants pour vos observations, créez une colonne dédiée :
# Mauvaise pratique (ancienne école)
df_mauvais <- data.frame(
age = c(25, 30, 35),
row.names = c("Alice", "Bob", "Charlie")
)
print(df_mauvais)#> age
#> Alice 25
#> Bob 30
#> Charlie 35
# Bonne pratique (moderne)
df_bon <- data.frame(
nom = c("Alice", "Bob", "Charlie"),
age = c(25, 30, 35)
)
print(df_bon)#> nom age
#> 1 Alice 25
#> 2 Bob 30
#> 3 Charlie 35
Avec une colonne dédiée, l’identifiant est une vraie variable que vous pouvez manipuler, filtrer, et utiliser dans les opérations de fusion.
Les data frames sont la structure centrale pour l’analyse de données en R :
data.frame()str(), summary(), head(), tail(), dim(), names()[ligne, colonne]$colonne, [["colonne"]], ["colonne"]df[condition, ] ou subset()df$nouvelle <- valeursdf[order(df$colonne), ]merge() avec all.x, all.y, all pour différents types de jointuresaggregate(variable ~ groupe, data, FUN)drop = FALSE pour garantir un data frame en sortierow.names, créer une colonne id à la placeLes data frames sont optimisés pour les données rectangulaires avec des variables de types mixtes, exactement ce dont on a besoin pour l’analyse statistique.
Jusqu’à présent, nous avons créé nos données directement dans R. En pratique, vous travaillerez le plus souvent avec des données stockées dans des fichiers : CSV, Excel, bases de données, etc.
L’import de données est une étape cruciale : c’est là que se jouent de nombreux problèmes (encodage, séparateurs, types de données). Une bonne gestion de l’import vous évitera beaucoup de frustrations par la suite.
Principe fondamental : nettoyez vos données dès l’import. Il vaut mieux passer quelques minutes à bien configurer l’import que de corriger des problèmes pendant des heures plus tard.
La fonction générique pour lire des fichiers texte est read.table(). Les autres fonctions (read.csv(), read.csv2(), etc.) ne sont que des raccourcis : ce sont des appels à read.table() avec des valeurs par défaut différentes pour les paramètres sep, dec, et header.
Tableau récapitulatif :
| Fonction | header |
sep |
dec |
Usage |
|---|---|---|---|---|
read.table() |
FALSE |
"" (espace) |
"." |
Fonction générique |
read.csv() |
TRUE |
"," |
"." |
CSV anglo-saxon |
read.csv2() |
TRUE |
";" |
"," |
CSV européen |
read.delim() |
TRUE |
"\t" |
"." |
Fichiers tabulés (anglo-saxon) |
read.delim2() |
TRUE |
"\t" |
"," |
Fichiers tabulés (européen) |
Ainsi, read.csv(fichier) est strictement équivalent à read.table(fichier, header = TRUE, sep = ",", dec = ".").
read.table() : la fonction génériqueArguments essentiels : - file : chemin vers le fichier - header : TRUE si la première ligne contient les noms de colonnes - sep : caractère de séparation des colonnes (",", ";", "\t", etc.) - dec : séparateur décimal ("." ou ",") - stringsAsFactors : conversion automatique en facteurs (défaut : FALSE en R ≥ 4.0)
read.csv() : format anglo-saxonPour les fichiers CSV au format international (séparateur ,, décimale .) :
Exemple de fichier (format anglo-saxon) :
nom,age,taille
Alice,25,1.65
Bob,30,1.80
Charlie,35,1.75
read.csv2() : format européenPour les fichiers CSV au format européen/français (séparateur ;, décimale ,) :
Exemple de fichier (format européen) :
nom;age;taille
Alice;25;1,65
Bob;30;1,80
Charlie;35;1,75
Attention : la confusion entre read.csv() et read.csv2() est une source d’erreur très fréquente ! Vérifiez toujours le format de vos fichiers.
read.delim() et read.delim2() : fichiers tabulésPour les fichiers avec tabulations comme séparateur :
colClassesPar défaut, R devine les types de données. Vous pouvez les spécifier explicitement :
donnees <- read.csv(
"donnees.csv",
colClasses = c(
"character", # Colonne 1 : texte
"integer", # Colonne 2 : entier
"numeric", # Colonne 3 : numérique
"factor" # Colonne 4 : facteur
)
)
# Avec noms de colonnes
donnees <- read.csv(
"donnees.csv",
colClasses = c(
nom = "character",
age = "integer",
taille = "numeric",
categorie = "factor"
)
)colClasses est très utile pour : - Accélérer la lecture (pas de détection automatique) - Forcer un type spécifique (par exemple, un code postal en caractère et non en entier) - Éviter les conversions indésirables
na.stringsPar défaut, R reconnaît NA comme valeur manquante. Vous pouvez spécifier d’autres valeurs :
skip et nrowsencoding et fileEncodingLes problèmes d’encodage causent souvent des caractères bizarres (é devient é, etc.).
Encodages courants : - "UTF-8" : standard moderne (recommandé) - "latin1" ou "ISO-8859-1" : Europe occidentale, Windows - "windows-1252" : Windows français
Si vous voyez des caractères étranges, c’est probablement un problème d’encodage !
# Fichier avec virgules comme décimales
# Si vous utilisez read.csv() au lieu de read.csv2() :
# 1,65 sera lu comme "1,65" (texte) et non comme 1.65 (numérique)
# MAUVAIS
donnees <- read.csv("donnees_francaises.csv") # Tous les nombres deviennent du texte !
# BON
donnees <- read.csv2("donnees_francaises.csv") # Reconnaît la virgule comme décimaleSi vos données contiennent le séparateur (par exemple, une virgule dans un texte), elles doivent être entre guillemets :
nom,description,prix
"Dupont, Jean","Produit A, version 2",10.5
read.csv() gère automatiquement ce cas avec quote = "\"".
# Fichier avec des noms problématiques :
# Prénom et Nom,Âge (années),Taille en cm
# Par défaut, R "nettoie" les noms (check.names = TRUE)
donnees <- read.csv("donnees.csv")
names(donnees) # "Prénom.et.Nom" "Âge..années." "Taille.en.cm"
# Pour garder les noms originaux (déconseillé)
donnees <- read.csv("donnees.csv", check.names = FALSE)
names(donnees) # "Prénom et Nom" "Âge (années)" "Taille en cm"
# Mais maintenant difficile à utiliser : donnees$`Prénom et Nom`Nous verrons comment nettoyer ces noms correctement dans la section suivante.
En R versions < 4.0, stringsAsFactors = TRUE était le défaut, causant beaucoup de problèmes :
Depuis R 4.0, le défaut est FALSE, ce qui est beaucoup plus sûr.
Principe essentiel : nettoyez vos données une fois, au début, de manière systématique. Vous éviterez ainsi de nombreux problèmes par la suite.
Les noms de colonnes doivent être : - Sans espaces - Sans caractères spéciaux (sauf _ et .) - Courts et explicites - En minuscules (convention, pas obligatoire)
# Import initial
donnees <- read.csv2("donnees_brutes.csv")
names(donnees)
# [1] "Prénom et Nom" "Âge (années)" "Taille en cm" "Date d'entrée"
# Méthode 1 : Renommage manuel (recommandé pour petit nombre de colonnes)
names(donnees) <- c("nom", "age", "taille_cm", "date_entree")
# Méthode 2 : Avec gsub pour remplacements automatiques
noms_propres <- tolower(names(donnees)) # Minuscules
noms_propres <- gsub(" ", "_", noms_propres) # Espaces → _
noms_propres <- gsub("[éèêë]", "e", noms_propres) # Accents
noms_propres <- gsub("[^a-z0-9_]", "", noms_propres) # Supprime autres caractères
names(donnees) <- noms_propresAlternative : certains packages offrent des fonctions de nettoyage automatique (par exemple janitor::clean_names()), mais nous restons ici en base R.
Après l’import, vérifiez toujours les types avec str() :
donnees <- read.csv2("donnees.csv")
str(donnees)
# Observations :
# - 'code_postal' est en numeric → devrait être en character
# - 'date' est en character → devrait être en Date
# - 'categorie' est en character → devrait être en factor
# Corrections
donnees$code_postal <- as.character(donnees$code_postal)
donnees$date <- as.Date(donnees$date, format = "%Y-%m-%d")
donnees$categorie <- factor(donnees$categorie)
# Vérification
str(donnees)Plutôt que de corriger après coup, spécifiez les types dès l’import :
donnees <- read.csv2(
"donnees.csv",
colClasses = c(
nom = "character",
age = "integer",
code_postal = "character", # Pas de calculs → character
date = "character", # Convertir ensuite en Date
categorie = "factor",
montant = "numeric"
)
)
# Conversion des dates après l'import
donnees$date <- as.Date(donnees$date, format = "%d/%m/%Y")donnees <- read.csv2("donnees.csv", na.strings = c("NA", "999", "-", ""))
# Vérifier les valeurs manquantes
summary(donnees)
colSums(is.na(donnees)) # Nombre de NA par colonne
# Décider quoi faire :
# 1. Supprimer les lignes avec NA
donnees_propres <- na.omit(donnees)
# 2. Supprimer seulement si NA dans certaines colonnes
donnees_propres <- donnees[!is.na(donnees$age), ]
# 3. Remplacer par une valeur (avec précaution !)
donnees$age[is.na(donnees$age)] <- mean(donnees$age, na.rm = TRUE)# 1. Import avec paramètres appropriés
donnees <- read.csv2(
"donnees_brutes.csv",
stringsAsFactors = FALSE,
na.strings = c("NA", "", " ", "999"),
encoding = "UTF-8"
)
# 2. Nettoyer les noms de colonnes
names(donnees) <- c("id", "nom", "age", "ville", "date_entree", "categorie")
# 3. Vérifier la structure
str(donnees)
summary(donnees)
# 4. Corriger les types
donnees$id <- as.character(donnees$id)
donnees$date_entree <- as.Date(donnees$date_entree, format = "%d/%m/%Y")
donnees$categorie <- factor(donnees$categorie, levels = c("A", "B", "C"))
# 5. Traiter les valeurs manquantes
donnees <- donnees[!is.na(donnees$age), ]
# 6. Vérifier le résultat final
str(donnees)
summary(donnees)
# Maintenant les données sont prêtes pour l'analyse !R a toujours un répertoire de travail (working directory) : le dossier à partir duquel il cherche et enregistre les fichiers.
Recommandation forte : utilisez les projets RStudio (fichiers .Rproj).
Avantages : - Le répertoire de travail est automatiquement le dossier du projet - Tout est organisé dans un seul dossier - Facile à partager et à déplacer - Chemins relatifs fonctionnent naturellement
Structure recommandée :
mon_projet/
├── mon_projet.Rproj
├── data/
│ ├── donnees_brutes.csv
│ └── donnees_propres.csv
├── scripts/
│ ├── 01_import.R
│ └── 02_analyse.R
└── resultats/
└── graphiques/
Avec cette organisation, vous pouvez simplement écrire :
L’export fonctionne exactement comme l’import, mais dans l’autre sens : la famille write.table() est le miroir de read.table(), avec les mêmes variantes et les mêmes paramètres.
Tableau récapitulatif :
| Fonction | sep |
dec |
Équivalent lecture |
|---|---|---|---|
write.table() |
" " |
"." |
read.table() |
write.csv() |
"," |
"." |
read.csv() |
write.csv2() |
";" |
"," |
read.csv2() |
write.delim() |
"\t" |
"." |
read.delim() |
write.delim2() |
"\t" |
"," |
read.delim2() |
Arguments utiles : row.names = FALSE (quasi-systématique), na = "" (format des valeurs manquantes), quote = FALSE (pas de guillemets), fileEncoding = "UTF-8" (encodage).
Note importante : utilisez toujours row.names = FALSE. Les noms de lignes sont une mauvaise pratique (comme nous l’avons vu), donc ne les exportez pas.
R de base ne peut pas lire les fichiers Excel directement. Le package readxl (partie du tidyverse) est la solution standard.
# D'abord, lister les feuilles disponibles
excel_sheets("donnees.xlsx")
# Lire la première feuille (par défaut)
donnees <- read_excel("donnees.xlsx")
# Ou spécifier la feuille (par nom ou position)
donnees <- read_excel("donnees.xlsx", sheet = "Feuille1")
donnees <- read_excel("donnees.xlsx", sheet = 2)read_excel() retourne un tibble (version moderne du data frame), pas un data.frame classique. Pour l’instant, considérez-les comme équivalents. La principale différence visible : - Les tibbles affichent mieux dans la console - Ils ne simplifient pas automatiquement (pas de problème de “drop”)
Vous pouvez convertir en data frame classique si nécessaire :
Note : R de base ne peut pas exporter vers Excel. Pour cela, il faut utiliser des packages comme writexl (simple) ou openxlsx (plus avancé).
L’import et l’export de données sont des étapes cruciales :
Import : - Famille read.table() : read.csv() (anglo-saxon), read.csv2() (européen) - Arguments clés : header, sep, dec, stringsAsFactors, colClasses, na.strings, encoding - Pièges : confusion séparateurs, encodage, types de données
Nettoyage initial (essentiel !) : - Nettoyer les noms de colonnes : simples, sans espaces, explicites - Vérifier et corriger les types avec str() et summary() - Spécifier colClasses dès l’import quand possible - Gérer les valeurs manquantes de manière cohérente - Principe : nettoyez une fois au début, profitez ensuite
Organisation : - Utiliser getwd() et setwd() pour le répertoire de travail - Recommandation : projets RStudio avec structure organisée - Chemins relatifs pour la portabilité
Export : - write.csv() ou write.csv2() selon le format - Toujours row.names = FALSE - Spécifier l’encodage si nécessaire
Excel : - Package readxl pour la lecture : read_excel() - Packages writexl ou openxlsx pour l’écriture
Une bonne gestion de l’import/export vous fera gagner un temps considérable et évitera de nombreuses erreurs dans vos analyses.
Les fonctions sont des blocs de code réutilisables qui accomplissent une tâche spécifique. Vous en avez déjà utilisé beaucoup : mean(), sum(), read.csv(), etc. Maintenant, vous allez apprendre à créer vos propres fonctions.
Avantages des fonctions :
Règle d’or : si vous copiez-collez du code plus de deux fois, créez une fonction !
La dernière expression évaluée est automatiquement retournée.
# Calculer des statistiques descriptives
stats_simples <- function(x) {
moy <- mean(x)
med <- median(x)
et <- sd(x)
# La dernière expression est retournée
c(moyenne = moy, mediane = med, ecart_type = et)
}
donnees <- c(10, 15, 12, 18, 14, 16)
stats_simples(donnees)#> moyenne mediane ecart_type
#> 14.166667 14.500000 2.857738
Par défaut, la dernière expression est retournée :
return()Vous pouvez utiliser return() pour être explicite ou sortir plus tôt :
# Explicite (optionnel mais plus clair)
calculer_aire_rectangle <- function(longueur, largeur) {
aire <- longueur * largeur
return(aire)
}
# Sortie anticipée
verifier_positif <- function(x) {
if (x < 0) {
return("Nombre négatif") # Sort immédiatement
}
if (x == 0) {
return("Zéro")
}
"Nombre positif"
}
verifier_positif(-5)#> [1] "Nombre négatif"
#> [1] "Zéro"
#> [1] "Nombre positif"
Une fonction ne peut retourner qu’un seul objet, mais cet objet peut être une liste :
statistiques_completes <- function(x) {
list(
moyenne = mean(x),
mediane = median(x),
ecart_type = sd(x),
min = min(x),
max = max(x),
n = length(x)
)
}
donnees <- c(10, 15, 12, 18, 14, 16, 11)
resultats <- statistiques_completes(donnees)
print(resultats)#> $moyenne
#> [1] 13.71429
#>
#> $mediane
#> [1] 14
#>
#> $ecart_type
#> [1] 2.870208
#>
#> $min
#> [1] 10
#>
#> $max
#> [1] 18
#>
#> $n
#> [1] 7
#> [1] 13.71429
#> [1] 2.870208
Les arguments sont passés dans l’ordre de définition :
On peut spécifier les arguments par leur nom, dans n’importe quel ordre :
#> [1] 5
#> [1] 5
Bonne pratique : utiliser les noms pour les arguments après les deux premiers, surtout s’ils sont nombreux.
On peut définir des valeurs par défaut pour les arguments optionnels :
saluer <- function(nom, formule = "Bonjour") {
paste(formule, nom)
}
saluer("Alice") # Utilise la valeur par défaut#> [1] "Bonjour Alice"
#> [1] "Bonsoir Alice"
Exemple plus pratique :
resume_numerique <- function(x, arrondir = 2) {
c(
moyenne = round(mean(x), arrondir),
mediane = round(median(x), arrondir),
ecart_type = round(sd(x), arrondir)
)
}
donnees <- c(10.123, 15.456, 12.789)
resume_numerique(donnees) # Arrondi à 2 décimales par défaut#> moyenne mediane ecart_type
#> 12.79 12.79 2.67
#> moyenne mediane ecart_type
#> 12.7893 12.7890 2.6665
Les variables créées dans une fonction sont locales : elles n’existent que dans la fonction.
Une fonction peut lire les variables globales, mais ne peut pas les modifier sans syntaxe spéciale :
x_global <- 100
lire_global <- function() {
print(x_global) # Peut lire x_global
}
lire_global() # Affiche 100#> [1] 100
modifier_local <- function() {
x_global <- 999 # Crée une NOUVELLE variable locale
print(x_global)
}
modifier_local() # Affiche 999#> [1] 999
#> [1] 100
Bonne pratique : évitez de modifier les variables globales dans les fonctions. Passez plutôt des arguments et retournez des valeurs.
# Fonction autonome qui ne dépend que de ses arguments
calculer_moyenne_ponderee <- function(valeurs, poids) {
sum(valeurs * poids) / sum(poids)
}
notes <- c(15, 12, 18)
coeffs <- c(2, 1, 3)
calculer_moyenne_ponderee(notes, coeffs)#> [1] 16
L’opérateur ... permet de passer un nombre variable d’arguments :
... est très utile pour transmettre des arguments à une fonction interne :
# Wrapper autour de mean() avec affichage
moyenne_avec_message <- function(x, ...) {
resultat <- mean(x, ...)
cat("La moyenne est :", resultat, "\n")
return(resultat)
}
donnees <- c(10, 20, NA, 30)
moyenne_avec_message(donnees) # NA à cause du NA#> La moyenne est : NA
#> [1] NA
#> La moyenne est : 20
#> [1] 20
...resume_avec_options <- function(x, titre = "Résumé", ...) {
cat("===", titre, "===\n")
print(summary(x, ...))
}
donnees <- c(10, 20, 30, 40, 50)
resume_avec_options(donnees)#> === Résumé ===
#> Min. 1st Qu. Median Mean 3rd Qu. Max.
#> 10 20 30 30 40 50
#> === Statistiques ===
#> Min. 1st Qu. Median Mean 3rd Qu. Max.
#> 10 20 30 30 40 50
Il est important de vérifier que les arguments sont valides :
calculer_racine <- function(x) {
# Vérifier que x est numérique
if (!is.numeric(x)) {
stop("L'argument doit être numérique")
}
# Vérifier que x est positif
if (any(x < 0)) {
warning("Certaines valeurs sont négatives, elles donneront des NaN")
}
sqrt(x)
}
calculer_racine(16)#> [1] 4
#> [1] 2 3 4
#> [1] 2 NaN 4
Fonctions utiles pour la validation : - stop() : arrête l’exécution avec un message d’erreur - warning() : affiche un avertissement mais continue - message() : affiche un message informatif - is.numeric(), is.character(), is.data.frame(), etc. : tests de type
nettoyer_donnees <- function(df) {
# Supprimer les lignes avec des NA
df_propre <- na.omit(df)
# Nettoyer les noms de colonnes
noms_propres <- tolower(names(df_propre))
noms_propres <- gsub(" ", "_", noms_propres)
names(df_propre) <- noms_propres
return(df_propre)
}
# Test
donnees_test <- data.frame(
"Nom Complet" = c("Alice", "Bob", NA),
"Age" = c(25, 30, 35)
)
donnees_propres <- nettoyer_donnees(donnees_test)
print(donnees_propres)#> nom.complet age
#> 1 Alice 25
#> 2 Bob 30
standardiser_colonne <- function(x) {
(x - mean(x)) / sd(x)
}
# Application à un data frame
donnees <- data.frame(
age = c(25, 30, 35, 40),
poids = c(65, 70, 75, 80),
taille = c(165, 170, 175, 180)
)
# Standardiser toutes les colonnes numériques
donnees_std <- as.data.frame(lapply(donnees, standardiser_colonne))
print(donnees_std)#> age poids taille
#> 1 -1.1618950 -1.1618950 -1.1618950
#> 2 -0.3872983 -0.3872983 -0.3872983
#> 3 0.3872983 0.3872983 0.3872983
#> 4 1.1618950 1.1618950 1.1618950
filtrer_par_seuil <- function(df, colonne, seuil, operation = "sup") {
if (operation == "sup") {
resultat <- df[df[[colonne]] > seuil, ]
} else if (operation == "inf") {
resultat <- df[df[[colonne]] < seuil, ]
} else {
stop("Opération doit être 'sup' ou 'inf'")
}
return(resultat)
}
etudiants <- data.frame(
nom = c("Alice", "Bob", "Charlie", "Diana"),
note = c(15, 12, 18, 14)
)
filtrer_par_seuil(etudiants, "note", 13, "sup")#> nom note
#> 1 Alice 15
#> 3 Charlie 18
#> 4 Diana 14
Vous avez déjà vu lapply() et sapply(). Créer vos propres fonctions les rend encore plus puissants :
# Créer une fonction personnalisée
coefficent_variation <- function(x) {
sd(x) / mean(x) * 100
}
# Appliquer à plusieurs colonnes
donnees <- data.frame(
mesure1 = c(10, 12, 11, 13),
mesure2 = c(100, 120, 110, 130),
mesure3 = c(5, 6, 5.5, 6.5)
)
sapply(donnees, coefficent_variation)#> mesure1 mesure2 mesure3
#> 11.22604 11.22604 11.22604
Avec des fonctions anonymes (définies directement dans apply) :
donnees <- data.frame(
a = c(1, 2, 3, 4),
b = c(10, 20, 30, 40)
)
# Normaliser entre 0 et 1
donnees_norm <- as.data.frame(lapply(donnees, function(x) {
(x - min(x)) / (max(x) - min(x))
}))
print(donnees_norm)#> a b
#> 1 0.0000000 0.0000000
#> 2 0.3333333 0.3333333
#> 3 0.6666667 0.6666667
#> 4 1.0000000 1.0000000
Une fonction peut s’appeler elle-même (récursivité). Exemple classique : la factorielle :
factorielle <- function(n) {
if (n <= 1) {
return(1)
} else {
return(n * factorielle(n - 1))
}
}
factorielle(5) # 5! = 5 × 4 × 3 × 2 × 1 = 120#> [1] 120
Note : la récursivité est élégante mais peut être inefficace en R. Préférez les boucles ou les fonctions vectorielles quand possible.
Commentez vos fonctions pour que vous (et les autres) puissiez les comprendre plus tard :
# Calculer l'indice de masse corporelle (IMC)
# Arguments :
# poids : poids en kilogrammes (numérique)
# taille : taille en mètres (numérique)
# Retourne : l'IMC calculé
calculer_imc <- function(poids, taille) {
# Validation des arguments
if (!is.numeric(poids) || !is.numeric(taille)) {
stop("Poids et taille doivent être numériques")
}
if (any(poids <= 0) || any(taille <= 0)) {
stop("Poids et taille doivent être positifs")
}
# Calcul : IMC = poids / taille²
imc <- poids / (taille^2)
return(imc)
}
calculer_imc(70, 1.75)#> [1] 22.85714
Quoi commenter ? - Une ligne au-dessus de la fonction : ce qu’elle fait - Les arguments attendus et leur type - Ce que la fonction retourne - Les étapes importantes dans le corps de la fonction
calculer_moyenne() plutôt que f1()Les fonctions permettent de créer du code réutilisable et maintenable :
nom <- function(args) { corps }return()... : pour nombre variable d’argumentsstop(), warning(), tests de typelapply()/sapply()/apply()Créer vos propres fonctions est une étape essentielle vers l’autonomie en R. Vous pouvez maintenant construire vos propres outils adaptés à vos besoins spécifiques.
Vous avez peut-être remarqué quelque chose d’étrange : la fonction print() affiche différemment selon le type d’objet.
#> [1] 1 2 3
#> x y
#> 1 1 4
#> 2 2 5
#> 3 3 6
#>
#> Call:
#> lm(formula = mpg ~ wt, data = mtcars)
#>
#> Coefficients:
#> (Intercept) wt
#> 37.285 -5.344
Comment print() “sait-elle” comment afficher chaque type d’objet différemment ? La réponse : le système de classes S3 et les méthodes génériques.
Ce système est au cœur de R et explique pourquoi des fonctions comme print(), summary(), et surtout plot() se comportent intelligemment selon le contexte.
Une classe est une étiquette attachée à un objet qui indique son type. En R, c’est simplement un attribut class.
Une fonction générique est une fonction qui se comporte différemment selon la classe de son argument. Les fonctions génériques les plus courantes :
print() : affichagesummary() : résuméplot() : graphiquesstr() : structurelength(), mean(), etc.Quand vous appelez print(objet), R :
objet avec class(objet)print.nom_classe()print.default()C’est ce qu’on appelle le dispatch de méthodes (ou répartition).
#> [1] "factor"
#> [1] A B A
#> Levels: A B
#> [1] print.acf*
#> [2] print.activeConcordance*
#> [3] print.AES*
#> [4] print.all_vars*
#> [5] print.anova*
#> [6] print.any_vars*
#> [7] print.aov*
#> [8] print.aovlist*
#> [9] print.ar*
#> [10] print.Arima*
#> [11] print.arima0*
#> [12] print.AsIs
#> [13] print.aspell*
#> [14] print.aspell_inspect_context*
#> [15] print.bibentry*
#> [16] print.Bibtex*
#> [17] print.browseVignettes*
#> [18] print.by
#> [19] print.changedFiles*
#> [20] print.check_bogus_return*
#> [21] print.check_code_usage_in_package*
#> [22] print.check_compiled_code*
#> [23] print.check_demo_index*
#> [24] print.check_depdef*
#> [25] print.check_details*
#> [26] print.check_details_changes*
#> [27] print.check_doi_db*
#> [28] print.check_dotInternal*
#> [29] print.check_make_vars*
#> [30] print.check_nonAPI_calls*
#> [31] print.check_package_code_assign_to_globalenv*
#> [32] print.check_package_code_attach*
#> [33] print.check_package_code_data_into_globalenv*
#> [34] print.check_package_code_startup_functions*
#> [35] print.check_package_code_syntax*
#> [36] print.check_package_code_unload_functions*
#> [37] print.check_package_compact_datasets*
#> [38] print.check_package_CRAN_incoming*
#> [39] print.check_package_datalist*
#> [40] print.check_package_datasets*
#> [41] print.check_package_depends*
#> [42] print.check_package_description*
#> [43] print.check_package_description_encoding*
#> [44] print.check_package_license*
#> [45] print.check_packages_in_dir*
#> [46] print.check_packages_used*
#> [47] print.check_po_files*
#> [48] print.check_pragmas*
#> [49] print.check_Rd_line_widths*
#> [50] print.check_Rd_metadata*
#> [51] print.check_Rd_xrefs*
#> [52] print.check_RegSym_calls*
#> [53] print.check_S3_methods_needing_delayed_registration*
#> [54] print.check_so_symbols*
#> [55] print.check_T_and_F*
#> [56] print.check_url_db*
#> [57] print.check_vignette_index*
#> [58] print.checkDocFiles*
#> [59] print.checkDocStyle*
#> [60] print.checkFF*
#> [61] print.checkRd*
#> [62] print.checkRdContents*
#> [63] print.checkReplaceFuns*
#> [64] print.checkS3methods*
#> [65] print.checkTnF*
#> [66] print.checkVignettes*
#> [67] print.citation*
#> [68] print.cli_ansi_html_style*
#> [69] print.cli_ansi_string*
#> [70] print.cli_ansi_style*
#> [71] print.cli_boxx*
#> [72] print.cli_diff_chr*
#> [73] print.cli_doc*
#> [74] print.cli_progress_demo*
#> [75] print.cli_rule*
#> [76] print.cli_sitrep*
#> [77] print.cli_spark*
#> [78] print.cli_spinner*
#> [79] print.cli_tree*
#> [80] print.codoc*
#> [81] print.codocClasses*
#> [82] print.codocData*
#> [83] print.col_spec*
#> [84] print.collector*
#> [85] print.colorConverter*
#> [86] print.compactPDF*
#> [87] print.condition
#> [88] print.connection
#> [89] print.CRAN_package_reverse_dependencies_and_views*
#> [90] print.data.frame
#> [91] print.Date
#> [92] print.date_names*
#> [93] print.default
#> [94] print.dendrogram*
#> [95] print.density*
#> [96] print.difftime
#> [97] print.dist*
#> [98] print.Dlist
#> [99] print.DLLInfo
#> [100] print.DLLInfoList
#> [101] print.DLLRegisteredRoutines
#> [102] print.dplyr_join_by*
#> [103] print.dplyr_sel_vars*
#> [104] print.dummy_coef*
#> [105] print.dummy_coef_list*
#> [106] print.ecdf*
#> [107] print.eigen
#> [108] print.element*
#> [109] print.factanal*
#> [110] print.factor
#> [111] print.family*
#> [112] print.fileSnapshot*
#> [113] print.findLineNumResult*
#> [114] print.flatGridListing*
#> [115] print.formula*
#> [116] print.fseq*
#> [117] print.ftable*
#> [118] print.fun_list*
#> [119] print.function
#> [120] print.getAnywhere*
#> [121] print.ggplot*
#> [122] print.ggplot2_bins*
#> [123] print.ggproto*
#> [124] print.ggproto_method*
#> [125] print.gList*
#> [126] print.glm*
#> [127] print.glue*
#> [128] print.gpar*
#> [129] print.GridCoords*
#> [130] print.GridGrobCoords*
#> [131] print.GridGTreeCoords*
#> [132] print.grob*
#> [133] print.gtable*
#> [134] print.hashtab*
#> [135] print.hcl_palettes*
#> [136] print.hclust*
#> [137] print.help_files_with_topic*
#> [138] print.hexmode
#> [139] print.hms*
#> [140] print.HoltWinters*
#> [141] print.hsearch*
#> [142] print.hsearch_db*
#> [143] print.htest*
#> [144] print.html*
#> [145] print.html_dependency*
#> [146] print.htmltools.selector*
#> [147] print.htmltools.selector.list*
#> [148] print.infl*
#> [149] print.integrate*
#> [150] print.isoreg*
#> [151] print.json*
#> [152] print.key_missing*
#> [153] print.kmeans*
#> [154] print.knitr_kable*
#> [155] print.last_dplyr_warnings*
#> [156] print.Latex*
#> [157] print.LaTeX*
#> [158] print.libraryIQR
#> [159] print.lifecycle_warnings*
#> [160] print.listof
#> [161] print.lm*
#> [162] print.loadings*
#> [163] print.locale*
#> [164] print.loess*
#> [165] print.logLik*
#> [166] print.ls_str*
#> [167] print.medpolish*
#> [168] print.MethodsFunction*
#> [169] print.mtable*
#> [170] print.NativeRoutineList
#> [171] print.news_db*
#> [172] print.nls*
#> [173] print.noquote
#> [174] print.numeric_version
#> [175] print.object_size*
#> [176] print.octmode
#> [177] print.packageDescription*
#> [178] print.packageInfo
#> [179] print.packageIQR*
#> [180] print.packageStatus*
#> [181] print.paged_df*
#> [182] print.pairwise.htest*
#> [183] print.path*
#> [184] print.person*
#> [185] print.pillar*
#> [186] print.pillar_1e*
#> [187] print.pillar_colonnade*
#> [188] print.pillar_ornament*
#> [189] print.pillar_shaft*
#> [190] print.pillar_squeezed_colonnade*
#> [191] print.pillar_tbl_format_setup*
#> [192] print.pillar_vctr*
#> [193] print.pillar_vctr_attr*
#> [194] print.POSIXct
#> [195] print.POSIXlt
#> [196] print.power.htest*
#> [197] print.ppr*
#> [198] print.prcomp*
#> [199] print.princomp*
#> [200] print.proc_time
#> [201] print.purrr_function_compose*
#> [202] print.purrr_function_partial*
#> [203] print.purrr_rate_backoff*
#> [204] print.purrr_rate_delay*
#> [205] print.quosure*
#> [206] print.quosures*
#> [207] print.R6*
#> [208] print.R6ClassGenerator*
#> [209] print.raster*
#> [210] print.Rconcordance*
#> [211] print.Rd*
#> [212] print.recordedplot*
#> [213] print.rel*
#> [214] print.restart
#> [215] print.RGBcolorConverter*
#> [216] print.RGlyphFont*
#> [217] print.rlang_box_done*
#> [218] print.rlang_box_splice*
#> [219] print.rlang_data_pronoun*
#> [220] print.rlang_dict*
#> [221] print.rlang_dyn_array*
#> [222] print.rlang_envs*
#> [223] print.rlang_error*
#> [224] print.rlang_fake_data_pronoun*
#> [225] print.rlang_lambda_function*
#> [226] print.rlang_message*
#> [227] print.rlang_trace*
#> [228] print.rlang_warning*
#> [229] print.rlang_zap*
#> [230] print.rlang:::list_of_conditions*
#> [231] print.rle
#> [232] print.rlib_bytes*
#> [233] print.rlib_error_3_0*
#> [234] print.rlib_trace_3_0*
#> [235] print.roman*
#> [236] print.scalar*
#> [237] print.sessionInfo*
#> [238] print.shiny.tag*
#> [239] print.shiny.tag.env*
#> [240] print.shiny.tag.list*
#> [241] print.shiny.tag.query*
#> [242] print.simple.list
#> [243] print.smooth.spline*
#> [244] print.socket*
#> [245] print.src*
#> [246] print.srcfile
#> [247] print.srcref
#> [248] print.stepfun*
#> [249] print.stl*
#> [250] print.stringr_view*
#> [251] print.StructTS*
#> [252] print.subdir_tests*
#> [253] print.summarize_CRAN_check_status*
#> [254] print.summary.aov*
#> [255] print.summary.aovlist*
#> [256] print.summary.ecdf*
#> [257] print.summary.glm*
#> [258] print.summary.lm*
#> [259] print.summary.loess*
#> [260] print.summary.manova*
#> [261] print.summary.nls*
#> [262] print.summary.packageStatus*
#> [263] print.summary.ppr*
#> [264] print.summary.prcomp*
#> [265] print.summary.princomp*
#> [266] print.summary.table
#> [267] print.summary.warnings
#> [268] print.summaryDefault
#> [269] print.table
#> [270] print.tables_aov*
#> [271] print.tbl*
#> [272] print.terms*
#> [273] print.theme*
#> [274] print.tidyverse_conflicts*
#> [275] print.tidyverse_logo*
#> [276] print.transform*
#> [277] print.trunc_mat*
#> [278] print.ts*
#> [279] print.tskernel*
#> [280] print.TukeyHSD*
#> [281] print.tukeyline*
#> [282] print.tukeysmooth*
#> [283] print.undoc*
#> [284] print.uneval*
#> [285] print.unit*
#> [286] print.vctrs_bytes*
#> [287] print.vctrs_sclr*
#> [288] print.vctrs_unspecified*
#> [289] print.vctrs_vctr*
#> [290] print.viewport*
#> [291] print.vignette*
#> [292] print.warnings
#> [293] print.xfun_raw_string*
#> [294] print.xfun_record_results*
#> [295] print.xfun_rename_seq*
#> [296] print.xfun_strict_list*
#> [297] print.xgettext*
#> [298] print.xngettext*
#> [299] print.xtabs*
#> see '?methods' for accessing help and source code
summary()# Summary sur un vecteur numérique
x <- c(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
summary(x) # Statistiques descriptives#> Min. 1st Qu. Median Mean 3rd Qu. Max.
#> 1.00 3.25 5.50 5.50 7.75 10.00
# Summary sur un facteur
f <- factor(c("A", "B", "A", "C", "B"))
summary(f) # Comptages des modalités#> A B C
#> 2 2 1
# Summary sur un data frame
df <- data.frame(age = c(25, 30, 35), note = c(15, 18, 12))
summary(df) # Résumé de chaque colonne#> age note
#> Min. :25.0 Min. :12.0
#> 1st Qu.:27.5 1st Qu.:13.5
#> Median :30.0 Median :15.0
#> Mean :30.0 Mean :15.0
#> 3rd Qu.:32.5 3rd Qu.:16.5
#> Max. :35.0 Max. :18.0
Chaque fois, summary() adapte son comportement à la classe de l’objet !
Créer une classe S3 est très simple : il suffit de définir l’attribut class.
# Créer un objet représentant une personne
personne <- list(
nom = "Alice",
age = 30,
ville = "Paris"
)
# Ajouter la classe
class(personne) <- "Personne"
# Vérifier
class(personne)#> [1] "Personne"
#> $nom
#> [1] "Alice"
#>
#> $age
#> [1] 30
#>
#> $ville
#> [1] "Paris"
#>
#> attr(,"class")
#> [1] "Personne"
Pour améliorer l’affichage, créons une méthode print.Personne() :
# Définir la méthode print pour la classe Personne
print.Personne <- function(x, ...) {
cat("=== Informations Personne ===\n")
cat("Nom :", x$nom, "\n")
cat("Âge :", x$age, "ans\n")
cat("Ville:", x$ville, "\n")
cat("============================\n")
invisible(x) # Retourne x invisiblement
}
# Maintenant print() utilise notre méthode
personne <- list(nom = "Alice", age = 30, ville = "Paris")
class(personne) <- "Personne"
print(personne)#> === Informations Personne ===
#> Nom : Alice
#> Âge : 30 ans
#> Ville: Paris
#> ============================
On peut créer des méthodes pour d’autres fonctions génériques :
# Méthode summary pour Personne
summary.Personne <- function(object, ...) {
cat("Résumé :", object$nom, "a", object$age, "ans et habite à", object$ville, "\n")
invisible(object)
}
personne <- list(nom = "Bob", age = 25, ville = "Lyon")
class(personne) <- "Personne"
summary(personne)#> Résumé : Bob a 25 ans et habite à Lyon
Créons une classe pour stocker des statistiques descriptives :
# Fonction constructeur (bonne pratique)
creer_stats <- function(x) {
# Calculer les statistiques
stats <- list(
donnees = x,
n = length(x),
moyenne = mean(x),
mediane = median(x),
ecart_type = sd(x),
min = min(x),
max = max(x)
)
# Assigner la classe
class(stats) <- "Statistiques"
return(stats)
}
# Méthode print
print.Statistiques <- function(x, ...) {
cat("Statistiques descriptives\n")
cat("=========================\n")
cat("Nombre d'observations:", x$n, "\n")
cat("Moyenne :", round(x$moyenne, 2), "\n")
cat("Médiane :", round(x$mediane, 2), "\n")
cat("Écart-type :", round(x$ecart_type, 2), "\n")
cat("Min - Max :", x$min, "-", x$max, "\n")
invisible(x)
}
# Méthode summary
summary.Statistiques <- function(object, ...) {
cat("Résumé : n =", object$n, ", moyenne =", round(object$moyenne, 2), "\n")
invisible(object)
}
# Utilisation
donnees <- c(10, 15, 12, 18, 14, 16, 11, 13)
stats <- creer_stats(donnees)
print(stats) # Utilise print.Statistiques()#> Statistiques descriptives
#> =========================
#> Nombre d'observations: 8
#> Moyenne : 13.62
#> Médiane : 13.5
#> Écart-type : 2.67
#> Min - Max : 10 - 18
#> Résumé : n = 8 , moyenne = 13.62
#> [1] print.acf*
#> [2] print.activeConcordance*
#> [3] print.AES*
#> [4] print.all_vars*
#> [5] print.anova*
#> [6] print.any_vars*
#> [7] print.aov*
#> [8] print.aovlist*
#> [9] print.ar*
#> [10] print.Arima*
#> [11] print.arima0*
#> [12] print.AsIs
#> [13] print.aspell*
#> [14] print.aspell_inspect_context*
#> [15] print.bibentry*
#> [16] print.Bibtex*
#> [17] print.browseVignettes*
#> [18] print.by
#> [19] print.changedFiles*
#> [20] print.check_bogus_return*
#> [21] print.check_code_usage_in_package*
#> [22] print.check_compiled_code*
#> [23] print.check_demo_index*
#> [24] print.check_depdef*
#> [25] print.check_details*
#> [26] print.check_details_changes*
#> [27] print.check_doi_db*
#> [28] print.check_dotInternal*
#> [29] print.check_make_vars*
#> [30] print.check_nonAPI_calls*
#> [31] print.check_package_code_assign_to_globalenv*
#> [32] print.check_package_code_attach*
#> [33] print.check_package_code_data_into_globalenv*
#> [34] print.check_package_code_startup_functions*
#> [35] print.check_package_code_syntax*
#> [36] print.check_package_code_unload_functions*
#> [37] print.check_package_compact_datasets*
#> [38] print.check_package_CRAN_incoming*
#> [39] print.check_package_datalist*
#> [40] print.check_package_datasets*
#> [41] print.check_package_depends*
#> [42] print.check_package_description*
#> [43] print.check_package_description_encoding*
#> [44] print.check_package_license*
#> [45] print.check_packages_in_dir*
#> [46] print.check_packages_used*
#> [47] print.check_po_files*
#> [48] print.check_pragmas*
#> [49] print.check_Rd_line_widths*
#> [50] print.check_Rd_metadata*
#> [51] print.check_Rd_xrefs*
#> [52] print.check_RegSym_calls*
#> [53] print.check_S3_methods_needing_delayed_registration*
#> [54] print.check_so_symbols*
#> [55] print.check_T_and_F*
#> [56] print.check_url_db*
#> [57] print.check_vignette_index*
#> [58] print.checkDocFiles*
#> [59] print.checkDocStyle*
#> [60] print.checkFF*
#> [61] print.checkRd*
#> [62] print.checkRdContents*
#> [63] print.checkReplaceFuns*
#> [64] print.checkS3methods*
#> [65] print.checkTnF*
#> [66] print.checkVignettes*
#> [67] print.citation*
#> [68] print.cli_ansi_html_style*
#> [69] print.cli_ansi_string*
#> [70] print.cli_ansi_style*
#> [71] print.cli_boxx*
#> [72] print.cli_diff_chr*
#> [73] print.cli_doc*
#> [74] print.cli_progress_demo*
#> [75] print.cli_rule*
#> [76] print.cli_sitrep*
#> [77] print.cli_spark*
#> [78] print.cli_spinner*
#> [79] print.cli_tree*
#> [80] print.codoc*
#> [81] print.codocClasses*
#> [82] print.codocData*
#> [83] print.col_spec*
#> [84] print.collector*
#> [85] print.colorConverter*
#> [86] print.compactPDF*
#> [87] print.condition
#> [88] print.connection
#> [89] print.CRAN_package_reverse_dependencies_and_views*
#> [90] print.data.frame
#> [91] print.Date
#> [92] print.date_names*
#> [93] print.default
#> [94] print.dendrogram*
#> [95] print.density*
#> [96] print.difftime
#> [97] print.dist*
#> [98] print.Dlist
#> [99] print.DLLInfo
#> [100] print.DLLInfoList
#> [101] print.DLLRegisteredRoutines
#> [102] print.dplyr_join_by*
#> [103] print.dplyr_sel_vars*
#> [104] print.dummy_coef*
#> [105] print.dummy_coef_list*
#> [106] print.ecdf*
#> [107] print.eigen
#> [108] print.element*
#> [109] print.factanal*
#> [110] print.factor
#> [111] print.family*
#> [112] print.fileSnapshot*
#> [113] print.findLineNumResult*
#> [114] print.flatGridListing*
#> [115] print.formula*
#> [116] print.fseq*
#> [117] print.ftable*
#> [118] print.fun_list*
#> [119] print.function
#> [120] print.getAnywhere*
#> [121] print.ggplot*
#> [122] print.ggplot2_bins*
#> [123] print.ggproto*
#> [124] print.ggproto_method*
#> [125] print.gList*
#> [126] print.glm*
#> [127] print.glue*
#> [128] print.gpar*
#> [129] print.GridCoords*
#> [130] print.GridGrobCoords*
#> [131] print.GridGTreeCoords*
#> [132] print.grob*
#> [133] print.gtable*
#> [134] print.hashtab*
#> [135] print.hcl_palettes*
#> [136] print.hclust*
#> [137] print.help_files_with_topic*
#> [138] print.hexmode
#> [139] print.hms*
#> [140] print.HoltWinters*
#> [141] print.hsearch*
#> [142] print.hsearch_db*
#> [143] print.htest*
#> [144] print.html*
#> [145] print.html_dependency*
#> [146] print.htmltools.selector*
#> [147] print.htmltools.selector.list*
#> [148] print.infl*
#> [149] print.integrate*
#> [150] print.isoreg*
#> [151] print.json*
#> [152] print.key_missing*
#> [153] print.kmeans*
#> [154] print.knitr_kable*
#> [155] print.last_dplyr_warnings*
#> [156] print.Latex*
#> [157] print.LaTeX*
#> [158] print.libraryIQR
#> [159] print.lifecycle_warnings*
#> [160] print.listof
#> [161] print.lm*
#> [162] print.loadings*
#> [163] print.locale*
#> [164] print.loess*
#> [165] print.logLik*
#> [166] print.ls_str*
#> [167] print.medpolish*
#> [168] print.MethodsFunction*
#> [169] print.mtable*
#> [170] print.NativeRoutineList
#> [171] print.news_db*
#> [172] print.nls*
#> [173] print.noquote
#> [174] print.numeric_version
#> [175] print.object_size*
#> [176] print.octmode
#> [177] print.packageDescription*
#> [178] print.packageInfo
#> [179] print.packageIQR*
#> [180] print.packageStatus*
#> [181] print.paged_df*
#> [182] print.pairwise.htest*
#> [183] print.path*
#> [184] print.person*
#> [185] print.Personne
#> [186] print.pillar*
#> [187] print.pillar_1e*
#> [188] print.pillar_colonnade*
#> [189] print.pillar_ornament*
#> [190] print.pillar_shaft*
#> [191] print.pillar_squeezed_colonnade*
#> [192] print.pillar_tbl_format_setup*
#> [193] print.pillar_vctr*
#> [194] print.pillar_vctr_attr*
#> [195] print.POSIXct
#> [196] print.POSIXlt
#> [197] print.power.htest*
#> [198] print.ppr*
#> [199] print.prcomp*
#> [200] print.princomp*
#> [201] print.proc_time
#> [202] print.purrr_function_compose*
#> [203] print.purrr_function_partial*
#> [204] print.purrr_rate_backoff*
#> [205] print.purrr_rate_delay*
#> [206] print.quosure*
#> [207] print.quosures*
#> [208] print.R6*
#> [209] print.R6ClassGenerator*
#> [210] print.raster*
#> [211] print.Rconcordance*
#> [212] print.Rd*
#> [213] print.recordedplot*
#> [214] print.rel*
#> [215] print.restart
#> [216] print.RGBcolorConverter*
#> [217] print.RGlyphFont*
#> [218] print.rlang_box_done*
#> [219] print.rlang_box_splice*
#> [220] print.rlang_data_pronoun*
#> [221] print.rlang_dict*
#> [222] print.rlang_dyn_array*
#> [223] print.rlang_envs*
#> [224] print.rlang_error*
#> [225] print.rlang_fake_data_pronoun*
#> [226] print.rlang_lambda_function*
#> [227] print.rlang_message*
#> [228] print.rlang_trace*
#> [229] print.rlang_warning*
#> [230] print.rlang_zap*
#> [231] print.rlang:::list_of_conditions*
#> [232] print.rle
#> [233] print.rlib_bytes*
#> [234] print.rlib_error_3_0*
#> [235] print.rlib_trace_3_0*
#> [236] print.roman*
#> [237] print.scalar*
#> [238] print.sessionInfo*
#> [239] print.shiny.tag*
#> [240] print.shiny.tag.env*
#> [241] print.shiny.tag.list*
#> [242] print.shiny.tag.query*
#> [243] print.simple.list
#> [244] print.smooth.spline*
#> [245] print.socket*
#> [246] print.src*
#> [247] print.srcfile
#> [248] print.srcref
#> [249] print.Statistiques
#> [250] print.stepfun*
#> [251] print.stl*
#> [252] print.stringr_view*
#> [253] print.StructTS*
#> [254] print.subdir_tests*
#> [255] print.summarize_CRAN_check_status*
#> [256] print.summary.aov*
#> [257] print.summary.aovlist*
#> [258] print.summary.ecdf*
#> [259] print.summary.glm*
#> [260] print.summary.lm*
#> [261] print.summary.loess*
#> [262] print.summary.manova*
#> [263] print.summary.nls*
#> [264] print.summary.packageStatus*
#> [265] print.summary.ppr*
#> [266] print.summary.prcomp*
#> [267] print.summary.princomp*
#> [268] print.summary.table
#> [269] print.summary.warnings
#> [270] print.summaryDefault
#> [271] print.table
#> [272] print.tables_aov*
#> [273] print.tbl*
#> [274] print.terms*
#> [275] print.theme*
#> [276] print.tidyverse_conflicts*
#> [277] print.tidyverse_logo*
#> [278] print.transform*
#> [279] print.trunc_mat*
#> [280] print.ts*
#> [281] print.tskernel*
#> [282] print.TukeyHSD*
#> [283] print.tukeyline*
#> [284] print.tukeysmooth*
#> [285] print.undoc*
#> [286] print.uneval*
#> [287] print.unit*
#> [288] print.vctrs_bytes*
#> [289] print.vctrs_sclr*
#> [290] print.vctrs_unspecified*
#> [291] print.vctrs_vctr*
#> [292] print.viewport*
#> [293] print.vignette*
#> [294] print.warnings
#> [295] print.xfun_raw_string*
#> [296] print.xfun_record_results*
#> [297] print.xfun_rename_seq*
#> [298] print.xfun_strict_list*
#> [299] print.xgettext*
#> [300] print.xngettext*
#> [301] print.xtabs*
#> see '?methods' for accessing help and source code
#> [1] summary.aov summary.aovlist*
#> [3] summary.aspell* summary.check_packages_in_dir*
#> [5] summary.connection summary.data.frame
#> [7] summary.Date summary.default
#> [9] summary.Duration* summary.ecdf*
#> [11] summary.factor summary.ggplot*
#> [13] summary.glm summary.hcl_palettes*
#> [15] summary.infl* summary.Interval*
#> [17] summary.lm summary.loess*
#> [19] summary.manova summary.matrix
#> [21] summary.mlm* summary.nls*
#> [23] summary.packageStatus* summary.Period*
#> [25] summary.Personne summary.POSIXct
#> [27] summary.POSIXlt summary.ppr*
#> [29] summary.prcomp* summary.princomp*
#> [31] summary.proc_time summary.rlang_error*
#> [33] summary.rlang_message* summary.rlang_trace*
#> [35] summary.rlang_warning* summary.rlang:::list_of_conditions*
#> [37] summary.srcfile summary.srcref
#> [39] summary.Statistiques summary.stepfun
#> [41] summary.stl* summary.table
#> [43] summary.tukeysmooth* summary.vctrs_sclr*
#> [45] summary.vctrs_vctr* summary.warnings
#> see '?methods' for accessing help and source code
#> [1] add1 alias anova case.names coerce
#> [6] confint cooks.distance deviance dfbeta dfbetas
#> [11] drop1 dummy.coef effects extractAIC family
#> [16] formula fortify hatvalues influence initialize
#> [21] kappa labels logLik model.frame model.matrix
#> [26] nobs plot predict print proj
#> [31] qr residuals rstandard rstudent show
#> [36] simulate slotsFromS3 summary variable.names vcov
#> see '?methods' for accessing help and source code
#> [1] [ [[ [[<- [<-
#> [5] %within% $<- add_count aggregate
#> [9] anti_join anyDuplicated anyNA arrange_
#> [13] arrange as_tibble as.col_spec as.data.frame
#> [17] as.list as.matrix as.tbl as.vector
#> [21] auto_copy by cbind coerce
#> [25] collapse collect complete_ complete
#> [29] compute count cross_join dim
#> [33] dimnames dimnames<- distinct_ distinct
#> [37] do_ do dplyr_col_modify dplyr_reconstruct
#> [41] dplyr_row_slice drop_na_ drop_na droplevels
#> [45] duplicated edit expand_ expand
#> [49] extract_ extract fill_ fill
#> [53] filter_ filter format formula
#> [57] fortify full_join gather_ gather
#> [61] ggplot_add glimpse group_by_ group_by
#> [65] group_data group_indices_ group_indices group_keys
#> [69] group_map group_modify group_nest group_size
#> [73] group_split group_trim group_vars groups
#> [77] head initialize inner_join intersect
#> [81] is.na knit_print left_join Math
#> [85] merge mutate_ mutate n_groups
#> [89] na.exclude na.omit nest_by nest_join
#> [93] nest_legacy nest Ops pivot_longer
#> [97] pivot_wider plot print prompt
#> [101] pull rbind reframe relocate
#> [105] rename_ rename_with rename replace_na
#> [109] right_join row.names row.names<- rows_append
#> [113] rows_delete rows_insert rows_patch rows_update
#> [117] rows_upsert rowsum rowwise same_src
#> [121] sample_frac sample_n select_ select
#> [125] semi_join separate_ separate_rows_ separate_rows
#> [129] separate setdiff setequal show
#> [133] slice_ slice_head slice_max slice_min
#> [137] slice_sample slice_tail slice slotsFromS3
#> [141] sort_by split split<- spread_
#> [145] spread stack str subset
#> [149] summarise_ summarise summary Summary
#> [153] symdiff t tail tally
#> [157] tbl_vars transform transmute_ transmute
#> [161] type.convert uncount ungroup union_all
#> [165] union unique unite_ unite
#> [169] unnest_legacy unnest unstack within
#> [173] xtfrm
#> see '?methods' for accessing help and source code
C’est ici que le système S3 brille vraiment. La fonction plot() est générique et possède de nombreuses méthodes :
# Plot d'un vecteur simple
x <- 1:10
plot(x) # Utilise plot.default()
# Plot d'une formule
plot(mpg ~ wt, data = mtcars) # Nuage de points
# Plot d'un modèle linéaire
modele <- lm(mpg ~ wt, data = mtcars)
plot(modele) # Utilise plot.lm() - produit 4 graphiques de diagnostic !
# Plot d'une série temporelle
ts_data <- ts(1:100 + rnorm(100), frequency = 12)
plot(ts_data) # Utilise plot.ts() - affichage adapté aux sériesChaque type d’objet a un affichage graphique adapté, sans que vous ayez à le spécifier !
#> [1] plot,ANY-method plot,color-method plot.acf*
#> [4] plot.data.frame* plot.decomposed.ts* plot.default
#> [7] plot.dendrogram* plot.density* plot.ecdf
#> [10] plot.factor* plot.formula* plot.function
#> [13] plot.ggplot* plot.gtable* plot.hcl_palettes*
#> [16] plot.hclust* plot.histogram* plot.HoltWinters*
#> [19] plot.isoreg* plot.lm* plot.medpolish*
#> [22] plot.mlm* plot.ppr* plot.prcomp*
#> [25] plot.princomp* plot.profile* plot.profile.nls*
#> [28] plot.R6* plot.raster* plot.spec*
#> [31] plot.stepfun plot.stl* plot.table*
#> [34] plot.transform* plot.ts plot.tskernel*
#> [37] plot.TukeyHSD*
#> see '?methods' for accessing help and source code
C’est puissant ! Quand vous ferez des analyses statistiques (régressions, tests, etc.), vous pourrez simplement taper plot(modele) et obtenir des graphiques de diagnostic adaptés automatiquement.
Nous approfondirons la création de graphiques avec R dans un cours ultérieur, où vous verrez comment plot() et le système graphique de R exploitent pleinement le système S3 pour créer des visualisations adaptées à chaque type de données.
Le système de classes S3 explique le polymorphisme en R :
class) attachée à un objetprint(), summary(), plot())fonction.classe()class(), methods(), inherits()Pourquoi c’est important pour vous ? - Comprendre pourquoi print() affiche différemment un vecteur, un data frame, ou un modèle - Comprendre pourquoi summary() donne des statistiques pour un vecteur, mais des comptages pour un facteur - Anticiper la visualisation : plot() s’adaptera automatiquement au type de données et d’analyses que vous utiliserez
Vous n’avez pas besoin de créer vos propres classes S3 pour l’instant. L’important est de comprendre comment le système fonctionne pour mieux utiliser R et interpréter son comportement. Ce mécanisme est au cœur de nombreux packages R et rend le langage à la fois puissant et intuitif.
Félicitations ! Vous avez maintenant les bases solides du langage R. Vous maîtrisez les structures de données fondamentales (vecteurs, listes, data frames, facteurs), l’import/export, la création de fonctions, et vous comprenez comment R organise ses objets en interne. Vous êtes prêts pour l’analyse de données… et l’apprentissage des outils de visualisation !

Nicolas Klutchnikoff - Université Rennes 2