3 Les vecteurs

Les vecteurs sont la structure de données fondamentale dans R. Le terme «structure de données» fait référence à la manière dont les données sont stockées et organisées par R. En plus des vecteurs, les quatre structures les plus souvent utilisées sont: les facteurs, les matrices, les listes et les data frames (tableau des données). Chacune de ces structures/objets sera traitée séparément dans les chapitres suivants.

3.1 Les trois types de vecteurs

Les vecteurs stockent une liste ordonnée d’éléments, tous du même type: numérique, caractère ou logique. Un objet dont tous les éléments sont de même type est appelé simple ou atomique (atomic object, en anglais).

Numérique

Imaginons que nous avons interrogé dix personnes au hasard dans la rue et que nous avons relevé pour chacune d’elle sa taille (en centimètre). On peut stocker cette information dans un seul objet à l’aide de la fonction c() qui permet simplement de combiner ces arguments.

taille <- c(167, 192, 173, 174, 172, 167, 171, 185, 163, 170) # mesures en cm
taille
 [1] 167 192 173 174 172 167 171 185 163 170

En réalité, R distingue deux types de numeric: double (ou dbl) pour réelle et integer (ou int) pour entier, mais cette distinction n’a pas beaucoup d’importance. En R, par défaut, un numeric est un double.

Chaîne de caractères

Imaginons que nous avons aussi relevé le nom de chaque personne. Voici comment stocker cette information :

nom <- c("Benjamin", "Hugo", 'Emma', 'Alex', "Tom", "Axel", "Alice", "Martin", "Robin", "Enzo")
nom
 [1] "Benjamin" "Hugo"     "Emma"     "Alex"     "Tom"      "Axel"    
 [7] "Alice"    "Martin"   "Robin"    "Enzo"    

Notez que les chaînes doivent impérativement être entourées de guillemets (" ou ').

Logique

Supposons que nous savons aussi si un individu est fumeur ou pas. Puisqu’il s’agit d’une quantité binaire (vrai ou faux), on peut utiliser un vecteur logique pour stocker cette information:

fum <- c(FALSE, FALSE, TRUE, FALSE, FALSE, TRUE, FALSE, FALSE, FALSE, TRUE)
fum
 [1] FALSE FALSE  TRUE FALSE FALSE  TRUE FALSE FALSE FALSE  TRUE

En R, les valeurs TRUE et FALSE sont les deux constantes logiques utilisées pour représenter le vrai et le faux. Elles peuvent être abrégées en T et F, mais il est recommandé d’utiliser TRUE et FALSE pour éviter les erreurs.

c(T, F)
[1]  TRUE FALSE

3.2 Les carctériqtiques d’un vecteur

Nous pouvons examiner la structure d’un vecteur avec la fonction str(). Cette fonction est essentielle à connaître et peut s’appliquer à n’importe quel objet R (pas uniquement les vecteurs). Elle indique la nature de l’objet ainsi qu’un aperçu de ce qui est stocker dedans.

str(taille)
str(nom)
str(fum)
 num [1:10] 167 192 173 174 172 167 171 185 163 170
 chr [1:10] "Benjamin" "Hugo" "Emma" "Alex" "Tom" "Axel" "Alice" "Martin" ...
 logi [1:10] FALSE FALSE TRUE FALSE FALSE TRUE ...

Dans la sortie ci-dessus, on peut voir que la fonction str() affiche la nature de chaque vecteur (num pour numérique, chr pour chaîne de caractères, logi pour logique), ainsi sa taille ou sa longueur ([1:10]), et un aperçu de son contenu, souvent limité aux premiers éléments.

Pour mieux visualiser à quoi correspond les valeurs stockées dans un vecteur, on peut attribuer des noms aux différents éléments/composantes. On obtient ainsi ce qu’on appelle un vecteur nommé. Voici un exemple.

person1 <- c(22, 1.84, 1348)
names(person1) <- c("age", "height", "zipcode")
person1
    age  height zipcode 
  22.00    1.84 1348.00 

Notez que les informations affichées par la fonction str() s’adaptent aux caractéristiques de l’objet considéré. Ainsi, pour person1, par exemple, on obtient :

str(person1)
 Named num [1:3] 22 1.84 1348
 - attr(*, "names")= chr [1:3] "age" "height" "zipcode"

On peut aussi nommer les éléments d’un vecteur au moment de sa création, comme ceci.

person1 <- c(age = 22, height = 1.84, zipcode = 1348)

Sachez qu’il existe d’autres fonctions qui aident à comprendre la nature et les caractéristiques d’un objet, telles que length(), typeof() ou encore attributes().

names(person1)   # noms des composants
length(person1)  # nombre de composants
typeof(person1)  # type interne de l'objet, càd son mode de stockage (integer, double, character, logical, ...).
attributes(person1) #  les attributs supplémentaires d'un objet, comme noms, dimensions ...
[1] "age"     "height"  "zipcode"
[1] 3
[1] "double"
$names
[1] "age"     "height"  "zipcode"

Il est intéressant de s’arrêter ici sur la fonction names(), qui montre bien qu’une même fonction peut avoir plusieurs usages en R. En effet, quand on écrit names(<objet>), on utilise la fonction pour consulter les noms associés à l’objet, mais quand on écrit names(<objet>) <- <valeur>, on utilise la même fonction pour attribuer ou changer ces noms. names() n’est pas unique dans son genre en R. D’autres fonctions possèdent la même propriété bidirectionnelle (lecture-écriture), permettant à la fois de consulter et de modifier les attributs d’un objet. C’est notamment le cas des fonctions length(), dim() et levels(), que nous verrons plus loin.

3.3 R est un langage vectoriel

Dans R, la majorité des opérations sont conçues pour s’appliquer directement à des vecteurs, ce qui évite d’avoir à les exécuter explicitement sur chaque élément individuellement. Voici quelques exemples.

taille + c(1, 2, 3, 4, 5, 6, 7, 8, 9, 40)
taille^2
log(taille)
poids <- c(86, 74, 83, 50, 78, 66, 66, 51, 50, 55) # mesures en Kg
imc <- poids / (taille / 100)^2    # Calcul de l'indice de masse corporelle (Kg/m^2)
imc
 [1] 168 194 176 178 177 173 178 193 172 210
 [1] 27889 36864 29929 30276 29584 27889 29241 34225 26569 28900
 [1] 5.12 5.26 5.15 5.16 5.15 5.12 5.14 5.22 5.09 5.14
 [1] 30.8 20.1 27.7 16.5 26.4 23.7 22.6 14.9 18.8 19.0

Si les vecteurs intervenant dans une opération ne sont pas de mêmes longueurs, la règle de recyclage s’applique. Cela signifie que les éléments du vecteur le plus court sont répétés (ou “recyclés”) pour correspondre à la longueur du vecteur le plus long. Voici deux exemples.

taille + 20
taille + c(0, 20)
 [1] 187 212 193 194 192 187 191 205 183 190
 [1] 167 212 173 194 172 187 171 205 163 190

C’est comme si l’on avait écrit taille + c(20, 20, 20, 20, 20, 20, 20, 20, 20, 20) dans le premier cas, et taille + c(0, 20, 0, 20, 0, 20, 0, 20, 0, 20) dans le second.

Si la longueur du vecteur le plus long n’est pas un multiple du plus court, un Warning est donné :

taille + c(0, 20, 1)
Warning in taille + c(0, 20, 1): longer object length is not a multiple of
shorter object length
 [1] 167 212 174 174 192 168 171 205 164 170

Cette sortie est la même que celle qu’on obtiendrait si on tape taille + c(0, 20, 1, 0, 20, 1, 0, 20, 1, 0).

3.4 Combiner des vecteurs

Imaginons que nous avons interrogé 10 autres personnes et récolté leurs tailles. Dans ce cas, il est naturel de vouloir combiner les deux échantillons dans un seul et même vecteur. Cette opération peut se faire aussi à l’aide de la fonction c().

taille1 <- taille
taille2 <- c(126, 177, 159, 143, 161, 180, 169, 159, 185, 160)
Taille <- c(taille1, taille2)
Taille
 [1] 167 192 173 174 172 167 171 185 163 170 126 177 159 143 161 180 169 159 185
[20] 160

La même opération peut être effectuée comme suite.

Taille <- c(taille, 126, 177, 159, 143, 161, 180, 169, 159, 185, 160)

Note
R distingue les majuscules et les minuscules. Ainsi, taille, Taille et taiLle sont trois objets différents.

Conversion de types

Rappelons-nous que tous les éléments d’un vecteur doivent être du même type. Mais que se passe-t-il si nous combinons des objets de type différent ? Examinons les exemples suivants.

c(112, TRUE, FALSE)
c(112, "Ben")
c("Ben", TRUE, FALSE)
[1] 112   1   0
[1] "112" "Ben"
[1] "Ben"   "TRUE"  "FALSE"

On voit que R convertit automatiquement les éléments d’un vecteur pour qu’ils soient tous de mêmes types. Pour cela, il utilise la règle (symbolique) suivante.
\[ logique -> numérique -> caractère \]

càd R transforme un objet logique en un numérique (FALSE devient 0 et TRUE devient 1) et un numérique en un caractère (1 devient "1", par exemple). Les caractères l’emportent toujours sur le reste.

Aussi, lorsqu’il s’agit d’effectuer des opérations mathématiques, R transforme automatiquement un objet logique en numérique.

c(FALSE, FALSE, TRUE, TRUE) + c(0, 1, 2, 3)
exp(c(FALSE, FALSE, TRUE, TRUE))
[1] 0 1 3 4
[1] 1.00 1.00 2.72 2.72

Il existe des fonctions, de type as.*(), pour réaliser des conversions de façon explicite. Voici un exemple

as.character(c(0, 1, 2, 3, 4))
[1] "0" "1" "2" "3" "4"
as.numeric(c("0", "1", "2", "3", "4", "a"))
Warning: NAs introduced by coercion
[1]  0  1  2  3  4 NA

Comme vous pourrez le constater ci-dessus, R retourne un NA (Not Available), pour indiquer une valeur manquante. Les NA sont typiquement utilisés pour signaler une information non-disponible. Par exemple, imaginez que la deuxième personne interrogée lors de notre enquête refuse de communiquer sa taille. Nous pouvons enregistrer cette information dans R comme ceci.

tailes <- c(167, NA, 173, 174, 172, 167, 171, 185, 163, 170)
tailes
 [1] 167  NA 173 174 172 167 171 185 163 170

Remarquez que NA n’est pas la même chose que NaN qui, pour rappelle, représente le résultat d’une opération numérique non défini (par exemple 0/0).

3.5 Opérateurs logiques et opérateurs de comparaison

Pour agir sur les vecteurs, R propose, en plus des opérateurs arithmétiques classiques (+, -, *, …), deux autres types d’opérateurs : les opérateurs logiques qui s’appliquent sur les vecteurs logiques et les opérateurs de comparaison qui s’appliquent sur tout type d’objets.

Opérateurs logiques

Pour agir sur les vecteurs logiques, R propose un certain nombre d’opérateurs dont voici les plus utilisés en pratique.

Opérateur logique Signification
& et
| ou
! négation

Voici un exemple.

x <- c(FALSE, TRUE, FALSE)
y <- c(FALSE, TRUE, TRUE)
x & y
x | y
!x
[1] FALSE  TRUE FALSE
[1] FALSE  TRUE  TRUE
[1]  TRUE FALSE  TRUE

Opérateurs de comparaison

Comme leur nom l’indique, ces opérateurs sont utiles pour comparer deux vecteurs.

Opérateur de comparaison Signification
== égal à
!= différent de
> strictement supérieur à
< strictement inférieur à
>= supérieur ou égal à
<= inférieur ou égal à

Voici deux exemples.

x <- c(1, 3, -1, 0)
y <- c(1, 0, 3, 0)
x >= 0
x == y
x != y
x > y
[1]  TRUE  TRUE FALSE  TRUE
[1]  TRUE FALSE FALSE  TRUE
[1] FALSE  TRUE  TRUE FALSE
[1] FALSE  TRUE FALSE FALSE
z <- c("D", "B", "C")
w <- c("A", "B", "E")
z <= w
[1] FALSE  TRUE  TRUE

On peut évidemment combiner les deux opérateurs (logiques et de comparaison) dans la même expression.

x <- c(1, 3, -1, 0)
y <- c(1, 0, 3, 0)
(x >= 0) & (x < 3) # quels éléments de x appartiennent à [0, 3)
(x == y) | (x > y) # Quels éléments de x sont égaux ou supérieurs à ceux de y (équivalent à écrire x >= y)
[1]  TRUE FALSE FALSE  TRUE
[1]  TRUE  TRUE FALSE  TRUE

Astuce: Comparer les nombres

Lors de test d’égalité entre des nombres, il arrive que R affiche FALSE alors qu’il s’agit bel et bien de nombres identiques !

0.1 + 0.2 == 0.3
[1] FALSE

Il ne s’agit pas d’un bug de R, mais d’une limitation fondamentale liée à la manière dont les nombres réels sont représentés et stockés en informatique. Contrairement aux mathématiques pures, où les nombres peuvent avoir une précision infinie, les ordinateurs utilisent des approximations binaires qui introduisent de légers écarts.

print(0.1 + 0.2, digits = 22)
print(0.3, digits = 22)
[1] 0.3000000000000000444089
[1] 0.2999999999999999888978

Pour éviter ce type d’erreur, il est conseillé d’arrondir les nombres avant de les comparer

round(0.1 + 0.2, 10) == 0.3
[1] TRUE

Une autre solution consiste à utiliser la fonction all.equal(), qui réalise une comparaison tolérante en tenant compte des limites de précision et de stockage des machines.

all.equal(0.1 + 0.2, 0.3)
[1] TRUE

3.6 Accéder aux éléments d’un vecteur

Le but recherché ici est de pouvoir accéder, remplacer, modifier ou encore supprimer certains éléments d’un vecteur. Pour cela on utilise ce qu’on appelle l’indexation (ou sélection). Une indexation peut être numérique (appelé aussi indexation par position), par nom, ou logiques. Dans les trois cas, on utilisera les crochets [ ] pour indiquer le ou les éléments à choisir ou à exclure.

Indexation numérique

Il s’agit de faire suivre le nom du vecteur de crochets contenant la position/numéro de(s) élément(s) (in)désiré(s). Voici quelques exemples

taille <- c(167, 192, 173, 174, 172, 167, 171, 185, 163, 170)
taille[1]          # extraire le premier élément
taille[10]         # dernier élément
taille[c(5, 1)]    # cinquième et premier
taille[-c(1, 5)]   # tous sauf premier et cinquième
[1] 167
[1] 170
[1] 172 167
[1] 192 173 174 167 171 185 163 170

La composition d’un vecteur n’est pas altérée lors de l’indexation, mais on peut se servir de cette dernière pour introduire des modifications dans le vecteur. Voici un exemple.

taille <- c(167, 192, 173, 174, 172, 167, 171, 185, 163, 170)
taille[1] <- 1 # modifier le 1er élément
taille[c(3, 10)] <- c(3, 10) # modifier le 3e et 10e élément
taille[11] <- 100  # ajouter un nouveau élément et le placer 11e
taille <- c(taille, 111) # ajouter un nouveau élément à la fin
taille <- taille[-2] # supprimer le 2e élément
 [1]   1 192 173 174 172 167 171 185 163 170
 [1]   1 192   3 174 172 167 171 185 163  10
 [1]   1 192   3 174 172 167 171 185 163  10 100
 [1]   1 192   3 174 172 167 171 185 163  10 100 111
 [1]   1   3 174 172 167 171 185 163  10 100 111

Parfois, il est utile de créer un vecteur vide et de la remplir par la suite (au fur et à mesure qu’on obtient des informations, par exemple).

x <- c()
x[1] <- 2
x[5] <- 22
x
[1]  2 NA NA NA 22

Indexation par nom

Dans le cas d’un vecteur nommé, on peut aussi utiliser les noms pour extraire ou modifier certains éléments.

person1 <- c(age = 22, height = 1.84, zipcode = 1348)
person1[c("zipcode", "age")]
zipcode     age 
   1348      22 

Indexation logique

On peut aussi accéder à des éléments d’un vecteur en faisant suivre le nom du vecteur de crochets contenant un vecteur logique. Prenons, par exemple, le cas suivant

nom <- c("Benjamin", "Hugo", "Emma", "Alex", "Tom", "Axel", "Alice", "Martin", "Robin", "Enzo")
fum <- c(FALSE, FALSE, TRUE, FALSE, FALSE, TRUE, FALSE, FALSE, FALSE, TRUE)

Supposons que l’on souhaite savoir le nom des fumeurs dans notre échantillon. Il suffit de taper

nom[fum]
[1] "Emma" "Axel" "Enzo"

Cela peut se lire comme : récupérer les éléments de nom pour lesquels fum est égal à TRUE.

Pour identifier les positions des TRUE dans un vecteur logique, il y a la fonction which().

which(fum)
[1]  3  6 10

Ainsi, nom[fum] est simplement une forme raccourcie de l’écriture nom[which(fum)]. Dans ce cas, which() est superflu, mais peut être utile dans d’autres contextes où les indices numériques sont nécessaires.

Pour qu’une indexation logique, de type nom[fum], fonctionne correctement, le vecteur à indexer (ici nom) et le vecteur logique (ici fum) doivent avoir la même longueur. Si ce n’est pas le cas, R recycle automatiquement les éléments du vecteur logique (ce qui peut entraîner des résultats inattendus) :

nom[c(TRUE, FALSE, TRUE)]
[1] "Benjamin" "Emma"     "Alex"     "Axel"     "Alice"    "Robin"    "Enzo"    

Voici d’autres exemples où l’indexation logique s’avère très utile.

nom <- c("Benjamin", "Hugo", "Emma", "Alex", "Tom", "Axel", "Alice", "Martin", "Robin", "Enzo")
taille <- c(167, 192, 173, 174, 172, 167, 171, 185, 163, 170)
nom[taille == 167]                # noms des personnes dont la taille est de 167 cm.
nom[taille >= 172 & taille < 180] # noms des personnes dont la taille est dans [172,180).
nom[taille >= 172 & !fum]         # noms des personnes qui ne fument pas et dont taille >= 172
[1] "Benjamin" "Axel"    
[1] "Emma" "Alex" "Tom" 
[1] "Hugo"   "Alex"   "Tom"    "Martin"

L’indexation logique peut aussi se réaliser via la fonction subset(). La syntaxe générale est subset(x, condition) :

nom |> subset(taille == 167) # ou subset(nom, taille == 167)
[1] "Benjamin" "Axel"    

subset() est particulièrement utile et couramment employée avec les data frames, que nous aborderons plus loin.

3.7 Quelques fonctions usuelles

Nous allons lister ici une série de fonctions/exmples très fréquemment utilisé en pratique pour créer ou manipuler les vecteurs.

Créer des séquences régulières

Il y a plusieurs façons pour créer des séries numériques en R. Voici un aperçu.

1:10                       # 1 à 10, par pas de 1
seq(1, 10)                 # 1 à 10, par pas de 1
seq(1, 10, by = 2.25)      # 1 à 10, par pas de 2.25
seq(1, 10, length = 5)     # 5 valeurs équidistantes de 1 à 10
rep(1:3, times = 3)        # répéter (1,2,3) trois fois
rep(1:3, times = c(3, 2, 4)) # répéter 1 trois fois, 2 deux fois, et 3 quatre fois
 [1]  1  2  3  4  5  6  7  8  9 10
 [1]  1  2  3  4  5  6  7  8  9 10
[1]  1.00  3.25  5.50  7.75 10.00
[1]  1.00  3.25  5.50  7.75 10.00
[1] 1 2 3 1 2 3 1 2 3
[1] 1 1 1 2 2 3 3 3 3

Réarranger les éléments d’un vecteur

taille <- c(167, 192, 173, 174, 172, 167, 171, 185, 163, 170)
rev(taille)              # inverser les éléments du vecteur
sort(taille)             # ranger par ordre croissant
sort(taille, dec = TRUE) # ranger par ordre décroissant
order(taille)            # indices pour ranger le vecteur par ordre croissant
 [1] 170 163 185 171 167 172 174 173 192 167
 [1] 163 167 167 170 171 172 173 174 185 192
 [1] 192 185 174 173 172 171 170 167 167 163
 [1]  9  1  6 10  7  5  3  4  8  2

Sommaires et statistiques descriptives

taille <- c(167, 192, 173, 174, 172, 167, 171, 185, 163, 170)
sum(taille)       # somme des éléments
prod(taille)      # produit des éléments
mean(taille)      # moyenne (empirique)
max(taille)       # maximum
min(taille)       # minimum
range(taille)     # min et max
median(taille)    # médiane (empirique)
[1] 1734
[1] 2.43e+22
[1] 173
[1] 192
[1] 163
[1] 163 192
[1] 172

Toutes ces fonctions (et d’autres encore) acceptent l’argument na.rm qui permet d’ignorer les valeurs manquantes dans le calcul. Pour voir comment cela marche, considérant l’exemple suivant.

tailes <- c(167, NA, 173, 174, 172, 167, 171, 185, 163, 170)
sum(tailes)
sum(tailes, na.rm = TRUE)
[1] NA
[1] 1542

Parmi les fonctions statistiques les plus utiles, on trouve (1) la fonction générique summary() qui résume les données sous forme de six chiffres clés (minimum, maximum, moyenne et quartiles), (2) la fonction table() qui permet de compter le nombre d’occurrences de chaque élément (unique) d’un vecteur donné.

summary(taille)                                # statistiques descriptives
c("A", "B", "E", "E", "A", "A", "A", "B") |> table()  # comptage
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
    163     168     172     173     174     192 

A B E 
4 2 2 

Recherche d’éléments dans un vecteur

taille <- c(167, 192, 173, 174, 172, 167, 171, 185, 163, 170)
# Positions des TRUE dans un vecteur logique (déjà été abordé)
(taille >= 180) |> which()
# Opérateur %in% : tester l'appartenance d'un ou plusieurs éléments à un ensemble
# x %in% y signifie : quels sont les éléments de x qui se trouvent dans y
c(160, 192, 200, 163) %in% taille
# Extraire les éléments uniques d'un vecteur
c(5, 2, 1, 4, 6, 9, 8, 5, 7, 9) |> unique()
[1] 2 8
[1] FALSE  TRUE FALSE  TRUE
[1] 5 2 1 4 6 9 8 7

Arrondi

x <- c(-3.6800000, -0.6666667, 3.1415927, 0.3333333, 1.2221201, 3.255166)
# Arrondi à un nombre défini de décimales (par défaut 0)
round(x, 4)
# Plus grand entier inférieur ou égal à l'argument
floor(x)
# Plus petit entier supérieur ou égal à l'argument
ceiling(x)
[1] -3.6800 -0.6667  3.1416  0.3333  1.2221  3.2552
[1] -4 -1  3  0  1  3
[1] -3  0  4  1  2  4

Concaténer des chaines de caractères

La fonction paste() est utile dans une situation où l’on souhaite “coller” des chaînes de caractères ensemble. Voici quelques exemples.

a <- "coucou"
b <- "comment vas-tu?"
c <- "j'espère que tout va bien."
paste(a, b, c)
[1] "coucou comment vas-tu? j'espère que tout va bien."
paste(a, b, c, sep = ", ")
[1] "coucou, comment vas-tu?, j'espère que tout va bien."
paste(a, b, c, sep = "") # ou paste0(a,b,c)
[1] "coucoucomment vas-tu?j'espère que tout va bien."
paste(a, 1:5, sep = "")
[1] "coucou1" "coucou2" "coucou3" "coucou4" "coucou5"