8  Traitement de données sous R

On fera surtout du dplyr dans la mesure où la syntaxe est plus simple à appréhender que le base R.

Prenons un example avec le jeu de données palmerpenguins.

data(package = 'palmerpenguins')
head(penguins)
  species    island bill_len bill_dep flipper_len body_mass    sex year
1  Adelie Torgersen     39.1     18.7         181      3750   male 2007
2  Adelie Torgersen     39.5     17.4         186      3800 female 2007
3  Adelie Torgersen     40.3     18.0         195      3250 female 2007
4  Adelie Torgersen       NA       NA          NA        NA   <NA> 2007
5  Adelie Torgersen     36.7     19.3         193      3450 female 2007
6  Adelie Torgersen     39.3     20.6         190      3650   male 2007

Il contient des informations sur trois espèces de manchots observées dans l’archipel de Palmer en Antarctique : Adelie, Chinstrap et Gentoo.

8.1 Manipulation de Data Frames

Assurez-vous de bien comprendre la structure de données d’un Data Frame avant de lire cette section (cf. Section 6.3).

8.1.1 Lire et écrire des Data Frames

Pour lire des DF à partir de fichiers csv1, on utilise la fonction de R base read.csv() dont l’équivalent en tydiverse est la fonction read_csv() du package readr. Je vous conseil d’utiliser la fonction readr::read_csv() car elle plus optimiser que la première.

1 On peut obtenir des DF à partir de fichiers autre que csv. Je me concentre sur ce format car il s’agit du plus courant. Si vous voulez lire des données à partir d’un fichier xls/xlsw vous pouvez utiliser le package read_excel. Je ne vous le conseil pas car les fichiers excel sont souvent très mal mis en forme en plus de contenir des feuilles. Il vaut mieux en extraire des csv propres.

NoteLe format csv

Le format csv est un des formats les plus utilisé pour stocker des données tabulaires en data science. csv signifie Comma Separated Values (comma signifie virgule). Donc, dans la majorité des cas, un fichier csv sera de la forme :

var_1,var_2
12,0.9
2,0.25
4,0.78

En Europe, on utilise souvent la virgule pour représenter le séparateur décimal. Le séparateur de valeurs du fichier csv devient alors le ;. Le fichier devient alors :

var_1;var_2
12;0,9
2;0,25
4;0,78

Vous trouvez aussi des fichiers csv séparé par des tabulations (on parle de tsv. C’est surtout le cas pour les fichiers contenants du texte long car la virgule et le point virgule peuvent se rencontrer dans le texte.

WarningAttention

Un fichier csv ne doit jamais avoir des informations avant la première ligne qui indique le nom des colonnes. Par exemple, le fichier suivant ne sera pas lu correctement.

Fichier écrit le 2025/05/02.
vars_1 : longueur (cm)
vars_2 : poid (g)
var_1;var_2
12;0,9
2;0,25
4;0,78

Cela veut dire qu’un fichier csv ne doit contenir que les données et jamais les métadonnées ou autres informations. Bien évidemment, si vous écrivez des données et que vous voulez les partager il faut toujours écrire les métadonnées, mais vous les écrirez dans un fichier séparé.

Avant de charger un fichier csv, il faut connaître le format employé : quel séparateur de valeur et quel séparateur de décimal est employé. Pour cela, il suffit d’ouvrir d’inspecter le fichier. Vous pouvez tout simplement essayer de l’ouvrir sous R et regarder le résultat. Sinon, vous pouvez ouvrir le fichier dans un logiciel externe (par exemple avec le notepad comme les csv sont des fichiers textuelles brutes).

Type de fichier Séparateur de colonnes Séparateur décimal Fonction readr
CSV standard , . read_csv()
CSV2 (Europe) ; , read_csv2()
TSV \t (tabulation) . read_tsv()
ALL argument delim argument locale (decimal_mark) read_delim()

Par exemple :

path <- "data/src/iris_brest.csv"
df <- readr::read_csv(path)
Rows: 90 Columns: 115
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr   (4): Libellé commune ou ARM, TRIRIS, Libellé de l'IRIS, Type d'IRIS
dbl (111): IRIS, Région, Département, Unité urbaine, Commune ou ARM, Grand q...

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.

La fonction readr::read_csv() renvoie directement des informations sur la classe des colonnes. Cela vous permet de voir rapidement si elles sont bien interprétées.

Pour écrire un fichier csv, vous pouvez utisez la fonction readr::write_csv(). Elle prend un data.frame en entrée et le chemin vers le fichier de destination. Je vous conseil d’utiliser la fonction avec les arguments par défaut, donc avec un séparateur virgule et un séparateur de décimale point. De cette façon, vous aurez toujours le format de csv et vous vous y retrouverez plus facilement.

output_path <- "data/res/output.csv"
readr::write_csv(df_res, output_path)
NoteLire et écrire des données spatiales vectorielles

Quand on travail avec des données spatiales vectorielles, ce qui est le cas dans ce cours, on utilise une variante spatiale du Data frame qui est un objet sf issu du package sf.

Avec sf, on peut lire tous les types de fichiers spatiaux vectoriels (par-ex, shapefile, geopackage, etc.). On utilise la fonction read_sf().

# Lecture d'un geopackage
path <- "data/src/iris_29.gpkg"
iris <- sf::read_sf(path)
class(iris)
[1] "sf"         "tbl_df"     "tbl"        "data.frame"

Si vous voulez lire un shapefile, vous indiquerez le chemin du fichier .shp, sf fera le lien avec les autres fichiers.

On écrit des données spatiales à partir d’un objet sf avec la fonction write_sf(). La fonction “devine” le format attendu à partir de l’extension que vous donnez au fichier :

# Écriture d'un fichier geopackage
path <- "data/res/output.gpkg"
sf::write_sf(sf_res, path)

8.1.2 Un framework pour la manipulation de données : dplyr

dplyr est un framework du tidyverse en R conçu pour faciliter la manipulation de données de manière claire, cohérente et efficace. Il repose sur une syntaxe intuitive et un petit ensemble de verbes cohérents qui correspondent aux opérations les plus courantes en analyse de données, comme :

  • select() pour choisir des colonnes,
  • filter() pour filtrer les lignes,
  • mutate() pour créer ou modifier des variables,
  • summarise() pour résumer les données,
  • arrange() pour trier les observations,
  • et group_by() pour travailler par groupes.

Ces verbes peuvent être reliés les uns aux autres par un pipe (|> ou %>%). Ce dernier prend le résultat de la fonction précédente comme tableau d’enttrée. Par exemple :

penguins |>
    select(island, species, bill_len, year) |>
    filter(year > 2007) |>
    group_by(island, species) |>
    summarise(mean_bill_len = mean(bill_len, na.rm = TRUE))
`summarise()` has grouped output by 'island'. You can override using the
`.groups` argument.
# A tibble: 5 × 3
# Groups:   island [3]
  island    species   mean_bill_len
  <fct>     <fct>             <dbl>
1 Biscoe    Adelie             39.2
2 Biscoe    Gentoo             47.7
3 Dream     Adelie             38.2
4 Dream     Chinstrap          48.9
5 Torgersen Adelie             39.0

8.1.3 Sélectionner, filtrer, modifier et trier

8.1.3.1 Sélectionner des données

Pour sélectionner une variable spécifique, on utilise la fonction homonime select().

selected <- select(penguins, species, island, body_mass)
head(selected)
  species    island body_mass
1  Adelie Torgersen      3750
2  Adelie Torgersen      3800
3  Adelie Torgersen      3250
4  Adelie Torgersen        NA
5  Adelie Torgersen      3450
6  Adelie Torgersen      3650

À l’inverse, pour supprimer des variables, on place un - devant la variable :

selected <- select(penguins, -species, -island, -body_mass)
head(selected)
  bill_len bill_dep flipper_len    sex year
1     39.1     18.7         181   male 2007
2     39.5     17.4         186 female 2007
3     40.3     18.0         195 female 2007
4       NA       NA          NA   <NA> 2007
5     36.7     19.3         193 female 2007
6     39.3     20.6         190   male 2007

On peut employer une expression de sélection pour choisir des variables par rapport à une condition. Par exemple, si on veut sélectionner toutes les variables dont le nom se termine par une chaine de caractère précise, on peut utiliser la fonction ends_with() dans la fonction select() :

selected <- select(penguins, ends_with("len"))
head(selected)
  bill_len flipper_len
1     39.1         181
2     39.5         186
3     40.3         195
4       NA          NA
5     36.7         193
6     39.3         190

Si on a une idée précise des variables que l’on veut sélectionner, on peut utiliser un vecteur de contenant les noms des variables et utiliser la fonction all_of() :

to_select <- c("island", "species", "body_mass")
selected <- select(penguins, all_of(to_select))
head(selected)
     island species body_mass
1 Torgersen  Adelie      3750
2 Torgersen  Adelie      3800
3 Torgersen  Adelie      3250
4 Torgersen  Adelie        NA
5 Torgersen  Adelie      3450
6 Torgersen  Adelie      3650

Bien sûr, on peut les combiner :

selected <- select(penguins, starts_with("bill"), where(is.factor))
head(selected)
  bill_len bill_dep species    island    sex
1     39.1     18.7  Adelie Torgersen   male
2     39.5     17.4  Adelie Torgersen female
3     40.3     18.0  Adelie Torgersen female
4       NA       NA  Adelie Torgersen   <NA>
5     36.7     19.3  Adelie Torgersen female
6     39.3     20.6  Adelie Torgersen   male

Voilà un tableau récapitulatif des expressions les plus communes que l’on peut utiliser pour sélectionner des variables.

Fonction Description Exemple
starts_with("x") Sélectionne les variables dont le nom commence par "x" select(starts_with("Sepal"))
ends_with("x") Sélectionne les variables dont le nom se termine par "x" select(ends_with("Width"))
contains("x") Sélectionne les variables dont le nom contient "x" select(contains("Petal"))
all_of(vars) Sélectionne toutes les colonnes listées dans un vecteur vars (et renvoie une erreur si une manque) select(all_of(c("Species", "Sepal.Length")))
everything() Sélectionne toutes les colonnes (utile pour repositionner) select(Species, everything())
where(predicate) Sélectionne selon un type ou une condition logique sur les colonnes select(where(is.numeric)), select(where(~mean(.) > 0))

8.1.3.2 filtrer des données

Pour filtrer les données avec dplyr on utilise la fonction homonyme filter(). Cette dernière filtre les lignes par vérification de conditions.

Par exemple, si on veut récupérer uniquement les donnnées pour l’espèce Adelie :

filtered <- filter(penguins, species == "Adelie")
head(filtered)
  species    island bill_len bill_dep flipper_len body_mass    sex year
1  Adelie Torgersen     39.1     18.7         181      3750   male 2007
2  Adelie Torgersen     39.5     17.4         186      3800 female 2007
3  Adelie Torgersen     40.3     18.0         195      3250 female 2007
4  Adelie Torgersen       NA       NA          NA        NA   <NA> 2007
5  Adelie Torgersen     36.7     19.3         193      3450 female 2007
6  Adelie Torgersen     39.3     20.6         190      3650   male 2007

Ou si on souhaite récupérer les observations pour les années postérieur à 2007.

filtered <- filter(penguins, year > 2007)
head(filtered)
  species island bill_len bill_dep flipper_len body_mass    sex year
1  Adelie Biscoe     39.6     17.7         186      3500 female 2008
2  Adelie Biscoe     40.1     18.9         188      4300   male 2008
3  Adelie Biscoe     35.0     17.9         190      3450 female 2008
4  Adelie Biscoe     42.0     19.5         200      4050   male 2008
5  Adelie Biscoe     34.5     18.1         187      2900 female 2008
6  Adelie Biscoe     41.4     18.6         191      3700   male 2008

On peut combiner les filtres :

filtered <- filter(penguins, year > 2007, species == "Adelie")
head(filtered)
  species island bill_len bill_dep flipper_len body_mass    sex year
1  Adelie Biscoe     39.6     17.7         186      3500 female 2008
2  Adelie Biscoe     40.1     18.9         188      4300   male 2008
3  Adelie Biscoe     35.0     17.9         190      3450 female 2008
4  Adelie Biscoe     42.0     19.5         200      4050   male 2008
5  Adelie Biscoe     34.5     18.1         187      2900 female 2008
6  Adelie Biscoe     41.4     18.6         191      3700   male 2008

Souvent, on souhaite sélectionner des données dans une gamme de valeurs, il suffit de faire :

filtered <- filter(penguins, bill_len > 35, bill_len < 40)
head(filtered)
  species    island bill_len bill_dep flipper_len body_mass    sex year
1  Adelie Torgersen     39.1     18.7         181      3750   male 2007
2  Adelie Torgersen     39.5     17.4         186      3800 female 2007
3  Adelie Torgersen     36.7     19.3         193      3450 female 2007
4  Adelie Torgersen     39.3     20.6         190      3650   male 2007
5  Adelie Torgersen     38.9     17.8         181      3625 female 2007
6  Adelie Torgersen     39.2     19.6         195      4675   male 2007

Plutôt que d’utiliser des virgules pour séparer les filtres on peut utiliser une syntaxe booléenne (je ne vous le conseil pour débuter) :

filtered <- filter(
    penguins,
    (
        (species == "Gentoo" | species == "Adelie") &
        body_mass > mean(body_mass, na.rm = TRUE)
    )
)
head(filtered)
  species    island bill_len bill_dep flipper_len body_mass  sex year
1  Adelie Torgersen     39.2     19.6         195      4675 male 2007
2  Adelie Torgersen     42.0     20.2         190      4250 <NA> 2007
3  Adelie Torgersen     34.6     21.1         198      4400 male 2007
4  Adelie Torgersen     42.5     20.7         197      4500 male 2007
5  Adelie     Dream     39.8     19.1         184      4650 male 2007
6  Adelie     Dream     44.1     19.7         196      4400 male 2007

Pour filter les modalités de facteurs ou de chaines de caractères, on peut utiliser l’opérateur %in%.

filtered <- filter(penguins, species %in% c("Gentoo", "Adelie"))
head(filtered)
  species    island bill_len bill_dep flipper_len body_mass    sex year
1  Adelie Torgersen     39.1     18.7         181      3750   male 2007
2  Adelie Torgersen     39.5     17.4         186      3800 female 2007
3  Adelie Torgersen     40.3     18.0         195      3250 female 2007
4  Adelie Torgersen       NA       NA          NA        NA   <NA> 2007
5  Adelie Torgersen     36.7     19.3         193      3450 female 2007
6  Adelie Torgersen     39.3     20.6         190      3650   male 2007

Pour récupérer l’inverse d’une condition, on utilise le point d’exclamation !.

filtered <- filter(penguins, !species %in% c("Gentoo", "Adelie"))
head(filtered)
    species island bill_len bill_dep flipper_len body_mass    sex year
1 Chinstrap  Dream     46.5     17.9         192      3500 female 2007
2 Chinstrap  Dream     50.0     19.5         196      3900   male 2007
3 Chinstrap  Dream     51.3     19.2         193      3650   male 2007
4 Chinstrap  Dream     45.4     18.7         188      3525 female 2007
5 Chinstrap  Dream     52.7     19.8         197      3725   male 2007
6 Chinstrap  Dream     45.2     17.8         198      3950 female 2007

Tableau récapitulatif

Catégorie Fonction / opérateur Description Exemple
Comparaison numérique ==, != Égal / différent filter(Species == "setosa")
>, <, >=, <= Supérieur, inférieur, etc. filter(Sepal.Length > 6)
Appartenance %in% Appartient à un ensemble de valeurs filter(Species %in% c("setosa", "virginica"))
!x %in% ... N’appartient pas à… filter(!Species %in% "setosa")
Texte / chaînes str_detect(x, "mot") Contient un motif (nécessite stringr) filter(str_detect(Species, "osa"))
startsWith(x, "mot") Commence par… filter(startsWith(Species, "set"))
endsWith(x, "mot") Se termine par… filter(endsWith(Species, "ica"))
Valeurs manquantes is.na(x) Est manquant filter(is.na(Sepal.Length))
!is.na(x) N’est pas manquant filter(!is.na(Sepal.Length))
Intervalles between(x, a, b) Valeur comprise entre a et b (inclusif) filter(between(Sepal.Length, 5, 6))
Conditions complexes if_else() ou logique combinée Choix conditionnels filter(if_else(Sepal.Width > 3, TRUE, FALSE))

8.1.3.3 Modifier des données

Pour modifier des données ou en rajouter, on utilise la fonction mutate().

Par exemple, si on veut faire un arrondi de la variable bill_len.

mutated <- mutate(penguins, bill_len = round(bill_len))
head(mutated$bill_len)
[1] 39 40 40 NA 37 39

Plutôt que d’écrire par-dessus la variable bill_len, on peut vouloir créer une autre variable pour enregistrer la modification. On écrit simplement le nom de la nouvelle variable à gauche du = :

mutated <- mutate(penguins, bill_len_round = round(bill_len))
head(mutated$bill_len_round)
[1] 39 40 40 NA 37 39

On peut faire des opérations qui utilisent plusieurs variables :

mutated <- mutate(penguins, bill_len_norm_mass = bill_len / body_mass)
head(mutated$bill_len_norm_mass)
[1] 0.01042667 0.01039474 0.01240000         NA 0.01063768 0.01076712

On peut modifier de façon conditionnelle nos variables. Par exemple, si on veut arrondir uniquement la variable bill_len pour les espèces Adelie alors, on peut utiliser la fonction ifelse() :

mutated <- mutate(
    penguins,
    bill_len_round = ifelse(species == "Adelie", round(bill_len), bill_len)
)

Quand on a plus d’une condition à vérifier, on privilégiera la fonction case_when() :

mutated <- mutate(
    penguins,
    geo_code = case_when(
        island == "Biscoe" ~ "North",
        island == "Torgersen" ~ "Midlle",
        TRUE ~ "South" # quand les deux premières ne sont pas vérifiées
    )
)

Si on souhaite modifier un ensemble de variables répondant à un ou des caractères communs, on peut utiliser la fonction across(). Par exemple, si on veut arrondir toutes les variables numériques, on écrira :

mutated <- mutate(penguins, across(where(is.numeric), ~round(.x)))
head(mutated)
  species    island bill_len bill_dep flipper_len body_mass    sex year
1  Adelie Torgersen       39       19         181      3750   male 2007
2  Adelie Torgersen       40       17         186      3800 female 2007
3  Adelie Torgersen       40       18         195      3250 female 2007
4  Adelie Torgersen       NA       NA          NA        NA   <NA> 2007
5  Adelie Torgersen       37       19         193      3450 female 2007
6  Adelie Torgersen       39       21         190      3650   male 2007

Il arrive que l’on souhaite modifier des variables ou en créer de nouvelles par groupe. Par exemple, on peut vouloir normaliser bill_len par la moyenne de body_mass par espèce. Dans ce cadre, on utilisera la fonction group_by() qui permet d’effectuer des opérations par groupe.

mutated <- penguins |>
    group_by(species) |>
    mutate(norm_bill_len = bill_len / mean(body_mass))

8.1.3.4 Trier les données

Pour trier les données, on utilise la fonction arrange(). Par exemple, si on veut ordonner le tableau par bill_len :

arranged <- arrange(penguins, bill_len)
head(arranged)
  species    island bill_len bill_dep flipper_len body_mass    sex year
1  Adelie     Dream     32.1     15.5         188      3050 female 2009
2  Adelie     Dream     33.1     16.1         178      2900 female 2008
3  Adelie Torgersen     33.5     19.0         190      3600 female 2008
4  Adelie     Dream     34.0     17.1         185      3400 female 2008
5  Adelie Torgersen     34.1     18.1         193      3475   <NA> 2007
6  Adelie Torgersen     34.4     18.4         184      3325 female 2007

Par défaut l’ordre est croissant. Pour trier de façon décroissante, on utilise place la fonction desc() sur la variable :

arranged <- arrange(penguins, desc(bill_len))
head(arranged)
    species island bill_len bill_dep flipper_len body_mass    sex year
1    Gentoo Biscoe     59.6     17.0         230      6050   male 2007
2 Chinstrap  Dream     58.0     17.8         181      3700 female 2007
3    Gentoo Biscoe     55.9     17.0         228      5600   male 2009
4 Chinstrap  Dream     55.8     19.8         207      4000   male 2009
5    Gentoo Biscoe     55.1     16.0         230      5850   male 2009
6    Gentoo Biscoe     54.3     15.7         231      5650   male 2008

On peut bien sûr trier par plusieurs variables :

arranged <- arrange(penguins, desc(year), desc(body_mass))
head(arranged)
  species island bill_len bill_dep flipper_len body_mass  sex year
1  Gentoo Biscoe     48.8     16.2         222      6000 male 2009
2  Gentoo Biscoe     49.8     15.9         229      5950 male 2009
3  Gentoo Biscoe     55.1     16.0         230      5850 male 2009
4  Gentoo Biscoe     50.4     15.7         222      5750 male 2009
5  Gentoo Biscoe     49.5     16.1         224      5650 male 2009
6  Gentoo Biscoe     50.8     17.3         228      5600 male 2009

Les données qualitatives sont triées par ordre alphabétique de la première lettre de la chaîne de caractères :

arranged <- arrange(penguins, desc(island))
head(arranged)
  species    island bill_len bill_dep flipper_len body_mass    sex year
1  Adelie Torgersen     39.1     18.7         181      3750   male 2007
2  Adelie Torgersen     39.5     17.4         186      3800 female 2007
3  Adelie Torgersen     40.3     18.0         195      3250 female 2007
4  Adelie Torgersen       NA       NA          NA        NA   <NA> 2007
5  Adelie Torgersen     36.7     19.3         193      3450 female 2007
6  Adelie Torgersen     39.3     20.6         190      3650   male 2007

8.1.4 Aggrégation de données

L’agrégation consiste à regrouper des observations selon une ou plusieurs variables (catégories, dates, régions, etc.) afin de calculer des indicateurs synthétiques (moyenne, total, écart-type, etc.) pour chaque groupe. C’est ce qui vous faites sur excel quand vous créez un tableau croisé dynamique.

En traitement et analyse de données, il s’agit d’une des opérations les plus courantes.

8.1.4.1 Aggrégation de l’ensemble d’un jeu de données

# On utilise le jeu de données palmerpenguins
data(package = 'palmerpenguins')
head(penguins)
  species    island bill_len bill_dep flipper_len body_mass    sex year
1  Adelie Torgersen     39.1     18.7         181      3750   male 2007
2  Adelie Torgersen     39.5     17.4         186      3800 female 2007
3  Adelie Torgersen     40.3     18.0         195      3250 female 2007
4  Adelie Torgersen       NA       NA          NA        NA   <NA> 2007
5  Adelie Torgersen     36.7     19.3         193      3450 female 2007
6  Adelie Torgersen     39.3     20.6         190      3650   male 2007
penguins <- tidyr::drop_na(penguins)

Imaginons que l’on souhaite calculer la moyenne pour la variable bill_len et la variable flipper_len et conserver le résultat dans un data.frame. La solution de base serait :

data.frame(
    mean_bill_len = mean(penguins$bill_len),
    mean_flipper_len = mean(penguins$flipper_len)
)
  mean_bill_len mean_flipper_len
1      43.99279          200.967

Maintenant, si on voulait calculer la moyenne pour tous les variables numériques, il faudrait réécrire quatre fois la même ligne. Ce n’est pas efficient. En R base, on utiliserait une boucle pour pallier ces redondances. dplyr a été conçu pour faciliter l’écriture dans ces cas là. Pour résumer un jeu de données on utilise la fonction summarise(). Pour obtenir le même résultat que précédemment, on écrira :

summarise(
    penguins,
    mean_bill_len = mean(bill_len),
    mean_flipper_len = mean(flipper_len)
)
  mean_bill_len mean_flipper_len
1      43.99279          200.967

Jusque là, ça ne nous avance pas à grand chose, si ce n’est que l’on plus obligé d’utiliser les $ pour accéder aux variables. Là où ça devient intéressant, c’est bien quand on a beaucoup de variables à résumer. Dans ce cas, on peut sélectionner les variables à résumer en utilisant la fonction across() qui permet prend une condition à vérifier et une opération à effectuer sur les variables retenues. Par exemple, si comme précédemment on souhaite calculer la moyenne pour tous les variables numériques, on écrira :

summarise(penguins, across(where(is.numeric), ~mean(.x)))
  bill_len bill_dep flipper_len body_mass     year
1 43.99279 17.16486     200.967  4207.057 2008.042

Une seul ligne de code permet de faire cela. Et si on souhaite également calculer d’autres statistiques ? C’est aussi possible ! Par exemple, si on souhaite également calculer la médiane et l’écart-type :

summarise(penguins, across(
    # Uniquement sur les variables numériques sauf la variable year
    where(is.numeric) & !year,
    # Quelles fonctions appliquer
    .fn = list(mean = mean, median = median, sd = sd),
    # Quelles noms de variables renvoyer
    .names = "{.col}_{.fn}" # .col est le nom de la var. et .fn le nom de la function
))
  bill_len_mean bill_len_median bill_len_sd bill_dep_mean bill_dep_median
1      43.99279            44.5    5.468668      17.16486            17.3
  bill_dep_sd flipper_len_mean flipper_len_median flipper_len_sd body_mass_mean
1    1.969235          200.967                197       14.01577       4207.057
  body_mass_median body_mass_sd
1             4050     805.2158

Très bien, mais cela ne s’applique que pour un jeu de données entier. Comment faire si l’on souhaite calculer ces statistiques pour des groupes ?

8.1.4.2 Aggrégation par groupe

Pour aggréger des données par groupe en utilisant dplyr il suffit d’employer la fonction group_by() avec summarise(). Le reste du code ne change pas. Dans group_by(), on indique la ou les variables de regroupement. Par exemple, pour calculer la moyenne pour toutes les variables numériques sauf year par espèces et par îles :

penguins |>
    group_by(species, island) |>
    summarise(across(where(is.numeric) & !year, ~mean(.x)))
`summarise()` has grouped output by 'species'. You can override using the
`.groups` argument.
# A tibble: 5 × 6
# Groups:   species [3]
  species   island    bill_len bill_dep flipper_len body_mass
  <fct>     <fct>        <dbl>    <dbl>       <dbl>     <dbl>
1 Adelie    Biscoe        39.0     18.4        189.     3710.
2 Adelie    Dream         38.5     18.2        190.     3701.
3 Adelie    Torgersen     39.0     18.5        192.     3709.
4 Chinstrap Dream         48.8     18.4        196.     3733.
5 Gentoo    Biscoe        47.6     15.0        217.     5092.

En R base, pour effectuer une aggrégation par groupe on utilisera la fonction aggregate(). Par exemple, pour calculer la moyenne de toutes les variables numériques dans iris

aggregate(
    penguins[, sapply(penguins, is.numeric)], # seule les variables numeriques
    by = list(Species = penguins$species), # la variable qui contient les groupes
    FUN = mean # La fonction
)
    Species bill_len bill_dep flipper_len body_mass     year
1    Adelie 38.82397 18.34726    190.1027  3706.164 2008.055
2 Chinstrap 48.83382 18.42059    195.8235  3733.088 2007.971
3    Gentoo 47.56807 14.99664    217.2353  5092.437 2008.067

8.1.5 Jointure de données

Pour faire des jointures, on s’appuira sur le package dplyr et ses fonctions *_join() (c.f. doc). Ces dernières permettent une approche intuitive des jointures, mimant le comportement des jointures en SQL.

Join in dplyr

Imaginons que nous disposons de deux jeux de données. Le premier contient le code postal et le nom de commune française. Le deuxième recèle la population et le code postal. On souhaite joindre les deux données pour disposer au sein d’un même tableau le code postal, le nom des communes et leur population.

dt1 <- data.frame(
    code = c(29120, 29200, 29600, 29160),
    commune = c("Pont-L'abbé", "Brest", "Morlaix", "Crozon")
)
print(dt1)
   code     commune
1 29120 Pont-L'abbé
2 29200       Brest
3 29600     Morlaix
4 29160      Crozon
dt2 <- data.frame(
    code = c(29120, 29200, 29600, 29880),
    pop = c(140993, 8403, 15220, 6719)
)
print(dt2)
   code    pop
1 29120 140993
2 29200   8403
3 29600  15220
4 29880   6719

Pour effectuer une jointure, il faut disposer d’une variable commune entre les deux tableaux. Dans notre cas, il s’agit de la variable code. On peut alors effectuer quatre jointure différentes :

library(dplyr)

left_join(dt1, dt2, by = "code") # garde toutes les lignes de dt1
   code     commune    pop
1 29120 Pont-L'abbé 140993
2 29200       Brest   8403
3 29600     Morlaix  15220
4 29160      Crozon     NA
right_join(dt1, dt2, by = "code") # garde toutes les lignes de dt2
   code     commune    pop
1 29120 Pont-L'abbé 140993
2 29200       Brest   8403
3 29600     Morlaix  15220
4 29880        <NA>   6719
inner_join(dt1, dt2, by = "code") # garde uniquement les codes communs
   code     commune    pop
1 29120 Pont-L'abbé 140993
2 29200       Brest   8403
3 29600     Morlaix  15220
full_join(dt1, dt2, by = "code") # garde toutes les lignes des deux
   code     commune    pop
1 29120 Pont-L'abbé 140993
2 29200       Brest   8403
3 29600     Morlaix  15220
4 29160      Crozon     NA
5 29880        <NA>   6719

Au quotidien, on emploie le plus souvent le left_join().

Souvent, on dispose de la même variable dans deux jeux de données mais nommée différemment. Dans ce cas soit :

  1. On renomme la variable dans un des tableaux puis on joint normalement ;
  2. On change l’argument by par by = c("var_dt1" = "var_dt2").

En base R, on utilise merge() avec différents paramètres pour obtenir le même résultat :

merge(dt1, dt2, by = "code", all.x = TRUE) # left join
   code     commune    pop
1 29120 Pont-L'abbé 140993
2 29160      Crozon     NA
3 29200       Brest   8403
4 29600     Morlaix  15220
merge(dt1, dt2, by = "code", all.y = TRUE) # right join
   code     commune    pop
1 29120 Pont-L'abbé 140993
2 29200       Brest   8403
3 29600     Morlaix  15220
4 29880        <NA>   6719
merge(dt1, dt2, by = "code") # inner join (par défaut)
   code     commune    pop
1 29120 Pont-L'abbé 140993
2 29200       Brest   8403
3 29600     Morlaix  15220
merge(dt1, dt2, by = "code", all = TRUE) # full join
   code     commune    pop
1 29120 Pont-L'abbé 140993
2 29160      Crozon     NA
3 29200       Brest   8403
4 29600     Morlaix  15220
5 29880        <NA>   6719

8.1.6 Format long / large

library(tidyr)
head(relig_income)
# A tibble: 6 × 11
  religion  `<$10k` `$10-20k` `$20-30k` `$30-40k` `$40-50k` `$50-75k` `$75-100k`
  <chr>       <dbl>     <dbl>     <dbl>     <dbl>     <dbl>     <dbl>      <dbl>
1 Agnostic       27        34        60        81        76       137        122
2 Atheist        12        27        37        52        35        70         73
3 Buddhist       27        21        30        34        33        58         62
4 Catholic      418       617       732       670       638      1116        949
5 Don’t kn…      15        14        15        11        10        35         21
6 Evangeli…     575       869      1064       982       881      1486        949
# ℹ 3 more variables: `$100-150k` <dbl>, `>150k` <dbl>,
#   `Don't know/refused` <dbl>

Pour convertir un format large (wide) en format long (long) on utilise la fonction tidyr::pivot_longer().

pivot_longer(
  relig_income,
  cols = 2:11,
  names_to = "income",
  values_to = "number",
)
# A tibble: 180 × 3
   religion income             number
   <chr>    <chr>               <dbl>
 1 Agnostic <$10k                  27
 2 Agnostic $10-20k                34
 3 Agnostic $20-30k                60
 4 Agnostic $30-40k                81
 5 Agnostic $40-50k                76
 6 Agnostic $50-75k               137
 7 Agnostic $75-100k              122
 8 Agnostic $100-150k             109
 9 Agnostic >150k                  84
10 Agnostic Don't know/refused     96
# ℹ 170 more rows

8.2 Manipulation de matrice

Bien que les data.frame (et objets associés) soit les objets les plus courants que vous aurez à traiter, il arrive que l’on se retrouve à traiter des matrices (matrix). Ces objets, plus simples dans leur structure que les Dataframe, sont souvent utilisés pour optimiser des traitements. Avant de lire cette section, renseignez-vous sur la structure de ce type d’objet (c.f. Section 6.4).

8.2.1 Opérations sur les matrices

Les opérations arithmétiques sont vectorisées.

m1 <- matrix(1:9, nrow = 3, ncol = 3)
m2 <- matrix(1:9, nrow = 3, byrow = TRUE)

m1 + 10 # Ajout d'une constante
     [,1] [,2] [,3]
[1,]   11   14   17
[2,]   12   15   18
[3,]   13   16   19
m1 * 2  # Multiplication élément par élément
     [,1] [,2] [,3]
[1,]    2    8   14
[2,]    4   10   16
[3,]    6   12   18
m1 ^ 2  # Carré de chaque élément
     [,1] [,2] [,3]
[1,]    1   16   49
[2,]    4   25   64
[3,]    9   36   81

Pour les multiplications matricielles

m1 * m2 # produit de hadamard (élément par élément)
     [,1] [,2] [,3]
[1,]    1    8   21
[2,]    8   25   48
[3,]   21   48   81
m1 %*% m2 # produit scalaire (dot product)
     [,1] [,2] [,3]
[1,]   66   78   90
[2,]   78   93  108
[3,]   90  108  126
m1[1, ] %o% m1[2, ] # produit extérieur (outer product)
     [,1] [,2] [,3]
[1,]    2    5    8
[2,]    8   20   32
[3,]   14   35   56

Il est également possible de calculer des statistiques pour les lignes et les colonnes :

rowSums(m1)
[1] 12 15 18
rowMeans(m1)
[1] 4 5 6
colSums(m1)
[1]  6 15 24
colMeans(m1)
[1] 2 5 8

8.2.2 Autres opérations

t(m1) # Transposée
     [,1] [,2] [,3]
[1,]    1    2    3
[2,]    4    5    6
[3,]    7    8    9
det(m1) # Déterminant
[1] 0
diag(m1) # Diagonale
[1] 1 5 9

8.2.3 Exemple

Les matrices sont notamment utilisées dans les fonctions orientées statistiques. C’est tout naturel car l’algèbre linéaire et les probabilités sont basées sur de tels objets mathématiques.

Par exemple, dans le cas d’une régression linéaire dont les \(\beta\) sont estimés par la méthode des moindres carrés ordinaires (OLS), les données sont stockées dans une matrice :

# Génération de y
y <- mtcars$mpg
X <- model.matrix(mpg ~ ., data = mtcars)

# Estimations des betas
XtX_inv <- solve(t(X) %*% X) # (X^T X)^(-1)
XtY <- t(X) %*% y            # (X^T y)
beta_hat <- XtX_inv %*% XtY  # beta = (X^T X)^(-1) X^T y

8.3 Traitement de chaîne de caractères

Références :

  • Le chapitre sur les chaînes de caractères de guide-R (Larmarange, 2025)

8.3.1 Chercher/remplacer

Pour chercher remplacer dans une chaîne de caractère (variable ou vecteur), on utiliser la fonction de base gsub() ou la fonction stringr::str_replace().

# Avec une chaîne de caractère
x <- "Une phras mal orthographié"
x <- gsub(x = x, "phras", "phrase")
gsub(x = x, "orthographié", "orthographiée")
[1] "Une phrase mal orthographiée"
# Avec un vecteur
x <- c("Puie", "pluie", "oRag_", "orage")
x <- gsub(x = x, "Puie", "pluie")
gsub(x = x, "oRag_", "orage")
[1] "pluie" "pluie" "orage" "orage"

Tout l’intérêt de passer par un langage de programmation est de pouvoir automatiser au maximum le remplacement des chaînes de caractère. Pour cela, on utilise les expressions régulières (on dira plutôt regex). Ces dernières permettent d’identifier efficacement des schémas dans des chaînes de caractères. Imaginons que l’on souhaite supprimer tous les espaces qui sont placés au début d’une chaîne de caractère dans un vecteur :

x <- c("Michel Legrand", "Glenn Gould", " Michel Legrand", " Glenn Gould")
# Quatre valeur unique alors qu'on souhaite en avoir 2.
unique(x)
[1] "Michel Legrand"  "Glenn Gould"     " Michel Legrand" " Glenn Gould"   
# Supprimer les espaces situés au début de la chaîne de caractère
x <- gsub(x = x, "^ ", "")
unique(x)
[1] "Michel Legrand" "Glenn Gould"   

On peut également mouvoir des bouts de chaînes en utilisant des groupes. Supposons que l’on veuille passer les noms avant les prénoms :

x <- c("Michel Legrand", "Glenn Gould", "Michel Legrand", "Glenn Gould")
gsub(x = x, "^(.*(?= )) (.*$)", "\\2 \\1", perl = TRUE)
[1] "Legrand Michel" "Gould Glenn"    "Legrand Michel" "Gould Glenn"   

Les expressions régulières ne sont pas forcément facile à manipuler mais quand on commence à les prendre en main on gagne un temps fou. Vous pouvez vous renseigner ici.

Quand vous commencez à maitriser un peu les regex

8.3.2 Concaténation

Pour concaténer deux chaînes de caractères :

library(glue)

str1 <- "un chêne"
str2 <- "vouvre"

# Méthode 1: paste() et paste0()
conc1 <- paste(str1, str2) # espace par défaut entre les deux chaînes
conc2 <- paste0(str1, " ", str2)

# Méthode 2: sprintf()
conc3 <- sprintf("%s %s", str1, str2)

# Méthode 3: glue()
conc4 <- glue("{str1} {str2}")

print(conc4)
un chêne vouvre
all(c(conc1, conc2, conc3) == conc4)
[1] TRUE

glue() est la méthode la plus moderne, elle est à privilégier.

8.3.3 Tableau de contingence

Pour calculer un tableau de contingence sous R, on utilise la fonction table() avec les deux vecteurs que l’on souhaite analyser.

table(penguins$species, penguins$island)
           
            Biscoe Dream Torgersen
  Adelie        44    55        47
  Chinstrap      0    68         0
  Gentoo       119     0         0