Trucs et astuces en R

R

Trucs et astuces en R.

Auteur·rice·s
Affiliations

Nassab ABDALLAH

Damien EUZENAT

Date de publication

3 octobre 2024

Voici quelques trucs et astuces utiles à connaître sur R, issus en partie des livres The R Inferno et Advanced R, à consulter pour plus de détails.

1 Effets des calculs en virgule flottante

sqrt(2) ^ 2 == 2
[1] FALSE
1 / 49 * 49 == 1
[1] FALSE
.1 == .3 / 3
[1] FALSE
seq(0, 1, by=.1) == .3
 [1] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
unique(c(.3, .4 - .1, .5 - .2, .6 - .3, .7 - .4))
[1] 0.3 0.3 0.3

2 Les types d’objet

# En savoir plus sur ses données
data("cars")
typeof(cars)
[1] "list"
class(cars)
[1] "data.frame"
mode(cars)
[1] "list"
str(cars)
'data.frame':   50 obs. of  2 variables:
 $ speed: num  4 4 7 7 8 9 10 10 10 11 ...
 $ dist : num  2 10 4 22 16 10 18 26 34 17 ...
dim(cars)
[1] 50  2
c(is.data.frame(cars), is.list(cars), is.vector(cars), is.array(cars))
[1]  TRUE  TRUE FALSE FALSE

Notation L

# Notation L : imposer à R de créer un nombre sous format "integer", pour des raisons d'efficacité
class(1)
[1] "numeric"
class(1L)
[1] "integer"

3 Les affectations

# Importance des espaces
x<-3
# S'agit-il de ?
x <- 3
# Ou de ?
x < -3
[1] FALSE
# Il s'agit du premier cas !
x
[1] 3
# Les {} permettent de réaliser une affectation (<-) au sein d'un traitement plus complexe !
# Exemple ici : sommer les positions paires multipliées par 2 et les positions impaires multipliées par 3
sum({x <- 1:6 ; x[1:length(x) %% 2 == 0] <- x[1:length(x) %% 2 == 0] * 2 ; x[1:length(x) %% 2 != 0] <- x[1:length(x) %% 2 != 0] * 3 ; x})
[1] 51
# Afficher ou non la valeur de l'objet affecté pendant l'affectation
x <- pi
(x <- pi)
[1] 3.141593
# Comparaisons multiples
x <- 0.5
# Correct
0 < x & x < 1
# Incorrect
0 < x < 1
Error: <text>:6:7: unexpected '<'
5: # Incorrect
6: 0 < x <
         ^
# Ne pas confondre !
seq(0:10)
 [1]  1  2  3  4  5  6  7  8  9 10 11
0:10
 [1]  0  1  2  3  4  5  6  7  8  9 10
seq(0, 10)
 [1]  0  1  2  3  4  5  6  7  8  9 10
# Privilégier seq_len à : lorsqu'il y a un risque de 0
# Fonctionnement identique
n <- 5
1:n
[1] 1 2 3 4 5
seq_len(n)
[1] 1 2 3 4 5
# Problème avec les 0
n <- 0
1:n
[1] 1 0
seq_len(n)
integer(0)
# Préférer seq_along à : au cas où la taille de l'objet est 0
x <- c() ;length(x)
[1] 0
for(i in 1:length(x)) print(i)
[1] 1
[1] 0
for(i in seq_along(x)) print(i)
for(i in seq(along=x)) print(i)
# Différences seq, seq_len et seq_along

# seq : créé une séquence d'éléments successifs, espacés éventuellement d'un pas
seq(from = 1, to = 10, by = 2)
[1] 1 3 5 7 9
seq(1, 10, 2)
[1] 1 3 5 7 9
# seq_len : créé une séquence d'éléments de 1 jusqu'au paramètre de la fonction (supposé positif)
seq_len(10)
 [1]  1  2  3  4  5  6  7  8  9 10
# seq_along : créé une séquence d'éléments de 1 jusqu'au nombre d'éléments du vecteur
seq_along(c(7,9,1,0))
[1] 1 2 3 4
# seq se comporte comme seq_along si le vecteur comprend plusieurs éléments, et comme seq_len s'il comprend un seul élément

# Plusieurs éléments
vecteur <- c(10, 20, 30)
seq_along(vecteur)
[1] 1 2 3
seq_len(vecteur)
 [1]  1  2  3  4  5  6  7  8  9 10
seq(vecteur)
[1] 1 2 3
# Un seul élement
vecteur <- 10
seq_along(vecteur)
[1] 1
seq_len(vecteur)
 [1]  1  2  3  4  5  6  7  8  9 10
seq(vecteur)
 [1]  1  2  3  4  5  6  7  8  9 10
# Equivalent des list-comprehensions de type Python en R
# Somme des entiers de 1 à 1 000 multiples de 3 ou 5, en une ligne !
# 4 manières de faire
sum({l <- 1:1000 ; l[l %% 3 == 0 | l %% 5 == 0]})
[1] 234168
sum({l <- 1:1000 ; l[seq_along(l) %% 3 == 0 | seq_along(l) %% 5 == 0]})
[1] 234168
sum(l<-(1:1000)[l %% 3 == 0 | l %% 5 == 0])
[1] 234168
sum((1:1000)[(1:1000 %% 3) == 0 | (1:1000 %% 5) == 0])
[1] 234168
# Opérations mathématiques
# Infini
1/0
[1] Inf
# Nan = not a number
0/0
[1] NaN
log(-1)
[1] NaN

4 Principe de vectorisation

La fonction s’applique à tous les éléments du vecteur !!

# Exemple de vecteur
valeurs <- c(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
valeurs <- 1:10
# Valeurs au carré
valeurs ** 2
 [1]   1   4   9  16  25  36  49  64  81 100
# logarithme des valeurs
log(valeurs)
 [1] 0.0000000 0.6931472 1.0986123 1.3862944 1.6094379 1.7917595 1.9459101
 [8] 2.0794415 2.1972246 2.3025851
# Maximum des valeurs
max(valeurs)
[1] 10
# Limite des valeurs
range(valeurs)
[1]  1 10
# Correct
mean(valeurs)
[1] 5.5
# Correct
mean(c(1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
[1] 5.5
# Inattendu
mean(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
[1] 1
# Attention aux parenthèses !
n <- 10
# Inattendu ! Correspond à (1-1, 2-1, 3-1, ..., 10-1)
1:n-1
 [1] 0 1 2 3 4 5 6 7 8 9
# Correct
1:(n-1)
[1] 1 2 3 4 5 6 7 8 9
# Minimum ou maximum élément par élément
x1 <- c(1, 2, 5, 6, 8)
x2 <- c(4, 0, 7, 2, 1)
# Minimum : 2 possibilités
pmin(x1, x2)
[1] 1 0 5 2 1
mapply(min, x1, x2)
[1] 1 0 5 2 1
# Maximum : 2 possibilités
pmax(x1, x2)
[1] 4 2 7 6 8
mapply(max, x1, x2)
[1] 4 2 7 6 8
# Calculer (x - min) / (max - min) pour chaque élément x d'un vecteur
x <- c(1,2,3)
sapply(x, function(xi, mn, mx) {(xi - mn) / (mx - mn)}, mn = min(x), mx = max(x))
[1] 0.0 0.5 1.0

5 Principe de coercion

Dans les comparaisons, les objets R sont successivement convertis en logical, integer, numeric, complex et character, jusqu’à ce que la comparaison puisse se faire.

# TRUE / FALSE est transformé en integer (1 / 0)
TRUE == 1
[1] TRUE
TRUE == 2
[1] FALSE
FALSE == 0
[1] TRUE
# TRUE est convertit en character
TRUE == "1"
[1] FALSE
# 5 est convertit en character
5 < '7'
[1] TRUE
# 50 est convertit en character
50 < '7'
[1] TRUE

6 Principe de recycling

# Fonctionne sans soulever de message d'erreur
x <- c(1, 2, 3)
y <- c(1, 2, 3, 4, 5, 6)
x + y
[1] 2 4 6 5 7 9
# Fonctionne, mais Warning
x <- c(1, 2, 3)
y <- c(1, 2, 3, 4, 5, 6, 7)
x + y
Warning in x + y: longer object length is not a multiple of shorter object
length
[1] 2 4 6 5 7 9 8

7 Opérateurs de contrôle

# Conditions if / else
x <- 1
# Correct
if (identical(x, 1)) {
print("x est égal à 1")
} else {
print("x est différent de 1")
}
[1] "x est égal à 1"
# Correct
if (identical(x, 1)) {print("x est égal à 1")
} else {print("x est différent de 1")}
[1] "x est égal à 1"
# Correct
if (identical(x, 1)) print("x est égal à 1") else
print("x est différent de 1")
[1] "x est égal à 1"
# Correct
if (identical(x, 1)) print("x est égal à 1") else print("x est différent de 1")
[1] "x est égal à 1"
# Incorrect
if (identical(x, 1)) print("x est égal à 1")
else print("x est différent de 1")
Error: <text>:3:1: unexpected 'else'
2: if (identical(x, 1)) print("x est égal à 1")
3: else
   ^
# Switch
# Fonctions et conditions if / else
condition <- function(langue = "Français") {
if (langue == "Français") {
"salut"
} else if (langue == "Anglais") {
"hello"
} else if (langue == "Russe") {
"привет"
} else {
stop("salut")
}
}
condition("Anglais")
[1] "hello"
# Autre possibilité avec switch, mais à réserver aux variables caractères !
condition <- function(langue = "Français") {
switch(langue,
Français = "salut",
Anglais = "hello",
Russe = "привет",
stop("salut")
)
}
condition("Français")
[1] "salut"
# Penser au print
for(i in 1:5) i
for(i in 1:5) print(i)
[1] 1
[1] 2
[1] 3
[1] 4
[1] 5
# Différences print et cat
cat("Salut !\nComment vas-tu ?")
Salut !
Comment vas-tu ?
print("Salut !\nComment vas-tu ?")
[1] "Salut !\nComment vas-tu ?"

8 Les valeurs manquantes

# Transformer les valeurs manquantes en 0
x <- data.frame(X1 = c(1, NA, 2), X2 = c(NA, NA, 3))
x
  X1 X2
1  1 NA
2 NA NA
3  2  3
x[is.na(x)] <- 0
x
  X1 X2
1  1  0
2  0  0
3  2  3
# Pour les valeurs manquantes, toujours utiliser is.na et non ==
NA == c(3, 1, 3, NA)
[1] NA NA NA NA
is.na(c(3, 1, 3, NA))
[1] FALSE FALSE FALSE  TRUE
# Utiliser %in% et non == pour vérifier une appartenance à plusieurs variables
x1 <- 1:6
x1 == c(1, 5)
[1]  TRUE FALSE FALSE FALSE FALSE FALSE
x1 %in% c(1, 5)
[1]  TRUE FALSE FALSE FALSE  TRUE FALSE
# Sélection de données en cas de valeurs manquantes
xna <- c(1, NA, 3, 2, 4, 2)
# Avec ce code, les NA sont conservés
xna[xna == 2]
[1] NA  2  2
# Code sans les NA
xna[!is.na(xna) & xna == 2]
[1] 2 2
# Code compact sans les NA
xna[which(xna == 2)]
[1] 2 2
# Incidence des NA dans le calcul de statistiques !
# Besoin de l'instruction na.rm = TRUE pour en supprimer l'incidence
x <- c(1, 5, 6, NA, 8)
mean(x)
[1] NA
mean(x, na.rm = TRUE)
[1] 5
max(x)
[1] NA
max(x, na.rm = TRUE)
[1] 8
# Une moyenne de NA donne NaN (Not a Number) avec na.rm = TRUE !
mean(c(NA, NA, NA), na.rm = TRUE)
[1] NaN
# Et donne NA avec na.rm = FALSE !
mean(c(NA, NA, NA), na.rm = FALSE)
[1] NA

9 Sélection d’éléments

# Le 1er élément est numéroté 1 et non 0 comme dans Python
x <- 1:3
x[c(0, 4)] <- c(-1, 9)
# Marche !!
for(i in 0:3) print(x[i])
numeric(0)
[1] 1
[1] 2
[1] 3
# Cas où les noms ne sont pas uniques
x <- c(a = 1, b = 2, a = 3)
x["a"]
a 
1 
x[names(x) %in% "a"]
a a 
1 3 
# Options drop = FALSE
x <- data.frame(X1 = c(1,2), X2 = c(3,4), x3 = c(5,6))
is.data.frame(x[, c(1,2)])
[1] TRUE
is.data.frame(x[, c(1)])
[1] FALSE
is.data.frame(x[, c(1), drop = FALSE])
[1] TRUE
# Fonction subset
x <- data.frame(x1 = c(1,2), x2 = c(3,4), x3 = c(5,6))
subset(x, select = x1)
  x1
1  1
2  2
subset(x, select = -x1)
  x2 x3
1  3  5
2  4  6
# Correct
subset(x, x1 == 1)
  x1 x2 x3
1  1  3  5
# Incorrect
subset(x, x1 = 1)
  x1 x2 x3
1  1  3  5
2  2  4  6
# Référence à des noms de colonne non usuels
df <- data.frame(x = rnorm(5), y = runif(5))
names(df) <- 1:2

# Correct
df$`1`

# Incorrect
df$1
Error: <text>:9:4: unexpected numeric constant
8: # Incorrect
9: df$1
      ^
# Utilisation de assign pour assigner une valeur à un objet en le désignant sous forme caractère
assign('objet', 3:5)
objet
[1] 3 4 5
for(i in 1:5) assign(paste('objet', i, sep = '_'), i)
objet_5
[1] 5

10 Les listes

# Créer une liste
liste <- list()
liste
list()
# Pré-allouer une liste de 5 éléments
liste <- vector(mode = "list", 5)
liste
[[1]]
NULL

[[2]]
NULL

[[3]]
NULL

[[4]]
NULL

[[5]]
NULL
# Ajouter un élément à une liste
liste <- list(1, 2)
# Il faut passer par cette syntaxe
liste <- c(liste, list(3))
liste
[[1]]
[1] 1

[[2]]
[1] 2

[[3]]
[1] 3
# Trier une liste
liste <- list("2" = 2, "0" = 0, "1" = 1)
liste
$`2`
[1] 2

$`0`
[1] 0

$`1`
[1] 1
liste[order(names(liste))]
$`0`
[1] 0

$`1`
[1] 1

$`2`
[1] 2
# Extraction de listes
liste <- list(nombres = 1:5, lettres = letters[1:5])
liste$nombres
[1] 1 2 3 4 5
# On veut faire appel à un élement de la liste à partir d'une variable extérieure
var <- "nombres"
# Ne marche pas, car nombres est en caractère
liste$var
NULL
# Fonctionne
liste[[var]]
[1] 1 2 3 4 5
# Sélection dans une liste
liste <- list(nombres = 1:5, lettres = letters[1:5])
liste["nombres"]
$nombres
[1] 1 2 3 4 5
is.list(liste["nombres"])
[1] TRUE
liste[["nombres"]]
[1] 1 2 3 4 5
is.list(liste[["nombres"]])
[1] FALSE
# Suppression des éléments d'une liste
liste <- list(nombres = 1:5, lettres = letters[1:5])
liste[-1]
$lettres
[1] "a" "b" "c" "d" "e"
liste["nombres"] <- NULL
liste
$lettres
[1] "a" "b" "c" "d" "e"
# Dernier élément de chaque composante d'une liste
liste <- list(1:26, letters)
lapply(liste, tail, 1)
[[1]]
[1] 26

[[2]]
[1] "z"

11 Différences entre sort(), order(), rank()

sort() trie un vecteur par ordre croissant. rank() donne le rang de chaque élément du vecteur, le plus petit élément se voyant attribué le rang 1. order() renvoie l’indice du vecteur trié par ordre croissant.

vecteur <- c(5,3,1,2,4)
sort(vecteur)
[1] 1 2 3 4 5
order(vecteur)
[1] 3 4 2 5 1
rank(vecteur)
[1] 5 3 1 2 4

Une autre solution que sort() pour trier un vecteur est :

vecteur <- c(5,3,1,2,4)
vecteur[order(vecteur)]
[1] 1 2 3 4 5

À noter aussi que sort() permet aussi d’exécuter simultanément les fonctions sort() et order(). Ainsi :

vecteur <- c(5,3,1,2,4)
sort(vecteur, index.return = TRUE)
$x
[1] 1 2 3 4 5

$ix
[1] 3 4 2 5 1
# Fonction sort()
sort(vecteur, index.return = TRUE)$x
[1] 1 2 3 4 5
sort(vecteur)
[1] 1 2 3 4 5
# Fonction order()
sort(vecteur, index.return = TRUE)$ix
[1] 3 4 2 5 1
order(vecteur)
[1] 3 4 2 5 1

12 Ramasse-miettes (Garbage Collector)

Si la mémoire vive de l’ordinateur est saturée, on peut essayer de la “purger” en utilisant la fonction gc() de R.

gc()
          used (Mb) gc trigger (Mb) max used (Mb)
Ncells  906045 48.4    1750371 93.5  1750371 93.5
Vcells 1757594 13.5   10146329 77.5  7529342 57.5

13 Fonctions try() et tryCatch() À FAIRE

14 Informations sur R

# Informations sur la session de R
sessionInfo()
Sys.info()
.Platform

# Version de R
R.version.string

# Lien vers le bureau de l'utilisateur
file.path(path.expand('~'), "Desktop")

# Chemin de l'exécutable R est-il installé ?
R.home("bin")

# Où sont installés les packages R
.libPaths()

15 Trucs et astuces divers

# Créer un objet sous forme de code R !
data("mtcars")
dput(mtcars)
structure(list(mpg = c(21, 21, 22.8, 21.4, 18.7, 18.1, 14.3, 
24.4, 22.8, 19.2, 17.8, 16.4, 17.3, 15.2, 10.4, 10.4, 14.7, 32.4, 
30.4, 33.9, 21.5, 15.5, 15.2, 13.3, 19.2, 27.3, 26, 30.4, 15.8, 
19.7, 15, 21.4), cyl = c(6, 6, 4, 6, 8, 6, 8, 4, 4, 6, 6, 8, 
8, 8, 8, 8, 8, 4, 4, 4, 4, 8, 8, 8, 8, 4, 4, 4, 8, 6, 8, 4), 
    disp = c(160, 160, 108, 258, 360, 225, 360, 146.7, 140.8, 
    167.6, 167.6, 275.8, 275.8, 275.8, 472, 460, 440, 78.7, 75.7, 
    71.1, 120.1, 318, 304, 350, 400, 79, 120.3, 95.1, 351, 145, 
    301, 121), hp = c(110, 110, 93, 110, 175, 105, 245, 62, 95, 
    123, 123, 180, 180, 180, 205, 215, 230, 66, 52, 65, 97, 150, 
    150, 245, 175, 66, 91, 113, 264, 175, 335, 109), drat = c(3.9, 
    3.9, 3.85, 3.08, 3.15, 2.76, 3.21, 3.69, 3.92, 3.92, 3.92, 
    3.07, 3.07, 3.07, 2.93, 3, 3.23, 4.08, 4.93, 4.22, 3.7, 2.76, 
    3.15, 3.73, 3.08, 4.08, 4.43, 3.77, 4.22, 3.62, 3.54, 4.11
    ), wt = c(2.62, 2.875, 2.32, 3.215, 3.44, 3.46, 3.57, 3.19, 
    3.15, 3.44, 3.44, 4.07, 3.73, 3.78, 5.25, 5.424, 5.345, 2.2, 
    1.615, 1.835, 2.465, 3.52, 3.435, 3.84, 3.845, 1.935, 2.14, 
    1.513, 3.17, 2.77, 3.57, 2.78), qsec = c(16.46, 17.02, 18.61, 
    19.44, 17.02, 20.22, 15.84, 20, 22.9, 18.3, 18.9, 17.4, 17.6, 
    18, 17.98, 17.82, 17.42, 19.47, 18.52, 19.9, 20.01, 16.87, 
    17.3, 15.41, 17.05, 18.9, 16.7, 16.9, 14.5, 15.5, 14.6, 18.6
    ), vs = c(0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 
    0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1), am = c(1, 
    1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 
    0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1), gear = c(4, 4, 4, 3, 
    3, 3, 3, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 4, 4, 4, 3, 3, 3, 
    3, 3, 4, 5, 5, 5, 5, 5, 4), carb = c(4, 4, 1, 1, 2, 1, 4, 
    2, 2, 4, 4, 3, 3, 3, 4, 4, 4, 1, 2, 1, 1, 2, 2, 4, 2, 1, 
    2, 2, 4, 6, 8, 2)), row.names = c("Mazda RX4", "Mazda RX4 Wag", 
"Datsun 710", "Hornet 4 Drive", "Hornet Sportabout", "Valiant", 
"Duster 360", "Merc 240D", "Merc 230", "Merc 280", "Merc 280C", 
"Merc 450SE", "Merc 450SL", "Merc 450SLC", "Cadillac Fleetwood", 
"Lincoln Continental", "Chrysler Imperial", "Fiat 128", "Honda Civic", 
"Toyota Corolla", "Toyota Corona", "Dodge Challenger", "AMC Javelin", 
"Camaro Z28", "Pontiac Firebird", "Fiat X1-9", "Porsche 914-2", 
"Lotus Europa", "Ford Pantera L", "Ferrari Dino", "Maserati Bora", 
"Volvo 142E"), class = "data.frame")
# En tidyverse, la fonction tribble peut être utile pour créer des bases lignes par lignes
library(tidyverse)
tribble(
  ~x, ~y,
  "a",   1,
  "b",   2
)
# A tibble: 2 × 2
  x         y
  <chr> <dbl>
1 a         1
2 b         2
# Outils de debogage
options(error = recover)
options(error=NULL)
# Définir ses propres opérateurs
# R reconnait le texte entre %% comme un opérateur binaire
# Exemple, simplifier la fonction paste
'%+%' <- function(x, y) { paste(x, y, sep = "") }
"Groupe_" %+% 0:9
 [1] "Groupe_0" "Groupe_1" "Groupe_2" "Groupe_3" "Groupe_4" "Groupe_5"
 [7] "Groupe_6" "Groupe_7" "Groupe_8" "Groupe_9"
# Récupérer les éléments à la diagonale d'un dataframe
diag(as.matrix(mtcars))
 [1]  21.00   6.00 108.00 110.00   3.15   3.46  15.84   1.00   0.00   4.00
[11]   4.00
# S'assurer qu'un objet R existe déjà
exists("mtcars")
[1] TRUE

16 Le pipe sur R

Cet opérateur permet d’enchaîner les opérations et d’avoir un code plus lisible.

Il existe différents types :

  • Le pipe de Rbase : |>

  • Le pipe du tidyverse : %>%

Les deux pipes fonctionnent de la même manière mais diffèrent sur les points suivants :

Thème |> %>%
Placeholder (pour indiquer où placer l’objet)

_

x |> f(y, a = _)

.

x %>% f(y, a = .)

Package Aucun : directement sur R Magrittr (pipe disponible lorsqu’on effectue library(tidyverse)) |
Fonction Impossible Possible
Temps de calcul Rapide Plus lent
Opérateurs |> %>% %<>% %$% %!>% %T>%

Source :

  • https://larmarange.github.io/guide-R/manipulation/pipe.html#le-pipe-natif-de-r

17 Conseils divers

Il est déconseillé de nommer des variables temporaires var, car ce nom désigne la fonction R permettant de calculer une variance.

# Exemple : variance d'un échantillon aléatoire de 1000 observations tiré d'une loi normale centrée réduite
var(rnorm(1000))
[1] 1.014639