x <- c(1, 3, 8)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 5La 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.0Une 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 :
x <- c("Pomme", "Poire")
seq_along(x)
#> [1] 1 2
y <- runif(10)
seq_along(y)
#> [1] 1 2 3 4 5 6 7 8 9 10Enfin, 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 4Si on souhaite connaître le nombre d’éléments d’un vecteur, on peut utiliser la fonction length().
v <- rep(1:4, 2)
length(v)
#> [1] 8Il 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.
x <- c(e1 = 1, e2 = 3, e3 = 8)
x
#> e1 e2 e3
#> 1 3 8On 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 816.1.3 Types de vecteurs
On peut déterminer le type d’un vecteur avec l’instruction typeof.
x <- c(1, 3, 8)
typeof(x)
#> [1] "double"
y <- c("foo", "bar", "baz")
typeof(y)
#> [1] "character"
z <- c(TRUE, FALSE, FALSE)
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.
x <- c(1L, 3L, 8L)
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.
x <- c(1, 3, 8)
is.numeric(x)
#> [1] TRUE
x > 2
#> [1] FALSE TRUE TRUE
is.logical(x > 2)
#> [1] TRUE
y <- c("foo", "bar", "baz")
is.character(y)
#> [1] TRUEPetite 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().
fac <- factor(c("rouge", "vert", "rouge"))
is.character(fac)
#> [1] FALSE
is.factor(fac)
#> [1] TRUETous 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 0Si 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] 2On peut donc appliquer sum() à un test, et on obtiendra le nombre de valeurs pour lesquelles le test est vrai.
x <- c(1, 5, 8, 12, 14)
sum(x > 10)
#> [1] 2Ceci 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.
x <- runif(1000)
sum(x < 0.5)
#> [1] 520Autre raccourci moins utilisé, appliquer mean() au résultat d’un test donne la proportion de valeurs pour lesquelles le test est vrai.
x <- c(1, 5, 8, 12, 14)
mean(x > 10)
#> [1] 0.4
x <- runif(1000)
mean(x < 0.5)
#> [1] 0.498On 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 2316.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)
x <- c(e1 = 1, e2 = 2, e3 = 8, e4 = 12)
x[c(1, 4)]
#> e1 e4
#> 1 12
x[c("e2", "e4")]
#> e2 e4
#> 2 12
x[x < 10]
#> e1 e2 e3
#> 1 2 8Si on fournit à [] un ou plusieurs nombres négatifs, les valeurs correspondantes seront supprimées plutôt que sélectionnées.
x[-1]
#> e2 e3 e4
#> 2 8 12
x[c(-2, -4)]
#> e1 e3
#> 1 8Si 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
#> 1216.1.5 Modification
Utilisé conjointement avec l’opérateur d’assignation <-, l’opérateur [] permet de remplacer des éléments.
x <- c(e1 = 1, e2 = 2, e3 = 8, e4 = 12)
x[1] <- -1000
x
#> e1 e2 e3 e4
#> -1000 2 8 12
x["e2"] <- 0
x
#> e1 e2 e3 e4
#> -1000 0 8 12
x[x > 10] <- NA
x
#> e1 e2 e3 e4
#> -1000 0 8 NAUtilisé 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.
x[] <- 3
x
#> e1 e2 e3 e4
#> 3 3 3 316.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.
liste <- list(nombre = 1, char = "foo", vecteur = c("Pomme", "Citron"))
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.
liste <- list(
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.06587635 0.08858196 0.45042813 0.97029249 0.25297611 0.15934176
#> [7] 0.37023902 0.85475044 0.10562688 0.07757847Dans 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.0659 0.0886 0.4504 0.9703 0.253 ...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”.
liste <- list(e1 = 1:3, e2 = "Chihuhua")
liste2 <- list(liste, nouveau = 100)
str(liste2)
#> List of 2
#> $ :List of 2
#> ..$ e1: int [1:3] 1 2 3
#> ..$ e2: chr "Chihuhua"
#> $ nouveau: num 100Il faut à la place utiliser c(), comme pour les vecteurs.
liste3 <- c(liste, nouveau = 100)
str(liste3)
#> List of 3
#> $ e1 : int [1:3] 1 2 3
#> $ e2 : chr "Chihuhua"
#> $ nouveau: num 100c() permet aussi de “concaténer” deux listes existantes en une seule.
liste1 <- list(a = 1, b = 2)
liste2 <- list(x = 3, y = 4)
c(liste1, liste2)
#> $a
#> [1] 1
#>
#> $b
#> [1] 2
#>
#> $x
#> [1] 3
#>
#> $y
#> [1] 416.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 :
liste <- list(1:5, "foo", c("Pomme", "Citron"))
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 :
liste[1]
#> [[1]]
#> [1] 1 2 3 4 5On notera que le résultat est une liste à un seul élément.
Si on utilise les crochets doubles :
liste[[1]]
#> [1] 1 2 3 4 5On 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.
liste[c(1, 2)]
#> [[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.
liste <- list(nombre = 1, char = "foo", vecteur = c("Pomme", "Citron"))
liste[c("nombre", "char")]
#> $nombre
#> [1] 1
#>
#> $char
#> [1] "foo"
liste[["vecteur"]]
#> [1] "Pomme" "Citron"On peut aussi utiliser l’opérateur $, qui équivaut à [[]] :
liste$vecteur
#> [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.
liste <- list(nombre = 1:5, char = "foo", vecteur = c("Pomme", "Citron"))
liste["nombre"] <- "first"
liste
#> $nombre
#> [1] "first"
#>
#> $char
#> [1] "foo"
#>
#> $vecteur
#> [1] "Pomme" "Citron"liste[c(1, 3)] <- 0
liste
#> $nombre
#> [1] 0
#>
#> $char
#> [1] "foo"
#>
#> $vecteur
#> [1] 0Attention à 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 NULL2.
liste <- list(nombre = 1:5, char = "foo", vecteur = c("Pomme", "Citron"))
liste$char <- NULL
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.
indicateurs <- function(x) {
list(
moyenne = mean(x),
variance = var(x)
)
}
x <- 1:10
res <- indicateurs(x)
res$moyenne
#> [1] 5.5
res$variance
#> [1] 9.166667On 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().
files <- list.files(pattern = "*.csv")
dfs <- purrr::map(files, read_csv)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() :
df <- data.frame(
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.
df_trib <- tribble(
~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().
df_tib <- as_tibble(df)
df_tib
#> # A tibble: 3 × 3
#> fruit poids couleur
#> <chr> <dbl> <chr>
#> 1 Pomme 154 vert
#> 2 Pomme 167 vert
#> 3 Citron 92 jaune16.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 jaune16.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 $.
df$fruit
#> [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.
df[["fruit"]]
#> [1] "Pomme" "Pomme" "Citron"
df[[2]]
#> [1] 154 167 92On 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 verttail(df, 1)
#> fruit poids couleur
#> fruit3 Citron 92 jauneOn 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"
df[c(1, 3), "poids"]
#> [1] 154 92# Toutes les lignes et colonnes "poids" et "fruit"
df[, c("poids", "fruit")]
#> poids fruit
#> fruit1 154 Pomme
#> fruit2 167 Pomme
#> fruit3 92 Citron# Lignes pour lesquelles poids > 150, et toutes les colonnes
df[df$poids > 150, ]
#> fruit poids couleur
#> fruit1 Pomme 154 vert
#> fruit2 Pomme 167 vertlibrary(stringr)
# Colonnes dont le nom contient un "o", et toutes les lignes
df[, str_detect(names(df), "o")]
#> poids couleur
#> fruit1 154 vert
#> fruit2 167 vert
#> fruit3 92 jauneAttention, 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.
df[, "fruit"]
#> [1] "Pomme" "Pomme" "Citron"df_tib[, "fruit"]
#> # A tibble: 3 × 1
#> fruit
#> <chr>
#> 1 Pomme
#> 2 Pomme
#> 3 CitronCette 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
df[["poids_kg"]] <- df$poids / 1000
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"
df[df$fruit == "Citron", "fruit"] <- "Agrume"
df
#> fruit poids couleur poids_kg
#> fruit1 Pomme 154 vert 0.154
#> fruit2 Pomme 167 vert 0.167
#> fruit3 Agrume 92 jaune 0.092Pour 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.
v <- seq(10, 20, by = 2)Sélectionner les 3 premières valeurs de v.
v[1:3]
head(v, 3)Sélectionner toutes les valeurs de v strictement inférieures à 15.
v[v < 15]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] 20derniere <- function(v) {
v[length(v)]
}
# Ou bien
derniere <- function(v) {
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 18sauf_derniere <- function(v) {
v[-length(v)]
}
# Ou bien
sauf_derniere <- function(v) {
head(v, -1)
}Exercice 1.2
Soit le vecteur vn suivant :
vn <- c(val1 = 10, val2 = 0, val3 = 14)Sélectionner les valeurs nommées “val1” et “val3”.
vn[c("val1", "val3")]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 14select_noms <- function(v, 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 14sauf_nom <- function(v, nom) {
v[names(v) != nom]
}Facultatif : comparer les résultats des deux instructions suivantes.
vn["val1"]
vn[["val1"]]Exercice 1.3
Soit les vecteurs x et y suivants :
x <- c(1, NA, 3, 4, NA)
y <- c(10, 20, 30, 40, 50)À l’aide de l’opérateur [], sélectionner uniquement les valeurs NA de x.
x[is.na(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).
y[is.na(x)]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.
x[is.na(x)] <- y[is.na(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
vqui n’ont pas été converties correctement, c’est-à-dire celles qui ne valaient pasNAdansvmais valentNAaprès la conversion.
Vérifier avec :
x <- c("igloo", "20", NA, "3.5", "4,8")
problemes_conversion(x)
#> Warning in problemes_conversion(x): NAs introduced by coercion
#> [1] "igloo" "4,8"problemes_conversion <- function(v) {
conv <- as.numeric(v)
v[!is.na(v) & is.na(conv)]
}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
liste <- list(1, "oui", 10:12)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.
liste <- c(liste, chat = "Ronron")Modifier l’élément chat pour lui donner la valeur “Ronpchi”.
liste$chat <- "Ronpchi"
# Ou bien
liste["chat"] <- "Ronpchi"Supprimer l’élément vec de liste.
liste$vec <- NULL
# Ou bien
liste["vec"] <- NULLExercice 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.
extremes <- function(x) {
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).
v <- runif(10)
res <- extremes(v)
res$max - res$minExercice 2.3
Soit la liste suivante :
liste <- list(1:3, runif(5), "youpi")Sélectionner la sous liste composée des éléments 1 et 3 de liste.
liste[c(1, 3)]Sélectionner la sous-liste composée du premier élément de liste.
liste[1]Sélectionner le contenu du premier élément de liste.
liste[[1]]En enchaînant deux opérations de sélection, sélectionner le deuxième élément du premier élément de liste.
liste[[1]][2]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 :
liste <- list(1:3, runif(5), "youpi")
description_liste(liste)
#> $premier_element
#> [1] 1 2 3
#>
#> $dernier_element
#> [1] "youpi"
#>
#> $nb_elements
#> [1] 3description_liste <- function(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 :
df <- tribble(
~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.
df$fruitFaire de même avec l’opérateur [[]].
df[["fruit"]]À 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)
df[["fruit"]] <- str_to_upper(df[["fruit"]])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 JAUNEcolonne_maj <- function(d, colonne) {
d[[colonne]] <- str_to_upper(d[[colonne]])
d
}Exercice 3.2
Créer le tableau df suivant :
df <- tribble(
~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
fruitetcouleur - la première colonne des lignes ayant un poids inférieur à 100
df[df$fruit == "Citron",]
df[df$fruit == "Pomme", c("fruit", "couleur")]
df[df$poids < 100, 1]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 vertfiltre_valeur <- function(valeur) {
df[df$fruit == valeur,]
}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 vertfiltre_valeur <- function(d, valeur) {
d[d$fruit == valeur,]
}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 jaunefiltre_valeur <- function(d, colonne, valeur) {
d[d[colonne] == valeur,]
}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 :
df <- tribble(
~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
idavec les valeurs 1, 2, 3 - Remplacer la valeur “jaune” de la variable
couleurpar “jaune citron” - Créer une nouvelle colonne
poids_recqui vaut “léger” sipoidsest inférieur à 100, et “lourd” sinon
df[, "id"] <- 1:3
df[df$couleur == "jaune", "couleur"] <- "jaune citron"
df[df$poids < 100, "poids_rec"] <- "léger"
df[df$poids >= 100, "poids_rec"] <- "lourd"
# Ou bien :
df[, "poids_rec"] <- ifelse(df$poids < 100, "léger", "lourd")Facultatif : effectuer les mêmes opérations en utilisant les verbes de dplyr.
df <- df %>% mutate(id = 1:3)
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
complexouraw, 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.↩︎