<- c(1, 3, 8) x
16 Structures de données
R propose de nombreuses structures de données différentes, et les extensions peuvent en implémenter de nouvelles. Cette section introduit trois structures parmi les plus utilisées : les vecteurs atomiques, les listes et les tableaux de données. Certaines ont déjà été abordées et utilisées précédemment, mais connaître leurs spécificités et savoir les manipuler est utile voire indispensable, notamment lorsqu’on veut créer ses propres fonctions.
16.1 Vecteurs atomiques
Les vecteurs atomiques sont des structures qui regroupent ensemble plusieurs éléments constitués d’une seule valeur, avec deux contraintes : ces valeurs doivent toutes être du même type. Les vecteurs atomiques ont déjà été introduits Section 9.1.
16.1.1 Création d’un vecteur
On peut construire un vecteur manuellement avec la fonction c()
.
Si on souhaite générer un vecteur de valeurs entières successives, on peut utiliser l’opérateur :
ou la fonction seq_len()
.
2:8
#> [1] 2 3 4 5 6 7 8
seq_len(5)
#> [1] 1 2 3 4 5
La fonction seq()
permet de générer des séquences régulière plus complexes.
seq(0.5, 2.5, by = 0.5)
#> [1] 0.5 1.0 1.5 2.0 2.5
seq(0, 4, length.out = 6)
#> [1] 0.0 0.8 1.6 2.4 3.2 4.0
Une autre variante de seq()
, nommée seq_along()
, permet de générer un vecteur d’entiers correspondant à la longueur d’un objet passé en argument :
<- c("Pomme", "Poire")
x seq_along(x)
#> [1] 1 2
<- runif(10)
y seq_along(y)
#> [1] 1 2 3 4 5 6 7 8 9 10
Enfin, la fonction rep()
permet de répéter un élément ou un vecteur.
rep("Pomme", 6)
#> [1] "Pomme" "Pomme" "Pomme" "Pomme" "Pomme" "Pomme"
rep(1:4, 2)
#> [1] 1 2 3 4 1 2 3 4
Si on souhaite connaître le nombre d’éléments d’un vecteur, on peut utiliser la fonction length()
.
<- rep(1:4, 2)
v length(v)
#> [1] 8
Il peut parfois être utile de créer des vecteurs “vides”. Dans ce cas on peut les initialiser avec les fonctions vector()
, character()
ou numeric()
. Par défaut ces fonctions renvoient un vecteur sans élément, mais on peut aussi leur indiquer en argument le nombre d’éléments souhaités (qui seront alors initialisés avec une valeur par défaut).
numeric()
#> numeric(0)
character(2)
#> [1] "" ""
16.1.2 Vecteurs nommés
Les éléments d’un vecteur peuvent être nommés. Ces noms peuvent êtré définis au moment de la création du vecteur.
<- c(e1 = 1, e2 = 3, e3 = 8)
x
x#> e1 e2 e3
#> 1 3 8
On peut utiliser names()
pour récupérer les noms des éléments d’un vecteur.
names(x)
#> [1] "e1" "e2" "e3"
On peut aussi utiliser names()
pour créer ou modifier les noms d’un vecteur existant.
names(x) <- c("brouette", "moto", "igloo")
x#> brouette moto igloo
#> 1 3 8
16.1.3 Types de vecteurs
On peut déterminer le type d’un vecteur avec l’instruction typeof
.
<- c(1, 3, 8)
x typeof(x)
#> [1] "double"
<- c("foo", "bar", "baz")
y typeof(y)
#> [1] "character"
<- c(TRUE, FALSE, FALSE)
z typeof(z)
#> [1] "logical"
Parmi les principaux types de données on notera1 :
- les chaînes de caractères (
character
) - les nombres flottants (
double
) - les nombres entiers (
integer
) - les valeurs logiques (
logical
)
À noter que par défaut les nombres sont considérés comme des nombres flottants (des nombres décimaux avec une virgule) : pour les définir explicitement comme nombres entiers on peut leur ajouter le suffixe L
.
<- c(1L, 3L, 8L)
x typeof(x)
#> [1] "integer"
On peut tester le type d’un vecteur avec les fonctions is.character
, is.double
, is.logical
… Autre fonction utile, is.numeric
teste si un vecteur est de type double
ou integer
.
<- c(1, 3, 8)
x is.numeric(x)
#> [1] TRUE
> 2
x #> [1] FALSE TRUE TRUE
is.logical(x > 2)
#> [1] TRUE
<- c("foo", "bar", "baz")
y is.character(y)
#> [1] TRUE
Petite spécificité, les facteurs (voir Section 9.3.1) ne sont pas considérés par R comme des character
, même s’ils comportent des chaînes de caractères. Pour tester si un vecteur est de type facteur, on utilise is.factor()
.
<- factor(c("rouge", "vert", "rouge"))
fac is.character(fac)
#> [1] FALSE
is.factor(fac)
#> [1] TRUE
Tous les éléments d’un vecteur doivent être du même type. Si ça n’est pas le cas, les éléments seront convertis au type le plus “général” présent dans le vecteur, sachant que les character
sont plus généraux que les numeric
, qui sont eux-mêmes plus généraux que les logical
.
Dans l’exemple suivant, le nombre 1
est transformé en chaîne de caractère "1"
.
c(1, "foo")
#> [1] "1" "foo"
Si on mélange nombres et valeurs logiques, les TRUE
sont convertis en 1
et les FALSE
en 0
.
c(TRUE, 2, FALSE)
#> [1] 1 2 0
Si la valeur NA
, comme on l’a vu, permet d’indiquer une valeur manquante (Not Available
), il existe en réalité plusieurs types de NA
, même si cette distinction est la plupart du temps transparente pour l’utilisateur. On a ainsi notamment des valeurs NA_integer_
, NA_character_
, NA_real_
.
La conversion automatique d’un type en un autre est à l’origine d’un idiome courant en R. Quand on applique une fonction qui attend un vecteur de nombres à un vecteur de valeurs logiques, celles-ci sont automatiquement converties, les TRUE
devenant 1 et les FALSE
devenant 0. Du coup, si on applique sum()
à un vecteur de valeurs logiques, le résultat est égal au nombre de valeurs TRUE
.
sum(c(TRUE, FALSE, TRUE))
#> [1] 2
On peut donc appliquer sum()
à un test, et on obtiendra le nombre de valeurs pour lesquelles le test est vrai.
<- c(1, 5, 8, 12, 14)
x sum(x > 10)
#> [1] 2
Ceci fournit un raccourci très pratique. Dans l’exemple suivant, on tire 1000 nombres au hasard entre 0 et 1 et on calcule le nombre de valeurs obtenues qui sont inférieures à 0.5.
<- runif(1000)
x sum(x < 0.5)
#> [1] 511
Autre raccourci moins utilisé, appliquer mean()
au résultat d’un test donne la proportion de valeurs pour lesquelles le test est vrai.
<- c(1, 5, 8, 12, 14)
x mean(x > 10)
#> [1] 0.4
<- runif(1000)
x mean(x < 0.5)
#> [1] 0.507
On peut convertir un vecteur d’un type à un autre avec les fonctions as.character()
, as.numeric()
et as.logical()
. Si une valeur ne peut pas être convertie, elle est remplacée par un NA
, et R affiche un avertissement.
as.character(1:3)
#> [1] "1" "2" "3"
as.logical(c(0, 2, 4))
#> [1] FALSE TRUE TRUE
as.numeric(c("foo", "23"))
#> Warning: NAs introduced by coercion
#> [1] NA 23
16.1.4 Sélection d’éléments
On a vu Section 9.1 que l’opérateur []
peut être utilisé pour sélectionner des éléments d’un vecteur. Cet opérateur peut comporter :
- des nombres (qui sélectionnent par position)
- des chaînes de caractères (qui sélectionnent par nom)
- un test ou des valeurs logiques (qui sélectionnent les éléments correspondant à
TRUE
)
<- c(e1 = 1, e2 = 2, e3 = 8, e4 = 12)
x c(1, 4)]
x[#> e1 e4
#> 1 12
c("e2", "e4")]
x[#> e2 e4
#> 2 12
< 10]
x[x #> e1 e2 e3
#> 1 2 8
Si on fournit à []
un ou plusieurs nombres négatifs, les valeurs correspondantes seront supprimées plutôt que sélectionnées.
-1]
x[#> e2 e3 e4
#> 2 8 12
c(-2, -4)]
x[#> e1 e3
#> 1 8
Si on souhaite afficher les premières ou dernières valeurs d’un vecteur, les fonctions head()
et tail()
peuvent être utiles.
head(x, 2)
#> e1 e2
#> 1 2
tail(x, 1)
#> e4
#> 12
16.1.5 Modification
Utilisé conjointement avec l’opérateur d’assignation <-
, l’opérateur []
permet de remplacer des éléments.
<- c(e1 = 1, e2 = 2, e3 = 8, e4 = 12)
x 1] <- -1000
x[
x#> e1 e2 e3 e4
#> -1000 2 8 12
"e2"] <- 0
x[
x#> e1 e2 e3 e4
#> -1000 0 8 12
> 10] <- NA
x[x
x#> e1 e2 e3 e4
#> -1000 0 8 NA
Utilisé sans arguments, []
se contente de renvoyer le vecteur entier. Mais couplé à une assignation, il remplace chacun des éléments du vecteur plutôt que le vecteur lui-même.
<- 3
x[]
x#> e1 e2 e3 e4
#> 3 3 3 3
16.2 Listes
Les listes sont une généralisation des vecteurs : elles regroupent également plusieurs éléments ensemble, mais ceux-ci peuvent être de n’importe quel type, y compris des objets complexes. Une liste peut donc contenir des vecteurs, des listes, des tableaux de données, des fonctions, des graphiques ggplot2
stockés dans un objet, etc.
16.2.1 Création
On construit une liste avec la fonction list
.
list(1, "foo", c("Pomme", "Citron"))
#> [[1]]
#> [1] 1
#>
#> [[2]]
#> [1] "foo"
#>
#> [[3]]
#> [1] "Pomme" "Citron"
L’affichage du contenu d’une liste dans la console diffère de celui d’un vecteur. Dans le cas d’une liste les éléments sont affichés les uns en dessous des autres, et séparés par leur indice numérique entre une paire de crochets. Dans l’affichage ci-dessus, il faut bien distinguer les [[1]]
, [[2]]
et [[3]]
, qui correspondent au numéro de l’élément de la liste, et les [1]
qui font partie de l’affichage du contenu de ces éléments.
Comme pour les vecteurs, on peut nommer les éléments à la création de la liste.
<- list(nombre = 1, char = "foo", vecteur = c("Pomme", "Citron"))
liste
liste#> $nombre
#> [1] 1
#>
#> $char
#> [1] "foo"
#>
#> $vecteur
#> [1] "Pomme" "Citron"
Dans ce cas l’affichage de la liste dans la console montre ces noms plutôt que les indices numériques des éléments.
Comme pour les vecteurs atomiques, on peut utiliser names()
pour afficher ou modifier les noms des éléments.
names(liste)
#> [1] "nombre" "char" "vecteur"
Quand la liste est plus complexe, l’affichage peut vite devenir illisible.
<- list(
liste l2 = list(x = 1:10, y = c("Pomme", "Citron")),
df = data.frame(v1 = 2:5, v2 = LETTERS[2:5]),
y = runif(10)
)
liste#> $l2
#> $l2$x
#> [1] 1 2 3 4 5 6 7 8 9 10
#>
#> $l2$y
#> [1] "Pomme" "Citron"
#>
#>
#> $df
#> v1 v2
#> 1 2 B
#> 2 3 C
#> 3 4 D
#> 4 5 E
#>
#> $y
#> [1] 0.724597102 0.188168686 0.055785595 0.355001688 0.600353963 0.378297869
#> [7] 0.625124599 0.621432831 0.007753398 0.348671340
Dans ce cas la fonction str
peut être utile pour afficher de manière plus compacte la structure de la liste. Dans cet exemple elle permet de voir un peu plus clairement que x
et y
sont des éléments d’une sous-liste l2
.
str(liste)
#> List of 3
#> $ l2:List of 2
#> ..$ x: int [1:10] 1 2 3 4 5 6 7 8 9 10
#> ..$ y: chr [1:2] "Pomme" "Citron"
#> $ df:'data.frame': 4 obs. of 2 variables:
#> ..$ v1: int [1:4] 2 3 4 5
#> ..$ v2: chr [1:4] "B" "C" "D" "E"
#> $ y : num [1:10] 0.7246 0.1882 0.0558 0.355 0.6004 ...
16.2.2 Ajout d’éléments
Attention, si on souhaite ajouter un nouvel élément à une liste, il ne faut pas utiliser à nouveau list()
, car dans ce cas notre liste de départ est insérée comme une “sous-liste”.
<- list(e1 = 1:3, e2 = "Chihuhua")
liste <- list(liste, nouveau = 100)
liste2 str(liste2)
#> List of 2
#> $ :List of 2
#> ..$ e1: int [1:3] 1 2 3
#> ..$ e2: chr "Chihuhua"
#> $ nouveau: num 100
Il faut à la place utiliser c()
, comme pour les vecteurs.
<- c(liste, nouveau = 100)
liste3 str(liste3)
#> List of 3
#> $ e1 : int [1:3] 1 2 3
#> $ e2 : chr "Chihuhua"
#> $ nouveau: num 100
c()
permet aussi de “concaténer” deux listes existantes en une seule.
<- list(a = 1, b = 2)
liste1 <- list(x = 3, y = 4)
liste2 c(liste1, liste2)
#> $a
#> [1] 1
#>
#> $b
#> [1] 2
#>
#> $x
#> [1] 3
#>
#> $y
#> [1] 4
16.2.3 Sélection d’éléments
Il y a deux opérateurs différents qui permettent de sélectionner les éléments d’une liste : les crochets simples []
et les crochets doubles [[]]
. La différence entre ces deux opérateurs est souvent source de confusion.
Partons de la liste suivante :
<- list(1:5, "foo", c("Pomme", "Citron"))
liste
liste#> [[1]]
#> [1] 1 2 3 4 5
#>
#> [[2]]
#> [1] "foo"
#>
#> [[3]]
#> [1] "Pomme" "Citron"
Si on utilise les crochets simples pour sélectionner le premier élément de cette liste, on obtient le résultat suivant :
1]
liste[#> [[1]]
#> [1] 1 2 3 4 5
On notera que le résultat est une liste à un seul élément.
Si on utilise les crochets doubles :
1]]
liste[[#> [1] 1 2 3 4 5
On obtient cette fois-ci non pas une liste composée du premier élément, mais le contenu de ce premier élément.
La différence est importante, mais pas toujours facile à retenir. On peut utiliser deux petites astuces mnémotechniques :
- si une liste est un train composé de plusieurs wagons,
[1]
retourne le premier wagon du train, tandis que[[1]]
renvoie le contenu du premier wagon. - une alternative est de considérer que
[[]]
va chercher “plus profondément” que[]
.
Un autre point important est que si on passe plusieurs éléments à [[]]
, la sélection se fait d’une manière récursive peu intuitive et source d’erreurs. Il est donc conseillé de toujours utiliser [[]]
avec un seul argument, et d’utiliser []
si on souhaite sélectionner plusieurs éléments d’une liste.
c(1, 2)]
liste[#> [[1]]
#> [1] 1 2 3 4 5
#>
#> [[2]]
#> [1] "foo"
En résumé :
- si on souhaite récupérer uniquement le contenu d’un élément d’une liste, on utilise
[[]]
avec un seul argument. - si on souhaite récupérer une nouvelle liste en sélectionnant des éléments de notre liste actuelle, on utilise
[]
avec un ou plusieurs arguments.
Comme pour les vecteurs, on peut utiliser des nombres négatifs avec []
pour exclure des éléments plutôt que les sélectionner, et on peut également utiliser les fonctions head()
et tail()
.
Si la liste est nommée, on peut sélectionner des éléments par noms avec les deux opérateurs.
<- list(nombre = 1, char = "foo", vecteur = c("Pomme", "Citron"))
liste c("nombre", "char")]
liste[#> $nombre
#> [1] 1
#>
#> $char
#> [1] "foo"
"vecteur"]]
liste[[#> [1] "Pomme" "Citron"
On peut aussi utiliser l’opérateur $
, qui équivaut à [[]]
:
$vecteur
liste#> [1] "Pomme" "Citron"
16.2.4 Modification
Comme pour les vecteurs, on peut utiliser l’opérateur []
et l’opérateur d’assignation <-
pour modifier des éléments d’une liste.
<- list(nombre = 1:5, char = "foo", vecteur = c("Pomme", "Citron"))
liste "nombre"] <- "first"
liste[
liste#> $nombre
#> [1] "first"
#>
#> $char
#> [1] "foo"
#>
#> $vecteur
#> [1] "Pomme" "Citron"
c(1, 3)] <- 0
liste[
liste#> $nombre
#> [1] 0
#>
#> $char
#> [1] "foo"
#>
#> $vecteur
#> [1] 0
Attention à ne pas utiliser les crochets doubles pour modifier des éléments d’une liste car ceux-ci peuvent avoir un comportement inattendu si on veut modifier plusieurs éléments d’un coup.
Enfin, on peut supprimer un ou plusieurs éléments d’une liste, en leur attribuant la valeur NULL
2.
<- list(nombre = 1:5, char = "foo", vecteur = c("Pomme", "Citron"))
liste $char <- NULL
liste
liste#> $nombre
#> [1] 1 2 3 4 5
#>
#> $vecteur
#> [1] "Pomme" "Citron"
16.2.5 Utilisation
En tant que généralisation des vecteurs atomiques, les listes sont utiles dès qu’on souhaite regrouper des éléments complexes ou hétérogènes.
On les utilisera par exemple pour retourner plusieurs résultats depuis une fonction.
<- function(x) {
indicateurs list(
moyenne = mean(x),
variance = var(x)
)
}
<- 1:10
x <- indicateurs(x)
res $moyenne
res#> [1] 5.5
$variance
res#> [1] 9.166667
On utilise également les listes pour stocker des objets complexes et leur appliquer des fonctions. Ce fonctionnement sera abordé en détail dans la Chapitre 18, mais en guise de petit aperçu, l’exemple fictif suivant récupère les noms de tous les fichiers CSV du répertoire courant et les importe tous dans une liste à l’aide de purrr::map()
et de read_csv()
.
<- list.files(pattern = "*.csv")
files <- purrr::map(files, read_csv) dfs
On pourra ensuite utiliser cette liste de tableaux pour leur appliquer des transformations ou les fusionner.
16.3 Tableaux de données (data frame et tibble)
On a déjà utilisé les tableaux de données à de nombreux reprises en manipulant des data frames ou des tibbles. Les seconds sont une variante des premiers, les différences entre les deux ayant été abordées Section 6.4.
Un tableau de données est en réalité une liste nommée de vecteurs atomiques avec une contrainte spécifique : ces vecteurs doivent tous être de même longueur, ce qui garantit le format “tabulaire” des données.
16.3.1 Création
Un tableau de données est le plus souvent créé en important des données depuis un fichier au format CSV, tableur ou autre. On peut cependant créer un data frame manuellement via la fonction data.frame()
:
<- data.frame(
df fruit = c("Pomme", "Pomme", "Citron"),
poids = c(154, 167, 92),
couleur = c("vert", "vert", "jaune")
)
On peut aussi créer un tibble manuellement avec la fonction tibble()
. La syntaxe est la même que celle de data.frame()
, mais avec un comportement un peu différent : notamment, les noms comportant des espaces ou des caractères spéciaux sont conservés tels quels.
La fonction tribble()
permet de créer un tibble manuellement avec une syntaxe “par ligne” qui peut être un peu plus lisible.
<- tribble(
df_trib ~fruit, ~poids, ~couleur,
"Pomme", 154, "vert",
"Pomme", 167, "vert",
"Citron", 92, "jaune"
)
On peut convertir un data frame en tibble avec la fonction as_tibble()
.
<- as_tibble(df)
df_tib
df_tib#> # A tibble: 3 × 3
#> fruit poids couleur
#> <chr> <dbl> <chr>
#> 1 Pomme 154 vert
#> 2 Pomme 167 vert
#> 3 Citron 92 jaune
16.3.2 Noms de colonnes et de lignes
On peut lister et modifier les noms des colonnes d’un tableau avec les fonctions names()
ou colnames()
(qui sont équivalentes).
names(df)
#> [1] "fruit" "poids" "couleur"
colnames(df)
#> [1] "fruit" "poids" "couleur"
On peut attribuer des noms aux lignes d’un data frame à l’aide de la fonction rownames()
. Attention cependant, les noms de ligne ne sont (volontairement) pas pris en charge par les tibbles.
rownames(df) <- c("fruit1", "fruit2", "fruit3")
rownames(df)
#> [1] "fruit1" "fruit2" "fruit3"
rownames(df_tib) <- c("fruit1", "fruit2", "fruit3")
#> Warning: Setting row names on a tibble is deprecated.
Si on souhaite conserver des noms de ligne en passant d’un data frame à un tibble, il faut les stocker dans une nouvelle colonne, soit en la créant manuellement soit avec la fonction rownames_to_column()
(qui a l’avantage de placer la nouvelle colonne en première position du tableau).
rownames_to_column(df, "name")
#> name fruit poids couleur
#> 1 fruit1 Pomme 154 vert
#> 2 fruit2 Pomme 167 vert
#> 3 fruit3 Citron 92 jaune
16.3.3 Sélection de lignes et de colonnes
On a déjà vu dans les parties précédentes plusieurs manières de sélectionner des éléments dans un tableau de données.
Ainsi, on peut sélectionner une colonne via l’opérateur $
.
$fruit
df#> [1] "Pomme" "Pomme" "Citron"
Comme un tableau de données est en réalité une liste de colonnes, on peut aussi utiliser l’opérateur [[]]
pour sélectionner l’une de ses colonnes, par position ou par nom3.
"fruit"]]
df[[#> [1] "Pomme" "Pomme" "Citron"
2]]
df[[#> [1] 154 167 92
On peut utiliser head()
et tail()
avec un tableau de données : dans ce cas ces fonctions retourneront les premières ou dernières lignes du tableau.
head(df, 2)
#> fruit poids couleur
#> fruit1 Pomme 154 vert
#> fruit2 Pomme 167 vert
tail(df, 1)
#> fruit poids couleur
#> fruit3 Citron 92 jaune
On peut également utiliser l’opérateur [,]
pour sélectionner à la fois des lignes et des colonnes, en lui passant deux arguments séparés par une virgule : d’abord la sélection des lignes puis celle des colonnes. Dans les deux cas on peut sélectionner par position, nom ou condition. Si on laisse un argument vide, on sélectionne l’intégralité des lignes ou des colonnes.
# Lignes 1 et 3 et colonne "poids"
c(1, 3), "poids"]
df[#> [1] 154 92
# Toutes les lignes et colonnes "poids" et "fruit"
c("poids", "fruit")]
df[, #> poids fruit
#> fruit1 154 Pomme
#> fruit2 167 Pomme
#> fruit3 92 Citron
# Lignes pour lesquelles poids > 150, et toutes les colonnes
$poids > 150, ]
df[df#> fruit poids couleur
#> fruit1 Pomme 154 vert
#> fruit2 Pomme 167 vert
library(stringr)
# Colonnes dont le nom contient un "o", et toutes les lignes
str_detect(names(df), "o")]
df[, #> poids couleur
#> fruit1 154 vert
#> fruit2 167 vert
#> fruit3 92 jaune
Attention, le comportement de [,]
est différent entre les tibbles et les data frame lorsqu’on ne sélectionne qu’une seule colonne. Dans le cas d’un data frame, le résultat est un vecteur, dans le cas d’un tibble le résultat est un tableau à une colonne.
"fruit"]
df[, #> [1] "Pomme" "Pomme" "Citron"
"fruit"]
df_tib[, #> # A tibble: 3 × 1
#> fruit
#> <chr>
#> 1 Pomme
#> 2 Pomme
#> 3 Citron
Cette différence peut parfois être source d’erreurs, notamment quand on développe une fonction qui prend un tableau de données en argument.
16.3.4 Modification
On peut utiliser [[]]
et [,]
avec l’opérateur d’assignation <-
pour modifier tout ou partie d’un tableau de données.
# Création d'une nouvelle colonne poids_kg
"poids_kg"]] <- df$poids / 1000
df[[
df#> fruit poids couleur poids_kg
#> fruit1 Pomme 154 vert 0.154
#> fruit2 Pomme 167 vert 0.167
#> fruit3 Citron 92 jaune 0.092
# Remplacement de la valeur de la colonne "fruit" pour les lignes
# pour lesquelles "fruit" vaut "Citron"
$fruit == "Citron", "fruit"] <- "Agrume"
df[df
df#> fruit poids couleur poids_kg
#> fruit1 Pomme 154 vert 0.154
#> fruit2 Pomme 167 vert 0.167
#> fruit3 Agrume 92 jaune 0.092
Pour conclure, on peut noter que l’utilisation des opérateurs [[]]
et [,]
sur un tableau de données peut sembler redondante et moins pratique que l’utilisation des verbes de dplyr
comme select()
ou filter()
. Ils peuvent cependant être utiles lorsqu’on souhaite éviter les complications liées à l’utilisation du tidyverse à l’intérieur de fonctions, comme indiqué Chapitre 19. Ils peuvent également être plus rapides, et il est important de les connaître car on les rencontrera très fréquemment dans du code R sur le Web ou dans des packages.
16.4 Ressources
L’ouvrage R for Data Science (en anglais), accessible en ligne, contient un chapitre sur les vecteurs atomiques et les listes, et un chapitre dédié aux tibbles.
Pour aller encore plus loin, l’ouvrage Advanced R (également en anglais) aborde de manière approfondie les structures de données et les opérateurs de sélection []
, [[]]
et $
.
16.5 Exercices
16.5.1 Vecteurs atomiques
Exercice 1.1
À l’aide de seq()
, créer un vecteur v
contenant tous les nombres pairs entre 10 et 20.
<- seq(10, 20, by = 2) v
Sélectionner les 3 premières valeurs de v
.
1:3]
v[head(v, 3)
Sélectionner toutes les valeurs de v
strictement inférieures à 15.
< 15] v[v
Créer une fonction derniere()
qui prend en paramètre un vecteur et retourne son dernier élément (la fonction doit pouvoir s’appliquer à n’importe quel vecteur, quelle que soit sa longueur).
derniere(v)
#> [1] 20
<- function(v) {
derniere length(v)]
v[
}
# Ou bien
<- function(v) {
derniere tail(v, 1)
}
Créer une fonction sauf_derniere()
qui prend en paramètre un vecteur et retourne ce vecteur sans son dernier élément.
sauf_derniere(v)
#> [1] 10 12 14 16 18
<- function(v) {
sauf_derniere -length(v)]
v[
}
# Ou bien
<- function(v) {
sauf_derniere head(v, -1)
}
Exercice 1.2
Soit le vecteur vn
suivant :
<- c(val1 = 10, val2 = 0, val3 = 14) vn
Sélectionner les valeurs nommées “val1” et “val3”.
c("val1", "val3")] vn[
Créer une fonction select_noms()
qui prend en argument un vecteur v
et un ou plusieurs noms, et retourne uniquement les éléments de v
correspondant à ces noms.
select_noms(vn, c("val2", "val3"))
#> val2 val3
#> 0 14
<- function(v, noms) {
select_noms
v[noms] }
Facultatif : créer une fonction sauf_nom()
qui prend en argument un vecteur v
et un nom, et retourne tous les éléments de v
sauf celui correspondant à ce nom.
sauf_nom(vn, "val2")
#> val1 val3
#> 10 14
<- function(v, nom) {
sauf_nom names(v) != nom]
v[ }
Facultatif : comparer les résultats des deux instructions suivantes.
"val1"]
vn["val1"]] vn[[
Exercice 1.3
Soit les vecteurs x
et y
suivants :
<- c(1, NA, 3, 4, NA)
x <- c(10, 20, 30, 40, 50) y
À l’aide de l’opérateur []
, sélectionner uniquement les valeurs NA
de x
.
is.na(x)] x[
De la même manière, sélectionner les valeurs de y
correspondant aux valeurs NA
de x
(c’est-à-dire les valeurs 20 et 50).
is.na(x)] y[
En utilisant les deux instructions précédentes et l’opérateur d’assignation <-
, remplacer les valeurs manquantes de x
par les valeurs correspondantes de y
.
is.na(x)] <- y[is.na(x)] x[
Exercice 1.4
Créer une fonction problemes_conversion
qui :
- prend en argument un vecteur
v
- le convertit en vecteur numérique
- retourne les valeurs de
v
qui n’ont pas été converties correctement, c’est-à-dire celles qui ne valaient pasNA
dansv
mais valentNA
après la conversion.
Vérifier avec :
<- c("igloo", "20", NA, "3.5", "4,8")
x problemes_conversion(x)
#> Warning in problemes_conversion(x): NAs introduced by coercion
#> [1] "igloo" "4,8"
<- function(v) {
problemes_conversion <- as.numeric(v)
conv !is.na(v) & is.na(conv)]
v[ }
16.5.2 Listes
Exercice 2.1
Créer une liste liste
ayant la structure suivante :
#> List of 3
#> $ : num 1
#> $ : chr "oui"
#> $ : int [1:3] 10 11 12
<- list(1, "oui", 10:12) liste
Donner les noms suivants aux éléments de la liste : num
, reponse
et vec
.
names(liste) <- c("num", "reponse", "vec")
Ajouter un élément nommé chat
et ayant pour valeur “Ronron” à la fin de liste
.
<- c(liste, chat = "Ronron") liste
Modifier l’élément chat
pour lui donner la valeur “Ronpchi”.
$chat <- "Ronpchi"
liste# Ou bien
"chat"] <- "Ronpchi" liste[
Supprimer l’élément vec
de liste
.
$vec <- NULL
liste# Ou bien
"vec"] <- NULL liste[
Exercice 2.2
Créer une fonction nommée extremes
qui prend en argument un vecteur et retourne une liste nommée comportant sa valeur minimale et sa valeur maximale.
<- function(x) {
extremes list(min = min(x), max = max(x))
}
Appliquer cette fonction à un vecteur de votre choix et utiliser le résultat pour calculer l’étendue (soit la différence entre la valeur maximale et la valeur minimale).
<- runif(10)
v <- extremes(v)
res $max - res$min res
Exercice 2.3
Soit la liste suivante :
<- list(1:3, runif(5), "youpi") liste
Sélectionner la sous liste composée des éléments 1 et 3 de liste
.
c(1, 3)] liste[
Sélectionner la sous-liste composée du premier élément de liste
.
1] liste[
Sélectionner le contenu du premier élément de liste
.
1]] liste[[
En enchaînant deux opérations de sélection, sélectionner le deuxième élément du premier élément de liste
.
1]][2] liste[[
Exercice 2.4
Créer une fonction description_liste
qui prend en argument une liste et retourne :
- son premier élément
- son dernier élément
- le nombre d’éléments qu’elle contient
Vérifier avec :
<- list(1:3, runif(5), "youpi")
liste description_liste(liste)
#> $premier_element
#> [1] 1 2 3
#>
#> $dernier_element
#> [1] "youpi"
#>
#> $nb_elements
#> [1] 3
<- function(liste) {
description_liste list(
premier_element = liste[[1]],
dernier_element = liste[[length(liste)]],
nb_elements = length(liste)
) }
16.5.3 Tableaux de données
Exercice 3.1
Créer le tableau df
suivant :
<- tribble(
df ~fruit, ~poids, ~couleur,
"Pomme", 154, "vert",
"Pomme", 167, "vert",
"Citron", 92, "jaune"
)
À l’aide de l’opérateur $
, sélectionner la colonne fruit
de df
.
$fruit df
Faire de même avec l’opérateur [[]]
.
"fruit"]] df[[
À l’aide de l’opérateur [[]]
et de la fonction str_to_upper()
de stringr
, transformer la colonne fruit
en passant ses valeurs en majuscules.
library(stringr)
"fruit"]] <- str_to_upper(df[["fruit"]]) df[[
Créer une fonction colonne_maj
qui prend en argument un tableau de données d
et un nom de colonne colonne
, et retourne le tableau avec la colonne correspondante convertie en majuscules. Vérifier avec :
colonne_maj(df, "couleur")
#> # A tibble: 3 × 3
#> fruit poids couleur
#> <chr> <dbl> <chr>
#> 1 Pomme 154 VERT
#> 2 Pomme 167 VERT
#> 3 Citron 92 JAUNE
<- function(d, colonne) {
colonne_maj <- str_to_upper(d[[colonne]])
d[[colonne]]
d }
Exercice 3.2
Créer le tableau df
suivant :
<- tribble(
df ~fruit, ~poids, ~couleur,
"Pomme", 154, "vert",
"Pomme", 167, "vert",
"Citron", 92, "jaune"
)
À l’aide de l’opérateur [,]
, sélectionner :
- les citrons
- les pommes et les colonnes
fruit
etcouleur
- la première colonne des lignes ayant un poids inférieur à 100
$fruit == "Citron",]
df[df$fruit == "Pomme", c("fruit", "couleur")]
df[df$poids < 100, 1] df[df
Créer une fonction filtre_valeur()
qui prend un seul argument nommé valeur
et retourne les lignes de df
pour lesquelles la colonne fruit
vaut valeur
. Vérifier avec :
filtre_valeur("Pomme")
#> # A tibble: 2 × 3
#> fruit poids couleur
#> <chr> <dbl> <chr>
#> 1 Pomme 154 vert
#> 2 Pomme 167 vert
<- function(valeur) {
filtre_valeur $fruit == valeur,]
df[df }
Modifier la fonction pour qu’elle accepte également un argument d
contenant le tableau à filtrer. Vérifier avec :
filtre_valeur(df, "Pomme")
#> # A tibble: 2 × 3
#> fruit poids couleur
#> <chr> <dbl> <chr>
#> 1 Pomme 154 vert
#> 2 Pomme 167 vert
<- function(d, valeur) {
filtre_valeur $fruit == valeur,]
d[d }
Modifier à nouveau la fonction pour qu’elle accepte aussi un argument colonne
qui contient le nom de la colonne à utiliser pour filtrer les lignes. Vérifier avec :
filtre_valeur(df, colonne = "couleur", valeur = "jaune")
#> # A tibble: 1 × 3
#> fruit poids couleur
#> <chr> <dbl> <chr>
#> 1 Citron 92 jaune
<- function(d, colonne, valeur) {
filtre_valeur == valeur,]
d[d[colonne] }
Vérifier que cette fonction marche aussi sur un autre jeu de données :
library(questionr)
data(hdv2003)
filtre_valeur(hdv2003, "sexe", "Femme")
Exercice 3.3
Reprendre le tableau df
des exercices précédents :
<- tribble(
df ~fruit, ~poids, ~couleur,
"Pomme", 154, "vert",
"Pomme", 167, "vert",
"Citron", 92, "jaune"
)
À l’aide de l’opérateur [,]
, effectuer les opérations suivantes :
- Créer une nouvelle colonne
id
avec les valeurs 1, 2, 3 - Remplacer la valeur “jaune” de la variable
couleur
par “jaune citron” - Créer une nouvelle colonne
poids_rec
qui vaut “léger” sipoids
est inférieur à 100, et “lourd” sinon
"id"] <- 1:3
df[, $couleur == "jaune", "couleur"] <- "jaune citron"
df[df
$poids < 100, "poids_rec"] <- "léger"
df[df$poids >= 100, "poids_rec"] <- "lourd"
df[df# Ou bien :
"poids_rec"] <- ifelse(df$poids < 100, "léger", "lourd") df[,
Facultatif : effectuer les mêmes opérations en utilisant les verbes de dplyr
.
<- df %>% mutate(id = 1:3)
df
<- df %>%
df mutate(
couleur = ifelse(couleur == "jaune", "jaune_citron", couleur)
)# Ou bien :
library(forcats)
<- df %>%
df mutate(
couleur = fct_recode(couleur, "jaune_citron" = "jaune")
)
<- df %>%
df mutate(
poids_rec = ifelse(poids < 100, "léger", "lourd")
)
Il en existe d’autres, comme
complex
ouraw
, mais qui sont moins fréquemment utilisés.↩︎Si on veut ajouter un élément
NULL
à une liste, il faut utiliser les crochets simples avec la syntaxeliste["foo"] <- list(NULL)
.↩︎Attention, comme pour les listes, à ne pas utiliser
[[]]
avec un argument de longueur supérieur à 1, car cela mène soit à des erreurs soit à des résultats contre-intuitifs.↩︎