$$ \newcommand{\RR}{\mathbb{R}} \newcommand{\NN}{\mathbb{N}} \newcommand{\ZZ}{\mathbb{Z}} \newcommand{\QQ}{\mathbb{Q}} \newcommand{\CC}{\mathbb{C}} \newcommand{\PP}{\mathsf{P}} \newcommand{\EE}{\mathsf{E}} \newcommand{\Var}{\operatorname{Var}} \newcommand{\Cov}{\operatorname{Cov}} \newcommand{\argmin}{\operatorname{arg\,min}} \newcommand{\argmax}{\operatorname{arg\,max}} \newcommand{\norm}[1]{\left\| #1 \right\|} \newcommand{\abs}[1]{\left| #1 \right|} \newcommand{\set}[1]{\left\{ #1 \right\}} $$

Initiation au langage de programmation R

Nicolas Klutchnikoff

Université Rennes 2

10 novembre 2025

Introduction et philosophie de R

Si vous venez de Python, R peut sembler déroutant au premier abord. Les deux langages ont des philosophies très différentes :

  • Python privilégie une approche itérative (sauf à utiliser des packages comme numpy) : on parcourt les éléments un par un avec des boucles for.
  • R privilégie une approche vectorielle : on opère sur des collections entières en une seule instruction.

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) :

nombres = [1, 2, 3, 4, 5]
doubles = []
for n in nombres:
    doubles.append(n * 2)

En R (approche vectorielle) :

nombres <- c(1, 2, 3, 4, 5)
doubles <- nombres * 2
print(doubles)
#> [1]  2  4  6  8 10

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.

x <- c(10, 20, 30, 40)
x[1]  # Premier élément : 10
#> [1] 10
x[4]  # Dernier élément : 40
#> [1] 40

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 :

  • Orienté vers le calcul numérique et statistique
  • Très riche en fonctions d’analyse de données
  • Doté d’excellentes capacités graphiques
  • Avec un écosystème de packages (bibliothèques) très développé

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 :

  • Une console pour exécuter des commandes interactives
  • Un éditeur de scripts pour écrire et sauvegarder du code
  • Un visualiseur de données et de graphiques
  • Un gestionnaire de packages et d’aide

Vous pouvez exécuter du code R de plusieurs manières :

  • Directement dans la console (pour tester rapidement)
  • Dans un script R (fichier .R) que vous exécutez ligne par ligne ou en entier
  • Dans un document Quarto ou R Markdown (fichier .qmd ou .Rmd) qui mélange code et texte formaté

L’opérateur d’assignation

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
print(y)
#> [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.

Obtenir de l’aide

R possède un système d’aide très complet. Pour obtenir de l’aide sur une fonction :

?mean  # Ouvre la documentation de la fonction mean
help(sum)  # Équivalent, ouvre la documentation de sum
# On peut aussi utiliser la touche `F1` dans RSStudio après avoir placé le curseur sur le nom de la fonction.

Si vous ne connaissez pas le nom exact de la fonction, utilisez la recherche :

??regression  # Recherche "regression" dans toute la documentation

Les types primitifs

R possède quatre types de données primitifs principaux :

1. Numeric (nombres réels)

a <- 3.14
b <- 42
class(a)
#> [1] "numeric"
class(b)
#> [1] "numeric"

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 :

c <- 42L
class(c)
#> [1] "integer"

3. Character (chaînes de caractères)

Les chaînes se délimitent avec des guillemets simples ou doubles :

nom <- "Alice"
prenom <- 'Bob'
class(nom)
#> [1] "character"

4. Logical (booléens)

Les valeurs logiques sont TRUE et FALSE (en majuscules, ou T et F en abrégé) :

vrai <- TRUE
faux <- FALSE
class(vrai)
#> [1] "logical"
# Résultat d'une comparaison
5 > 3
#> [1] TRUE
10 == 10
#> [1] TRUE

Afficher des valeurs

Pour afficher le contenu d’une variable, plusieurs options :

x <- 42

# Option 1 : taper simplement le nom de la variable
x
#> [1] 42
# Option 2 : utiliser print()
print(x)
#> [1] 42
# Option 3 : utiliser cat() pour un affichage plus simple
cat("La valeur de x est :", x, "\n")
#> La valeur de x est : 42

Style et conventions de code

Le code est lu bien plus souvent qu’il n’est écrit. Un style cohérent rend votre code :

  • Plus lisible : vous et les autres comprendrez rapidement ce que fait le code
  • Plus maintenable : retrouver et corriger des erreurs devient plus facile
  • Plus professionnel : respecter les standards de la communauté R
  • Plus collaboratif : facilite le travail en équipe

Adopter de bonnes habitudes dès le début vous fera gagner énormément de temps par la suite.

Variables et fonctions

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 <- 25

Privilégiez des noms explicites plutôt que des abréviations obscures. Le code temperature_celsius est immédiatement compréhensible, contrairement à temp ou t.

Constantes

Pour les constantes globales, utilisez les MAJUSCULES :

PI_APPROXIMATION <- 3.14159
SEUIL_SIGNIFICANCE <- 0.05

Un bon espacement améliore considérablement la lisibilité du code.

Autour des opérateurs

Mettez toujours des espaces autour des opérateurs d’assignation et arithmétiques :

# Bon
x <- 5
y <- x + 2
moyenne <- sum(x) / length(x)

# Mauvais (difficile à lire)
x<-5
y<-x+2
moyenne<-sum(x)/length(x)

Après les virgules

Mettez une espace après chaque virgule, mais pas avant :

# Bon
vecteur <- c(1, 2, 3, 4, 5)
resultat <- fonction(x = 10, y = 20, z = 30)

# Mauvais
vecteur <- c(1,2,3,4,5)
resultat <- fonction(x=10,y=20,z=30)

Autour des parenthèses

Mettez un espace après les mots-clés de contrôle (if, for, while), mais pas après les noms de fonctions :

# Bon
if (x > 0) {
  print("positif")
}
#> [1] "positif"
resultat <- mean(x)

# Mauvais
if(x>0){
  print("positif")
}
#> [1] "positif"
resultat <- mean (x)  # Espace superflu

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"
# Bon : style K&R pour les accolades
for (i in 1:5) {
  resultat <- i * 2
  print(resultat)
}
#> [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.

Quoi commenter ?

Expliquez le pourquoi, pas le quoi. Le code lui-même devrait montrer ce qu’il fait.

# Bon : explique le contexte et la raison
# Conversion entre unités impériales et métriques
x_corrige <- x * facteur_conversion

# Moins utile : répète simplement le code
# Multiplie x par facteur_conversion
x_corrige <- x * facteur_conversion

Commentaires en fin de ligne

Pour les commentaires courts, vous pouvez les placer en fin de ligne :

n <- 1000  # Taille de l'échantillon
alpha <- 0.05  # Niveau de signification

RStudio intègre plusieurs outils pour vous aider :

  • Réindentation automatique : Ctrl/Cmd + I
  • Reformatage du code : l’extension styler peut reformater automatiquement votre code selon les conventions
  • Vérification du style : le package lintr analyse votre code et signale les problèmes de style

Pour installer ces packages (nous verrons les packages plus en détail plus tard) :

install.packages("styler")
install.packages("lintr")

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.

Vecteurs : l’objet fondamental de 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.

La fonction c() : combine

La fonction la plus utilisée pour créer un vecteur est c() (pour “combine” ou “concatenate”) :

nombres <- c(1, 2, 3, 4, 5)
print(nombres)
#> [1] 1 2 3 4 5
prenoms <- c("Alice", "Bob", "Charlie")
print(prenoms)
#> [1] "Alice"   "Bob"     "Charlie"
logiques <- c(TRUE, FALSE, TRUE, TRUE)
print(logiques)
#> [1]  TRUE FALSE  TRUE  TRUE

On peut aussi combiner des vecteurs entre eux :

v1 <- c(1, 2, 3)
v2 <- c(4, 5, 6)
v_total <- c(v1, v2)
print(v_total)
#> [1] 1 2 3 4 5 6

L’opérateur : : séquences simples

Pour créer une séquence d’entiers consécutifs, l’opérateur : est très pratique :

sequence <- 1:10
print(sequence)
#>  [1]  1  2  3  4  5  6  7  8  9 10
sequence_inverse <- 10:1
print(sequence_inverse)
#>  [1] 10  9  8  7  6  5  4  3  2  1
sequence_negative <- -5:5
print(sequence_negative)
#>  [1] -5 -4 -3 -2 -1  0  1  2  3  4  5

La fonction seq() : séquences avancées

Pour plus de contrôle, utilisez seq() qui permet de spécifier le pas :

seq(1, 10, by = 2)  # De 1 à 10 par pas de 2
#> [1] 1 3 5 7 9
seq(0, 1, by = 0.1)  # De 0 à 1 par pas de 0.1
#>  [1] 0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0
seq(0, 1, length.out = 11)  # 11 valeurs équidistantes entre 0 et 1
#>  [1] 0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0

La fonction rep() : répétitions

Pour répéter des valeurs :

rep(5, times = 10)  # Répète 5 dix fois
#>  [1] 5 5 5 5 5 5 5 5 5 5
rep(c(1, 2, 3), times = 3)  # Répète le vecteur entier 3 fois
#> [1] 1 2 3 1 2 3 1 2 3
rep(c(1, 2, 3), each = 3)  # Répète chaque élément 3 fois
#> [1] 1 1 1 2 2 2 3 3 3
rep(c(9, 8, 7), times = c(2, 3, 4))  # Répète 9 deux fois, 8 trois fois, 7 quatre fois
#> [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"
class(melange1)
#> [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
class(melange2)
#> [1] "numeric"
# Mélange de logical et character → tout devient character
melange3 <- c(TRUE, "faux")
print(melange3)
#> [1] "TRUE" "faux"
class(melange3)
#> [1] "character"

Coercition explicite

Vous pouvez forcer la conversion avec les fonctions as.*() :

x <- c("1", "2", "3")  # Vecteur de caractères
print(x)
#> [1] "1" "2" "3"
x_numeric <- as.numeric(x)  # Conversion en numeric
print(x_numeric)
#> [1] 1 2 3
y <- c(0, 1, 1, 0)
y_logical <- as.logical(y)  # 0 → FALSE, tout le reste → TRUE
print(y_logical)
#> [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.

Arithmétique vectorielle

x <- c(1, 2, 3, 4, 5)
y <- c(10, 20, 30, 40, 50)

x + y  # Addition élément par élément
#> [1] 11 22 33 44 55
x * y  # Multiplication élément par élément
#> [1]  10  40  90 160 250
x^2  # Élévation au carré de chaque élément
#> [1]  1  4  9 16 25
sqrt(x)  # Racine carrée de chaque élément
#> [1] 1.000000 1.414214 1.732051 2.000000 2.236068

Avec une constante, l’opération s’applique à tous les éléments :

x <- c(1, 2, 3, 4, 5)
x + 10  # Ajoute 10 à chaque élément
#> [1] 11 12 13 14 15
x * 2  # Double chaque élément
#> [1]  2  4  6  8 10

Le recyclage des vecteurs

Quand deux vecteurs de longueurs différentes sont combinés, R recycle le plus court :

x <- c(1, 2, 3, 4, 5, 6)
y <- c(10, 20)

x + y  # y est recyclé : c(10, 20, 10, 20, 10, 20)
#> [1] 11 22 13 24 15 26

R vous avertit si la longueur du plus long n’est pas un multiple du plus court :

x <- c(1, 2, 3, 4, 5)
y <- c(10, 20)
x + y  # Avertissement : longueur 5 n'est pas un multiple de 2
#> [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 :

x <- c(1, 2, 3, 4, 5)
x * 2  # 2 est recyclé 5 fois
#> [1]  2  4  6  8 10

Fonctions vectorielles

R possède de nombreuses fonctions qui opèrent sur des vecteurs entiers :

x <- c(2, 5, 3, 9, 1, 7)

length(x)  # Nombre d'éléments
#> [1] 6
sum(x)  # Somme
#> [1] 27
mean(x)  # Moyenne
#> [1] 4.5
median(x)  # Médiane
#> [1] 4
sd(x)  # Écart-type
#> [1] 3.082207
var(x)  # Variance
#> [1] 9.5
min(x)  # Minimum
#> [1] 1
max(x)  # Maximum
#> [1] 9
range(x)  # Min et max
#> [1] 1 9
sort(x)  # Tri croissant
#> [1] 1 2 3 5 7 9
sort(x, decreasing = TRUE)  # Tri décroissant
#> [1] 9 7 5 3 2 1

Opérations logiques

Les comparaisons produisent des vecteurs logiques :

x <- c(1, 5, 3, 8, 2)

x > 3  # Quels éléments sont > 3 ?
#> [1] FALSE  TRUE FALSE  TRUE FALSE
x == 5  # Quels éléments sont égaux à 5 ?
#> [1] FALSE  TRUE FALSE FALSE FALSE
3 <= x & x <= 7  # Entre 3 et 7 ?
#> [1] FALSE  TRUE  TRUE FALSE FALSE
# Compter les éléments satisfaisant une condition
sum(x > 3)  # TRUE compte pour 1, FALSE pour 0
#> [1] 2

L’indexation est fondamentale en R. Il existe plusieurs façons d’accéder aux éléments d’un vecteur.

Indexation par position (entiers positifs)

Rappel : l’indexation commence à 1 en R.

x <- c(10, 20, 30, 40, 50)

x[1]  # Premier élément
#> [1] 10
x[3]  # Troisième élément
#> [1] 30
x[5]  # Cinquième élément (dernier)
#> [1] 50
# Plusieurs éléments à la fois
x[c(1, 3, 5)]  # Éléments 1, 3 et 5
#> [1] 10 30 50
# Utiliser une séquence
x[1:3]  # Éléments 1 à 3
#> [1] 10 20 30
# Répéter des indices
x[c(1, 1, 2, 2, 3)]  # Duplique des éléments
#> [1] 10 10 20 20 30

Indexation négative (exclusion)

Les indices négatifs excluent des éléments :

x <- c(10, 20, 30, 40, 50)

x[-1]  # Tout sauf le premier
#> [1] 20 30 40 50
x[-5]  # Tout sauf le dernier
#> [1] 10 20 30 40
x[-c(2, 4)]  # Tout sauf les éléments 2 et 4
#> [1] 10 30 50
x[-(1:3)]  # Tout sauf les trois premiers
#> [1] 40 50

Attention : on ne peut pas mélanger indices positifs et négatifs.

Indexation logique

L’indexation logique est extrêmement puissante et très utilisée en R :

x <- c(1, 5, 3, 8, 2, 9, 4)

# Créer un masque logique
masque <- x > 4
print(masque)
#> [1] FALSE  TRUE FALSE  TRUE FALSE  TRUE FALSE
# Utiliser le masque pour filtrer
x[masque]
#> [1] 5 8 9
# Souvent écrit directement
x[x > 4]  # Tous les éléments > 4
#> [1] 5 8 9
x[3 <= x & x <= 7]  # Entre 3 et 7
#> [1] 5 3 4
x[x == 5 | x == 9]  # Égaux à 5 ou 9
#> [1] 5 9

L’indexation logique est la méthode privilégiée pour filtrer des données en R.

Indexation par noms

On peut nommer les éléments d’un vecteur :

temperatures <- c(Paris = 15, Londres = 12, Berlin = 10, Rome = 18)
print(temperatures)
#>   Paris Londres  Berlin    Rome 
#>      15      12      10      18
# Accès par nom
temperatures["Paris"]
#> Paris 
#>    15
temperatures[c("Paris", "Rome")]
#> Paris  Rome 
#>    15    18
# Voir/modifier les noms
names(temperatures)
#> [1] "Paris"   "Londres" "Berlin"  "Rome"
names(temperatures) <- c("Par", "Lon", "Ber", "Rom")
print(temperatures)
#> Par Lon Ber Rom 
#>  15  12  10  18

Modification par indexation

On peut modifier des éléments en combinant indexation et assignation :

x <- c(1, 2, 3, 4, 5)

x[3] <- 99  # Remplace le 3e élément
print(x)
#> [1]  1  2 99  4  5
x[c(1, 5)] <- c(11, 55)  # Remplace plusieurs éléments
print(x)
#> [1] 11  2 99  4 55
x[x > 50] <- 0  # Remplace tous les éléments > 50 par 0
print(x)
#> [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 :

x <- c(1, 2, NA, 4, 5)
print(x)
#> [1]  1  2 NA  4  5

Les opérations avec NA propagent le manquant :

sum(x)  # Résultat : NA
#> [1] NA
mean(x)  # Résultat : NA
#> [1] NA
# Solution : ignorer les NA
sum(x, na.rm = TRUE)  # na.rm = "NA remove"
#> [1] 12
mean(x, na.rm = TRUE)
#> [1] 3

Tester la présence de NA :

is.na(x)  # Vecteur logique indiquant où sont les NA
#> [1] FALSE FALSE  TRUE FALSE FALSE
# Compter les NA
sum(is.na(x))
#> [1] 1
# Filtrer les NA
x[!is.na(x)]  # ! signifie "non"
#> [1] 1 2 4 5
# Alternative : na.omit()
na.omit(x)
#> [1] 1 2 4 5
#> attr(,"na.action")
#> [1] 3
#> attr(,"class")
#> [1] "omit"

NULL : l’absence d’objet

NULL représente l’absence d’un objet (différent d’une valeur manquante) :

x <- NULL
print(x)
#> NULL
length(NULL)  # Longueur 0
#> [1] 0
# NULL est souvent utilisé pour initialiser ou supprimer
y <- c(1, 2, 3)
y <- NULL  # Supprime l'objet

NaN : Not a Number (résultat indéfini)

NaN apparaît lors d’opérations mathématiques indéfinies :

0 / 0  # Division indéfinie
#> [1] NaN
sqrt(-1)  # Racine carrée d'un nombre négatif (utiliser complex pour les nombres complexes)
#> [1] NaN
is.nan(NaN)
#> [1] TRUE
is.na(NaN)  # NaN est aussi considéré comme NA
#> [1] TRUE

Inf et -Inf : infini

Représentent l’infini mathématique :

1 / 0  # Infini positif
#> [1] Inf
-1 / 0  # Infini négatif
#> [1] -Inf
is.infinite(Inf)
#> [1] TRUE
is.finite(Inf)
#> [1] FALSE
# Calculs avec l'infini
Inf + 1
#> [1] Inf
Inf * 2
#> [1] Inf
Inf - Inf  # NaN !
#> [1] NaN

Les vecteurs sont la structure fondamentale de R. Retenez :

  • Création : c(), :, seq(), rep()
  • Un vecteur = un seul type (coercition automatique sinon)
  • Opérations vectorielles : élément par élément
  • Recyclage automatique des vecteurs courts
  • Indexation : par position, négative, logique, par noms
  • Valeurs spéciales : NA, NULL, NaN, Inf

Maintenant 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.

Structures de contrôle

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 !

Syntaxe de base

x <- 5

if (x > 0) {
  print("x est positif")
}
#> [1] "x est positif"

Avec else :

x <- -3

if (x > 0) {
  print("x est positif")
} else {
  print("x est négatif ou nul")
}
#> [1] "x est négatif ou nul"

Conditions multiples avec else if

note <- 15

if (note >= 16) {
  mention <- "Très bien"
} else if (note >= 14) {
  mention <- "Bien"
} else if (note >= 12) {
  mention <- "Assez bien"
} else if (note >= 10) {
  mention <- "Passable"
} else {
  mention <- "Insuffisant"
}

print(mention)
#> [1] "Bien"

Conditions composées

age <- 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 :

ages <- c(5, 15, 25, 65, 80)
categories <- ifelse(ages < 18, "Mineur",
                     ifelse(ages < 65, "Adulte", "Senior"))
print(categories)
#> [1] "Mineur" "Mineur" "Adulte" "Senior" "Senior"

Syntaxe de base

# Boucle sur une séquence
for (i in 1:5) {
  print(i)
}
#> [1] 1
#> [1] 2
#> [1] 3
#> [1] 4
#> [1] 5
# Boucle sur un vecteur
fruits <- c("pomme", "banane", "orange")
for (fruit in fruits) {
  print(fruit)
}
#> [1] "pomme"
#> [1] "banane"
#> [1] "orange"

Exemple : accumuler des résultats

# 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.

Vectorisation vs boucle

# Avec boucle (moins efficace)
n <- 1000
resultats <- numeric(n)
for (i in 1:n) {
  resultats[i] <- i^2
}

# Vectorisé (beaucoup plus efficace)
resultats <- (1:n)^2

Principe : essayez toujours de vectoriser d’abord. La boucle est une solution de secours.

Quand les boucles sont nécessaires

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 !

# Exemple : trouver le premier nombre dont le carré dépasse 100
x <- 1
while (x^2 <= 100) {
  x <- x + 1
}
print(x)  # Premier entier dont le carré > 100
#> [1] 11

break : sortir d’une boucle

for (i in 1:10) {
  if (i > 5) {
    break  # Sort de la boucle
  }
  print(i)
}
#> [1] 1
#> [1] 2
#> [1] 3
#> [1] 4
#> [1] 5
# Affiche 1, 2, 3, 4, 5 puis s'arrête

next : passer à l’itération suivante

for (i in 1:10) {
  if (i %% 2 == 0) {
    next  # Saute les nombres pairs
  }
  print(i)
}
#> [1] 1
#> [1] 3
#> [1] 5
#> [1] 7
#> [1] 9
# Affiche seulement les nombres impairs : 1, 3, 5, 7, 9

Exemple combiné

# 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 :

# Table de multiplication
for (i in 1:3) {
  for (j in 1:3) {
    cat(i, "x", j, "=", i * j, "\n")
  }
}
#> 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) :

# Produit extérieur (outer product)
i <- 1:3
j <- 1:3
outer(i, j, "*")
#>      [,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 vecteur
  • while : boucle tant qu’une condition est vraie
  • break : sortir d’une boucle
  • next : passer à l’itération suivante

Philosophie 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.

Matrices : extension bidimensionnelle

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 :

# Un simple vecteur
v <- 1:12
print(v)
#>  [1]  1  2  3  4  5  6  7  8  9 10 11 12
# Ajoutons un attribut de dimension
dim(v) <- c(3, 4)  # 3 lignes, 4 colonnes
print(v)
#>      [,1] [,2] [,3] [,4]
#> [1,]    1    4    7   10
#> [2,]    2    5    8   11
#> [3,]    3    6    9   12
# v est maintenant une matrice !
class(v)
#> [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.

La fonction matrix()

La manière standard de créer une matrice est d’utiliser matrix() :

# Créer une matrice 3x4 à partir d'un vecteur
M <- matrix(1:12, nrow = 3, ncol = 4)
print(M)
#>      [,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

Combiner des vecteurs : 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
# Combiner par colonnes (column bind)
M_cbind <- cbind(v1, v2, v3)
print(M_cbind)
#>      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.

Accès aux éléments

M <- matrix(1:12, nrow = 3, ncol = 4)
print(M)
#>      [,1] [,2] [,3] [,4]
#> [1,]    1    4    7   10
#> [2,]    2    5    8   11
#> [3,]    3    6    9   12
# Un élément spécifique
M[2, 3]  # Ligne 2, colonne 3
#> [1] 8
# Une ligne entière (retourne un vecteur)
M[2, ]  # Toute la ligne 2
#> [1]  2  5  8 11
# Une colonne entière (retourne un vecteur)
M[, 3]  # Toute la colonne 3
#> [1] 7 8 9

Sous-matrices

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
# Avec des séquences
M[1:2, 2:4]  # Lignes 1-2, colonnes 2-4
#>      [,1] [,2] [,3]
#> [1,]    4    7   10
#> [2,]    5    8   11
# Exclusion (indices négatifs)
M[-1, ]  # Tout sauf la première ligne
#>      [,1] [,2] [,3] [,4]
#> [1,]    2    5    8   11
#> [2,]    3    6    9   12
M[, -c(1, 3)]  # Tout sauf les colonnes 1 et 3
#>      [,1] [,2]
#> [1,]    4   10
#> [2,]    5   11
#> [3,]    6   12

Indexation logique

Comme pour les vecteurs, on peut utiliser des conditions :

M <- matrix(1:12, nrow = 3, ncol = 4)

# Filtrer les lignes selon une condition
M[M[, 1] > 1, ]  # Lignes où la première colonne > 1
#>      [,1] [,2] [,3] [,4]
#> [1,]    2    5    8   11
#> [2,]    3    6    9   12
# Filtrer les colonnes
M[, colSums(M) > 15]  # Colonnes dont la somme > 15
#>      [,1] [,2]
#> [1,]    7   10
#> [2,]    8   11
#> [3,]    9   12

Modification par indexation

M <- matrix(1:12, nrow = 3, ncol = 4)

# Modifier un élément
M[2, 3] <- 99
print(M)
#>      [,1] [,2] [,3] [,4]
#> [1,]    1    4    7   10
#> [2,]    2    5   99   11
#> [3,]    3    6    9   12
# Modifier une ligne entière
M[1, ] <- c(10, 20, 30, 40)
print(M)
#>      [,1] [,2] [,3] [,4]
#> [1,]   10   20   30   40
#> [2,]    2    5   99   11
#> [3,]    3    6    9   12
# Modifier selon une condition
M[M > 10] <- 0
print(M)
#>      [,1] [,2] [,3] [,4]
#> [1,]   10    0    0    0
#> [2,]    2    5    0    0
#> [3,]    3    6    9    0
M <- matrix(1:12, nrow = 3, ncol = 4)

dim(M)  # Dimensions (lignes, colonnes)
#> [1] 3 4
nrow(M)  # Nombre de lignes
#> [1] 3
ncol(M)  # Nombre de colonnes
#> [1] 4
length(M)  # Nombre total d'éléments (= nrow * ncol)
#> [1] 12

Arithmétique élément par élément

Comme pour les vecteurs, les opérations sont élément par élément :

M1 <- matrix(1:6, nrow = 2, ncol = 3)
M2 <- matrix(10:15, nrow = 2, ncol = 3)

M1 + M2  # Addition élément par élément
#>      [,1] [,2] [,3]
#> [1,]   11   15   19
#> [2,]   13   17   21
M1 * M2  # Multiplication élément par élément (pas matricielle !)
#>      [,1] [,2] [,3]
#> [1,]   10   36   70
#> [2,]   22   52   90
M1^2  # Élévation au carré de chaque élément
#>      [,1] [,2] [,3]
#> [1,]    1    9   25
#> [2,]    4   16   36

Opérations avec un scalaire

Le recyclage s’applique aussi :

M <- matrix(1:6, nrow = 2, ncol = 3)

M + 10  # Ajoute 10 à chaque élément
#>      [,1] [,2] [,3]
#> [1,]   11   13   15
#> [2,]   12   14   16
M * 2  # Multiplie chaque élément par 2
#>      [,1] [,2] [,3]
#> [1,]    2    6   10
#> [2,]    4    8   12

Fonctions d’agrégation

M <- matrix(1:12, nrow = 3, ncol = 4)

# Sur toute la matrice
sum(M)
#> [1] 78
mean(M)
#> [1] 6.5
max(M)
#> [1] 12
min(M)
#> [1] 1
# Par ligne
rowSums(M)  # Somme de chaque ligne
#> [1] 22 26 30
rowMeans(M)  # Moyenne de chaque ligne
#> [1] 5.5 6.5 7.5
# Par colonne
colSums(M)  # Somme de chaque colonne
#> [1]  6 15 24 33
colMeans(M)  # Moyenne de chaque colonne
#> [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
apply(M, 1, max)  # Maximum de chaque ligne
#> [1] 10 11 12
# Appliquer sur les colonnes (MARGIN = 2)
apply(M, 2, sum)  # Équivalent à colSums(M)
#> [1]  6 15 24 33
apply(M, 2, mean)  # Moyenne de chaque colonne
#> [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 :

  • Création : matrix(), rbind(), cbind(), ou dim()
  • Indexation bidimensionnelle : [i, j], [i, ], [, j] ← Important pour les data frames !
  • Opérations : élément par élément (comme les vecteurs)
  • Fonctions d’agrégation : rowSums(), colMeans(), etc.
  • apply() : appliquer une fonction sur lignes ou colonnes

L’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.

Facteurs : représenter des variables catégorielles

En statistiques, on distingue deux grands types de variables :

  • Variables quantitatives : prennent des valeurs numériques mesurables (âge, taille, revenu…)
  • Variables qualitatives (ou catégorielles) : prennent un nombre limité de valeurs non numériques appelées modalités ou catégories (sexe, couleur, catégorie socio-professionnelle…)

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 :

  1. Contrôle des valeurs possibles : un facteur définit explicitement l’ensemble des modalités autorisées
  2. Efficacité mémoire : en interne, R stocke des entiers plutôt que des chaînes répétées
  3. Ordre des modalités : important pour les graphiques et les tableaux
  4. Modélisation statistique : les fonctions de modélisation (lm(), glm(), etc.) traitent automatiquement les facteurs comme des variables catégorielles

Avec factor()

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
# Observer le type
class(f_couleurs)
#> [1] "factor"

Notez l’affichage : R indique les “Levels” (modalités en anglais).

Avec as.factor()

Une alternative équivalente :

sexe <- c("H", "F", "F", "H", "H", "F")
f_sexe <- as.factor(sexe)
print(f_sexe)
#> [1] H F F H H F
#> Levels: F H

Spécifier l’ordre des modalités

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.

Contrôle des valeurs autorisées

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 :

  1. Les codes internes : un vecteur d’entiers
  2. La table de correspondance : l’attribut levels qui associe chaque code à une étiquette

Visualisation de la structure

couleurs <- factor(c("rouge", "bleu", "vert", "bleu", "rouge"))
print(couleurs)
#> [1] rouge bleu  vert  bleu  rouge
#> Levels: bleu rouge vert
# Voir les codes internes
as.integer(couleurs)
#> [1] 2 1 3 1 2
# Voir la table de correspondance
levels(couleurs)
#> [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.

Implication cruciale

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"
# Nombre de modalités
nlevels(couleurs)
#> [1] 3
# Table de fréquences
table(couleurs)
#> couleurs
#>  bleu rouge  vert 
#>     2     2     3
# Résumé
summary(couleurs)
#>  bleu rouge  vert 
#>     2     2     3

Modifier les levels change les étiquettes mais pas les codes internes :

sexe <- factor(c("H", "F", "H", "F", "H"))
print(sexe)
#> [1] H F H F H
#> Levels: F H
levels(sexe)
#> [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
# Les codes internes n'ont pas changé !
as.integer(sexe)  # Toujours les mêmes valeurs internes
#> [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).

Renommer une modalité spécifique

categorie <- factor(c("A", "B", "C", "A", "B"))
print(levels(categorie))
#> [1] "A" "B" "C"
# Renommer seulement la deuxième modalité
levels(categorie)[2] <- "Beta"
print(categorie)
#> [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.

Le mécanisme de fusion

Lorsqu’on assigne le même nom à plusieurs levels, on fusionne ces modalités :

x <- factor(c("0", "1", "2", "1", "0", "2", "1"))
print(x)
#> [1] 0 1 2 1 0 2 1
#> Levels: 0 1 2
levels(x)
#> [1] "0" "1" "2"
# Codes internes avant fusion
print("Codes internes avant :")
#> [1] "Codes internes avant :"
print(as.integer(x))
#> [1] 1 2 3 2 1 3 2
# Fusionner les modalités "1" et "2" en une seule "1"
levels(x)[c(2, 3)] <- "1"
print(x)
#> [1] 0 1 1 1 0 1 1
#> Levels: 0 1
# Codes internes après fusion : IDENTIQUES !
print("Codes internes après :")
#> [1] "Codes internes après :"
print(as.integer(x))
#> [1] 1 2 2 2 1 2 2
# Mais maintenant seulement 2 modalités distinctes
levels(x)
#> [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.

Exemple pratique : regroupement d’âges

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
print(table(age_cat))
#> age_cat
#>  Jeune Adulte Senior 
#>      3      3      1
# Codes internes : toujours 5 codes différents !
as.integer(age_cat)
#> [1] 1 1 2 2 3 1 2

Vraiment fusionner : supprimer les niveaux inutilisés

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 !

notes <- factor(c("10", "20", "15", "18", "12"))
print(notes)
#> [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.

Pourquoi ce piège existe-t-il ?

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
# La table de correspondance
levels(notes)  # "10" "12" "15" "18" "20" (ordre alphabétique !)
#> [1] "10" "12" "15" "18" "20"
# as.numeric() ne fait que révéler les codes internes

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
# Les comparaisons ont du sens
f_satisfaction[1] < f_satisfaction[2]
#> [1] TRUE
# Utile pour les modèles statistiques et les visualisations

Ajouter une modalité non utilisée

couleur <- factor(c("rouge", "bleu", "rouge"))
levels(couleur)
#> [1] "bleu"  "rouge"
# Ajouter "vert" comme modalité possible (mais pas encore observée)
levels(couleur) <- c(levels(couleur), "vert")
levels(couleur)
#> [1] "bleu"  "rouge" "vert"

Supprimer les modalités non utilisées

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
levels(x_filtre)  # "C" reste dans les levels !
#> [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 :

  • Vocabulaire : variable catégorielle/qualitative (concept) → facteur (objet R) → modalités (catégories, “levels” en anglais)
  • Structure interne : codes internes (entiers) + table de correspondance (levels)
  • Création : factor() ou as.factor()
  • Inspection : levels(), nlevels(), table()
  • Renommer : levels(f) <- nouveaux_noms (modifie la table, pas les codes)
  • Fusionner : assigner le même nom à plusieurs levels : levels(f)[c(2,3)] <- "nouveau"
  • Codes internes : restent inchangés lors du renommage/fusion (utiliser factor() ou droplevels() pour “nettoyer”)
  • Piège numérique : toujours faire as.numeric(as.character(f)), jamais as.numeric(f) directement
  • Facteurs ordonnés : pour variables ordinales avec ordered = TRUE

Les facteurs sont essentiels pour travailler avec des données catégorielles, particulièrement dans les data frames que nous verrons bientôt.

Listes : structures hétérogènes

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).

# Un vecteur : tous les éléments deviennent des caractères
v <- c(1, "deux", TRUE)
print(v)
#> [1] "1"    "deux" "TRUE"
class(v)
#> [1] "character"
# Une liste : conserve les types de chaque élément
l <- list(1, "deux", TRUE)
print(l)
#> [[1]]
#> [1] 1
#> 
#> [[2]]
#> [1] "deux"
#> 
#> [[3]]
#> [1] TRUE
class(l)
#> [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).

Avec list()

La fonction list() crée une liste en combinant n’importe quels objets :

# Liste avec des types variés
ma_liste <- list(
  42,
  "texte",
  TRUE,
  c(1, 2, 3)
)
print(ma_liste)
#> [[1]]
#> [1] 42
#> 
#> [[2]]
#> [1] "texte"
#> 
#> [[3]]
#> [1] TRUE
#> 
#> [[4]]
#> [1] 1 2 3

Listes nommées

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.

Liste contenant des structures complexes

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ément

Les doubles crochets [[]] extraient l’élément lui-même (son contenu) :

ma_liste <- list(
  nombres = c(1, 2, 3),
  texte = "Bonjour",
  logique = TRUE
)

# Extraction avec [[]]
ma_liste[[1]]  # Retourne le vecteur c(1, 2, 3)
#> [1] 1 2 3
class(ma_liste[[1]])  # C'est un vecteur numérique
#> [1] "numeric"
ma_liste[[2]]  # Retourne "Bonjour"
#> [1] "Bonjour"
class(ma_liste[[2]])  # C'est un caractère
#> [1] "character"

[] : sous-liste

Les crochets simples [] retournent toujours une sous-liste contenant les éléments sélectionnés :

ma_liste <- list(
  nombres = c(1, 2, 3),
  texte = "Bonjour",
  logique = TRUE
)

# Extraction avec []
ma_liste[1]  # Retourne UNE LISTE contenant le premier élément
#> $nombres
#> [1] 1 2 3
class(ma_liste[1])  # C'est une liste !
#> [1] "list"
ma_liste[c(1, 3)]  # Sous-liste avec les éléments 1 et 3
#> $nombres
#> [1] 1 2 3
#> 
#> $logique
#> [1] TRUE

Analogie pour comprendre la différence

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)
boite <- list(
  contenu1 = c(10, 20, 30),
  contenu2 = "texte"
)

# [] retourne une boîte (liste) contenant l'élément
x <- boite[1]
class(x)  # liste
#> [1] "list"
x  # Une liste avec un élément
#> $contenu1
#> [1] 10 20 30
# [[]] ouvre la boîte et retourne son contenu
y <- boite[[1]]
class(y)  # numeric
#> [1] "numeric"
y  # Le vecteur c(10, 20, 30)
#> [1] 10 20 30

Conséquence pratique

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 $

L’opérateur $ est un raccourci pour [[]] avec des noms :

personne <- list(
  nom = "Alice",
  age = 30,
  ville = "Paris"
)

# Accès avec $
personne$nom  # "Alice"
#> [1] "Alice"
personne$age  # 30
#> [1] 30
# Équivalent à :
personne[["nom"]]
#> [1] "Alice"
personne[["age"]]
#> [1] 30

$ est plus concis et plus lisible pour un usage interactif (console, scripts simples).

Accès avec [[]] et noms

Cependant, [[]] 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"
# Avec $, cela ne fonctionnerait pas :
# personne$champ  # Erreur : cherche un élément nommé "champ" littéralement

En résumé : $ pour l’usage quotidien, [[]] pour la programmation.

Accès avec [] et noms

Avec [], on obtient toujours une sous-liste :

personne <- list(nom = "Alice", age = 30, ville = "Paris")

personne["nom"]  # Liste avec un élément nommé "nom"
#> $nom
#> [1] "Alice"
personne[c("nom", "ville")]  # Sous-liste avec deux éléments
#> $nom
#> [1] "Alice"
#> 
#> $ville
#> [1] "Paris"

Modifier un élément

personne <- list(nom = "Alice", age = 30)

personne$age <- 31
print(personne)
#> $nom
#> [1] "Alice"
#> 
#> $age
#> [1] 31
# Ou avec [[]]
personne[["nom"]] <- "Bob"
print(personne)
#> $nom
#> [1] "Bob"
#> 
#> $age
#> [1] 31

Ajouter un élément

personne <- list(nom = "Alice", age = 30)

# Ajouter un nouvel élément
personne$ville <- "Lyon"
print(personne)
#> $nom
#> [1] "Alice"
#> 
#> $age
#> [1] 30
#> 
#> $ville
#> [1] "Lyon"
# Équivalent
personne[["profession"]] <- "Ingénieure"
print(personne)
#> $nom
#> [1] "Alice"
#> 
#> $age
#> [1] 30
#> 
#> $ville
#> [1] "Lyon"
#> 
#> $profession
#> [1] "Ingénieure"

Supprimer un élément

Pour supprimer un élément, on lui assigne NULL :

personne <- list(nom = "Alice", age = 30, ville = "Paris")

personne$ville <- NULL
print(personne)
#> $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"

Accéder à des éléments imbriqués

On enchaîne les opérateurs d’accès :

entreprise <- list(
  nom = "TechCorp",
  employes = list(
    employe1 = list(nom = "Alice", age = 30),
    employe2 = list(nom = "Bob", age = 25)
  )
)

# Accès en profondeur
entreprise$employes$employe1$nom  # "Alice"
#> [1] "Alice"
# Équivalent avec [[]]
entreprise[["employes"]][["employe1"]][["nom"]]
#> [1] "Alice"
# Mélange de $ et [[]]
entreprise$employes[[1]]$age  # 30 (premier employé)
#> [1] 30

Longueur et noms

ma_liste <- list(a = 1, b = 2, c = 3)

length(ma_liste)  # Nombre d'éléments
#> [1] 3
names(ma_liste)  # Noms des éléments
#> [1] "a" "b" "c"
# Modifier les noms
names(ma_liste) <- c("premier", "deuxieme", "troisieme")
print(ma_liste)
#> $premier
#> [1] 1
#> 
#> $deuxieme
#> [1] 2
#> 
#> $troisieme
#> [1] 3

Structure : 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 liste

lapply() (pour “list apply”) applique une fonction à chaque élément et retourne une liste :

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 <- lapply(nombres, mean)
print(moyennes)
#> $a
#> [1] 2
#> 
#> $b
#> [1] 5
#> 
#> $c
#> [1] 8
class(moyennes)  # C'est une liste
#> [1] "list"

sapply() : simplifie le résultat

sapply() (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
class(moyennes)  # C'est un vecteur numérique
#> [1] "numeric"

sapply() est souvent plus pratique car il retourne un vecteur quand c’est possible.

Fonctions anonymes

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 :

ma_liste <- list(a = 1:3, b = 4:6, c = 7:9)
print(ma_liste)
#> $a
#> [1] 1 2 3
#> 
#> $b
#> [1] 4 5 6
#> 
#> $c
#> [1] 7 8 9
vecteur <- unlist(ma_liste)
print(vecteur)
#> a1 a2 a3 b1 b2 b3 c1 c2 c3 
#>  1  2  3  4  5  6  7  8  9
class(vecteur)  # numeric
#> [1] "integer"

Attention : si la liste contient des types différents, la coercition habituelle s’applique :

liste_mixte <- list(a = 1:3, b = c("x", "y"))
unlist(liste_mixte)  # Tout devient caractère
#>  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 :

  1. C’est une liste de vecteurs
  2. Tous les vecteurs ont la même longueur
  3. Chaque vecteur représente une colonne de données

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
class(df)
#> [1] "data.frame"
# Un data frame EST une liste
class(df)
#> [1] "data.frame"
is.list(df)  # TRUE !
#> [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 :

  • Création : list() peut contenir n’importe quel type d’objet
  • Indexation cruciale :
    • [[i]] ou $nom : extrait l’élément (son contenu)
    • [i] : retourne une sous-liste
  • Accès par nom : $ est le plus pratique (équivalent à [[""]])
  • Listes imbriquées : on peut créer des structures hiérarchiques
  • Fonctions utiles :
    • str() pour visualiser la structure
    • lapply() retourne une liste, sapply() simplifie en vecteur
    • unlist() pour aplatir en vecteur
  • Data frames : sont des listes de vecteurs de même longueur

Comprendre la différence entre [[]] et [] est essentiel, car vous la retrouverez constamment dans le travail avec les listes et les data frames.

Data frames : le cœur de l’analyse de données

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
# C'est bien une liste
is.list(df)
#> [1] TRUE
# Mais c'est aussi un data frame
class(df)
#> [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

Avec 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

Conversion depuis d’autres structures

# 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

Options importantes lors de la création

# 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
# Résumé statistique
summary(iris_petit)
#>   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
# Premières lignes
head(iris_petit, n = 3)
#>   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
# Dernières lignes
tail(iris_petit, n = 3)
#>    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
# Dimensions
dim(iris_petit)  # lignes × colonnes
#> [1] 10  5
nrow(iris_petit)  # Nombre de lignes
#> [1] 10
ncol(iris_petit)  # Nombre de colonnes
#> [1] 5
# Noms des colonnes
names(iris_petit)
#> [1] "Sepal.Length" "Sepal.Width"  "Petal.Length" "Petal.Width"  "Species"
colnames(iris_petit)  # Équivalent
#> [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.

Comme une matrice : [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
# Une ligne entière
etudiants[2, ]
#>   nom age note
#> 2 Bob  22   12
# Une colonne entière (retourne un vecteur)
etudiants[, 2]
#> [1] 20 22 21
# Plusieurs lignes
etudiants[c(1, 3), ]
#>       nom age note
#> 1   Alice  20 15.5
#> 3 Charlie  21 18.0
# Plusieurs colonnes (par position)
etudiants[, c(1, 3)]
#>       nom note
#> 1   Alice 15.5
#> 2     Bob 12.0
#> 3 Charlie 18.0
# Par noms de colonnes
etudiants[, c("nom", "note")]
#>       nom note
#> 1   Alice 15.5
#> 2     Bob 12.0
#> 3 Charlie 18.0

Comme une liste : $, [[]], []

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"
etudiants$age
#> [1] 20 22 21
# Avec [[]] (retourne un vecteur)
etudiants[["note"]]
#> [1] 15.5 12.0 18.0
etudiants[[2]]  # Deuxième colonne
#> [1] 20 22 21
# Avec [] (retourne un data frame)
etudiants["nom"]  # Data frame avec une colonne
#>       nom
#> 1   Alice
#> 2     Bob
#> 3 Charlie
etudiants[c("nom", "note")]  # Data frame avec deux colonnes
#>       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)

Filtrage par condition

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
# Étudiants âgés de plus de 21 ans
etudiants[etudiants$age > 21, ]
#>     nom age note
#> 2   Bob  22 12.0
#> 4 Diana  23 14.5
# Conditions multiples avec & (et) et | (ou)
etudiants[etudiants$age > 20 & etudiants$note >= 15, ]
#>       nom age note
#> 3 Charlie  21   18

La fonction 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
subset(etudiants, age > 20 & note >= 15)
#>       nom age note
#> 3 Charlie  21   18
# Sélectionner aussi des colonnes spécifiques
subset(etudiants, note >= 15, select = c(nom, note))
#>       nom note
#> 1   Alice 15.5
#> 3 Charlie 18.0
# Exclure des colonnes
subset(etudiants, age > 20, select = -age)
#>       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).

Ajouter une nouvelle colonne

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
# Ou avec [[]]
etudiants[["classe"]] <- c("A", "B", "A")
print(etudiants)
#>       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

Modifier une colonne existante

etudiants <- data.frame(
  nom = c("Alice", "Bob", "Charlie"),
  age = c(20, 22, 21)
)

# Modifier toute la colonne
etudiants$age <- etudiants$age + 1
print(etudiants)
#>       nom age
#> 1   Alice  21
#> 2     Bob  23
#> 3 Charlie  22
# Modification conditionnelle
etudiants$age[etudiants$nom == "Bob"] <- 23
print(etudiants)
#>       nom age
#> 1   Alice  21
#> 2     Bob  23
#> 3 Charlie  22

Supprimer une colonne

etudiants <- data.frame(
  nom = c("Alice", "Bob"),
  age = c(20, 22),
  note = c(15, 12)
)

# Assigner NULL
etudiants$note <- NULL
print(etudiants)
#>     nom age
#> 1 Alice  20
#> 2   Bob  22
# Ou sélectionner les colonnes à garder
etudiants <- etudiants[, c("nom", "age")]

Ajouter des lignes avec 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.

Supprimer des lignes

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
# Supprimer par condition
etudiants <- etudiants[etudiants$age != 21, ]
print(etudiants)
#>     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
# Trier par note (décroissant)
etudiants[order(-etudiants$note), ]
#>       nom age note
#> 1 Charlie  21 18.0
#> 2   Alice  20 15.5
#> 3     Bob  22 12.0
# Ou
etudiants[order(etudiants$note, decreasing = TRUE), ]
#>       nom age note
#> 1 Charlie  21 18.0
#> 2   Alice  20 15.5
#> 3     Bob  22 12.0
# Trier par plusieurs colonnes (âge puis note)
etudiants[order(etudiants$age, etudiants$note), ]
#>       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.

Fusion simple (inner join)

# 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.

Types de jointures

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

Fusion sur plusieurs colonnes

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

Fusion avec des noms de colonnes différents

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.

Syntaxe de base avec formule

# 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
# Montant total par région
aggregate(montant ~ region, data = ventes, FUN = sum)
#>   region montant
#> 1   Nord     360
#> 2    Sud     460

La syntaxe variable ~ groupe se lit : “variable en fonction de groupe”.

Grouper par plusieurs variables

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

Agréger plusieurs variables simultanément

ventes <- data.frame(
  produit = c("A", "B", "A", "B"),
  montant = c(100, 150, 120, 180),
  quantite = c(5, 8, 6, 9)
)

# Moyenne du montant ET de la quantité par produit
aggregate(cbind(montant, quantite) ~ produit, data = ventes, FUN = mean)
#>   produit montant quantite
#> 1       A     110      5.5
#> 2       B     165      8.5

Fonctions personnalisées

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.

df <- data.frame(
  a = 1:3,
  b = 4:6,
  c = 7:9
)

# Extraire une colonne
resultat <- df[, "a"]
class(resultat)  # "integer" - c'est un VECTEUR !
#> [1] "integer"
# Extraire deux colonnes
resultat2 <- df[, c("a", "b")]
class(resultat2)  # "data.frame" - c'est un data frame
#> [1] "data.frame"

Pourquoi c’est problématique

Ce comportement peut casser du code qui fonctionne avec plusieurs colonnes mais échoue avec une seule :

df <- data.frame(
  a = 1:3,
  b = 4:6,
  c = 7:9
)

# Fonction qui compte les lignes
compter_lignes <- function(data, colonnes) {
  sous_df <- data[, colonnes]
  nrow(sous_df)  # Fonctionne si colonnes contient plusieurs noms
}

# Fonctionne avec deux colonnes
compter_lignes(df, c("a", "b"))  # 3
#> [1] 3
# ERREUR avec une seule colonne !
# compter_lignes(df, "a")  # Erreur : dim(X) must have a positive length

Solution : drop = FALSE

Pour 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"
print(resultat)
#>   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
compter_lignes_robuste(df, c("a", "b"))  # 3 - fonctionne aussi !
#> [1] 3

Alternatives qui ne “drop” pas

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
df$a  # Vecteur (mais on sait qu'on extrait un vecteur)
#> [1] 1 2 3
df[["a"]]  # Vecteur (mais on sait qu'on extrait un vecteur)
#> [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 :

  • Les noms de lignes peuvent être perdus lors de certaines opérations
  • Ils ne sont pas vraiment une variable du data frame
  • Ils compliquent les manipulations

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 :

  • Nature : liste de vecteurs de même longueur (colonnes de types différents)
  • Création : data.frame()
  • Inspection : str(), summary(), head(), tail(), dim(), names()
  • Double indexation :
    • Comme matrice : [ligne, colonne]
    • Comme liste : $colonne, [["colonne"]], ["colonne"]
  • Filtrage : df[condition, ] ou subset()
  • Ajout de colonnes : df$nouvelle <- valeurs
  • Tri : df[order(df$colonne), ]
  • Fusion : merge() avec all.x, all.y, all pour différents types de jointures
  • Opérations groupées : aggregate(variable ~ groupe, data, FUN)
  • Piège du drop : utiliser drop = FALSE pour garantir un data frame en sortie
  • Bonne pratique : pas de row.names, créer une colonne id à la place

Les 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.

Import et export de données

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érique

# Syntaxe générale
donnees <- read.table(
  file = "chemin/vers/fichier.txt",
  header = TRUE,           # La première ligne contient les noms de colonnes
  sep = "\t",             # Séparateur de colonnes (tabulation ici)
  dec = ".",              # Séparateur décimal
  stringsAsFactors = FALSE  # Ne pas convertir les chaînes en facteurs
)

Arguments 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-saxon

Pour les fichiers CSV au format international (séparateur ,, décimale .) :

# Format anglo-saxon
donnees <- read.csv("donnees.csv")

# Équivalent à :
donnees <- read.table(
  "donnees.csv",
  header = TRUE,
  sep = ",",
  dec = "."
)

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éen

Pour les fichiers CSV au format européen/français (séparateur ;, décimale ,) :

# Format européen
donnees <- read.csv2("donnees.csv")

# Équivalent à :
donnees <- read.table(
  "donnees.csv",
  header = TRUE,
  sep = ";",
  dec = ","
)

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és

Pour les fichiers avec tabulations comme séparateur :

# Format anglo-saxon (décimale .)
donnees <- read.delim("donnees.txt")

# Format européen (décimale ,)
donnees <- read.delim2("donnees.txt")

Contrôler les types de colonnes : colClasses

Par 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

Gérer les valeurs manquantes : na.strings

Par défaut, R reconnaît NA comme valeur manquante. Vous pouvez spécifier d’autres valeurs :

# Considérer aussi "999" et "" comme NA
donnees <- read.csv(
  "donnees.csv",
  na.strings = c("NA", "999", "", " ")
)

Ignorer des lignes : skip et nrows

# Ignorer les 5 premières lignes
donnees <- read.csv("donnees.csv", skip = 5)

# Lire seulement les 100 premières lignes
donnees <- read.csv("donnees.csv", nrows = 100)

Gérer l’encodage : encoding et fileEncoding

Les problèmes d’encodage causent souvent des caractères bizarres (é devient é, etc.).

# Pour fichiers en UTF-8
donnees <- read.csv("donnees.csv", encoding = "UTF-8")

# Pour fichiers en Latin-1 (Windows français)
donnees <- read.csv("donnees.csv", encoding = "latin1")

# Alternative (plus ancien)
donnees <- read.csv("donnees.csv", fileEncoding = "UTF-8")

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 !

Piège 1 : Confusion séparateur décimal

# 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écimale

Piège 2 : Séparateurs dans les données

Si 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 = "\"".

Piège 3 : Noms de colonnes avec espaces ou caractères spéciaux

# 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.

Piège 4 : Conversion automatique en facteurs (R < 4.0)

En R versions < 4.0, stringsAsFactors = TRUE était le défaut, causant beaucoup de problèmes :

# R < 4.0 : les colonnes texte deviennent des facteurs !
donnees <- read.csv("donnees.csv")  # Par défaut : stringsAsFactors = TRUE

# Solution : toujours spécifier
donnees <- read.csv("donnees.csv", stringsAsFactors = FALSE)

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.

Nettoyer les noms de colonnes

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_propres

Alternative : certains packages offrent des fonctions de nettoyage automatique (par exemple janitor::clean_names()), mais nous restons ici en base R.

Vérifier et corriger les types de colonnes

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)

Spécifier les types dès l’import (méthode recommandée)

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")

Gérer les valeurs aberrantes et manquantes

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)

Exemple complet de nettoyage

# 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.

Connaître et changer le répertoire de travail

# Voir le répertoire actuel
getwd()

# Changer le répertoire
setwd("/chemin/vers/mon/dossier")

# Sous Windows (attention aux backslashes)
setwd("C:/Users/nom/Documents/projet")  # Avec /
# ou
setwd("C:\\Users\\nom\\Documents\\projet")  # Avec \\

Chemins relatifs vs absolus

# Chemin absolu (depuis la racine du disque)
donnees <- read.csv("/Users/nom/Documents/projet/data/fichier.csv")

# Chemin relatif (depuis le répertoire de travail)
setwd("/Users/nom/Documents/projet")
donnees <- read.csv("data/fichier.csv")  # Plus simple !

Bonne pratique : les projets RStudio

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 :

donnees <- read.csv("data/donnees_brutes.csv")

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()

Utilisation simple

# Format européen
write.csv2(donnees, file = "resultats.csv", row.names = FALSE)

# Format anglo-saxon
write.csv(donnees, file = "resultats.csv", row.names = FALSE)

# Personnalisé
write.table(
  donnees,
  file = "resultats.txt",
  sep = "\t",
  dec = ".",
  row.names = FALSE
)

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.

Installation et chargement

# Installation (une seule fois)
install.packages("readxl")

# Chargement (à chaque session)
library(readxl)

Lecture simple

# 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)

Options utiles

donnees <- read_excel(
  "donnees.xlsx",
  sheet = "Feuille1",
  range = "A1:D100",      # Plage de cellules
  skip = 5,               # Ignorer les premières lignes
  col_names = TRUE,       # La première ligne contient les noms
  na = c("", "NA", "-")   # Valeurs considérées comme NA
)

Différence avec base R

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 :

donnees <- as.data.frame(read_excel("donnees.xlsx"))

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.

Fonctions en R

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 :

  1. Réutilisabilité : écrivez une fois, utilisez partout
  2. Lisibilité : un nom explicite remplace des lignes de code complexes
  3. Maintenabilité : corriger à un seul endroit plutôt que partout
  4. Modularité : décomposer un problème complexe en parties simples
  5. Éviter les erreurs : moins de copier-coller = moins de risques

Règle d’or : si vous copiez-collez du code plus de deux fois, créez une fonction !

Syntaxe de base

# Structure générale
nom_fonction <- function(arguments) {
  # Corps de la fonction
  # Calculs et opérations
  resultat
}

La dernière expression évaluée est automatiquement retournée.

Premier exemple

# Fonction qui double un nombre
doubler <- function(x) {
  x * 2
}

# Utilisation
doubler(5)
#> [1] 10
doubler(c(1, 2, 3))
#> [1] 2 4 6

Fonction sans arguments

# Fonction qui affiche un message
saluer <- function() {
  print("Bonjour !")
}

saluer()
#> [1] "Bonjour !"

Fonction avec plusieurs instructions

# 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

Retour implicite

Par défaut, la dernière expression est retournée :

calculer_aire_carre <- function(cote) {
  cote^2  # Cette valeur est automatiquement retournée
}

calculer_aire_carre(5)
#> [1] 25

Retour explicite avec 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"
verifier_positif(0)
#> [1] "Zéro"
verifier_positif(10)
#> [1] "Nombre positif"

Retourner plusieurs valeurs

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
# Accéder aux éléments
resultats$moyenne
#> [1] 13.71429
resultats$ecart_type
#> [1] 2.870208

Arguments positionnels

Les arguments sont passés dans l’ordre de définition :

diviser <- function(a, b) {
  a / b
}

diviser(10, 2)  # a = 10, b = 2
#> [1] 5
diviser(2, 10)  # a = 2, b = 10 (différent !)
#> [1] 0.2

Arguments nommés

On peut spécifier les arguments par leur nom, dans n’importe quel ordre :

diviser <- function(a, b) {
  a / b
}

diviser(a = 10, b = 2)
#> [1] 5
diviser(b = 2, a = 10)  # Même résultat
#> [1] 5

Bonne pratique : utiliser les noms pour les arguments après les deux premiers, surtout s’ils sont nombreux.

Arguments avec valeurs par défaut

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"
saluer("Alice", formule = "Bonsoir")  # Remplace la valeur par défaut
#> [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
resume_numerique(donnees, arrondir = 4)  # Arrondi à 4 décimales
#>    moyenne    mediane ecart_type 
#>    12.7893    12.7890     2.6665

Arguments obligatoires vs optionnels

# a et b obligatoires, operation optionnelle
calculer <- function(a, b, operation = "somme") {
  if (operation == "somme") {
    return(a + b)
  } else if (operation == "produit") {
    return(a * b)
  } else {
    stop("Opération non reconnue")
  }
}

calculer(5, 3)  # Utilise "somme" par défaut
#> [1] 8
calculer(5, 3, operation = "produit")
#> [1] 15

Variables locales

Les variables créées dans une fonction sont locales : elles n’existent que dans la fonction.

ma_fonction <- function() {
  x_local <- 10
  print(x_local)
}

ma_fonction()  # Affiche 10
#> [1] 10
# x_local n'existe pas en dehors de la fonction
# print(x_local)  # Erreur : object 'x_local' not found

Variables globales

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
print(x_global)  # Affiche toujours 100 (inchangé)
#> [1] 100

Bonne pratique : évitez de modifier les variables globales dans les fonctions. Passez plutôt des arguments et retournez des valeurs.

Exemple : isolation complète

# 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 :

# Calculer la somme d'un nombre quelconque de valeurs
somme_totale <- function(...) {
  valeurs <- c(...)
  sum(valeurs)
}

somme_totale(1, 2, 3)
#> [1] 6
somme_totale(10, 20, 30, 40, 50)
#> [1] 150

Transmettre des arguments à une autre fonction

... 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
moyenne_avec_message(donnees, na.rm = TRUE)  # na.rm transmis à mean()
#> La moyenne est : 20
#> [1] 20

Combiner arguments nommés et ...

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
resume_avec_options(donnees, titre = "Statistiques")
#> === 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
calculer_racine(c(4, 9, 16))
#> [1] 2 3 4
# calculer_racine("texte")  # Erreur
calculer_racine(c(4, -9, 16))  # Warning
#> [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

Exemple : nettoyer un data frame

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

Exemple : transformer des colonnes

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

Exemple : filtrage personnalisé

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

  1. Un nom explicite : calculer_moyenne() plutôt que f1()
  2. Une tâche par fonction : une fonction doit faire une chose et la faire bien
  3. Arguments clairs : noms explicites et ordre logique
  4. Validation : vérifier les arguments en début de fonction
  5. Valeurs par défaut : pour les arguments optionnels
  6. Commentaires : expliquer le “pourquoi”, pas le “quoi”
  7. Tests : tester avec des exemples simples avant utilisation
  8. Pas d’effets de bord : éviter de modifier les variables globales

Les fonctions permettent de créer du code réutilisable et maintenable :

  • Syntaxe : nom <- function(args) { corps }
  • Retour : dernière expression ou return()
  • Arguments : positionnels, nommés, avec valeurs par défaut
  • ... : pour nombre variable d’arguments
  • Portée : variables locales vs globales (privilégier l’isolation)
  • Validation : stop(), warning(), tests de type
  • Applications : data frames, avec lapply()/sapply()/apply()
  • Commentaires : documenter les arguments, le retour, et les étapes importantes
  • Bonne pratique : DRY (Don’t Repeat Yourself) - si vous copiez-collez, créez une fonction !

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.

Classes et méthodes S3

Vous avez peut-être remarqué quelque chose d’étrange : la fonction print() affiche différemment selon le type d’objet.

# Affichage d'un vecteur
print(c(1, 2, 3))
#> [1] 1 2 3
# Affichage d'un data frame
print(data.frame(x = 1:3, y = 4:6))
#>   x y
#> 1 1 4
#> 2 2 5
#> 3 3 6
# Affichage d'un modèle linéaire
modele <- lm(mpg ~ wt, data = mtcars)
print(modele)
#> 
#> 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.

Qu’est-ce qu’une classe ?

Une classe est une étiquette attachée à un objet qui indique son type. En R, c’est simplement un attribut class.

# Un vecteur simple
x <- c(1, 2, 3)
class(x)
#> [1] "numeric"
# Un data frame
df <- data.frame(a = 1:3, b = 4:6)
class(df)
#> [1] "data.frame"
# Un facteur
f <- factor(c("A", "B", "A"))
class(f)
#> [1] "factor"

Qu’est-ce qu’une méthode générique ?

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() : affichage
  • summary() : résumé
  • plot() : graphiques
  • str() : structure
  • length(), mean(), etc.

Quand vous appelez print(objet), R :

  1. Regarde la classe de objet avec class(objet)
  2. Cherche une méthode spécifique : print.nom_classe()
  3. Si elle existe, l’utilise
  4. Sinon, utilise la méthode par défaut : print.default()

C’est ce qu’on appelle le dispatch de méthodes (ou répartition).

Visualiser le dispatch

# Quelle méthode print() sera utilisée ?
x <- factor(c("A", "B", "A"))
class(x)
#> [1] "factor"
# R va chercher print.factor()
print(x)
#> [1] A B A
#> Levels: A B
# Voir toutes les méthodes print disponibles
methods(print)  # Liste très longue !
#>   [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

Exemple avec 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"
print(personne)  # Affichage par défaut (pas très joli)
#> $nom
#> [1] "Alice"
#> 
#> $age
#> [1] 30
#> 
#> $ville
#> [1] "Paris"
#> 
#> attr(,"class")
#> [1] "Personne"

Créer une méthode print personnalisée

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 
#> ============================

Créer d’autres méthodes

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
summary(stats)  # Utilise summary.Statistiques()
#> Résumé : n = 8 , moyenne = 13.62

Fonctions utiles

# Vérifier la classe
x <- factor(c("A", "B"))
class(x)
#> [1] "factor"
# Tester si un objet est d'une classe spécifique
is.factor(x)
#> [1] TRUE
is.data.frame(x)
#> [1] FALSE
# Pour les classes personnalisées
personne <- list(nom = "Alice")
class(personne) <- "Personne"
inherits(personne, "Personne")  # TRUE
#> [1] TRUE

Voir les méthodes disponibles

# Toutes les méthodes pour une fonction générique
methods(print)  # Très long !
#>   [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
methods(summary)
#>  [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
# Méthodes pour une classe spécifique
methods(class = "lm")  # Méthodes pour les modèles linéaires
#>  [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
methods(class = "data.frame")
#>   [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éries

Chaque type d’objet a un affichage graphique adapté, sans que vous ayez à le spécifier !

# Voir les méthodes plot disponibles
methods(plot)
#>  [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 :

  • Classe : étiquette (attribut class) attachée à un objet
  • Méthode générique : fonction qui adapte son comportement selon la classe (ex: print(), summary(), plot())
  • Dispatch : R choisit automatiquement la bonne méthode fonction.classe()
  • Fonctions utiles : 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 !