5 Les matrices

En R, une matrice est essentiellement un vecteur dont les éléments sont organisés sous forme de tableau, avec des lignes et des colonnes. Ce qui distingue une matrice d’un simple vecteur, c’est qu’elle possède un attribut appelé dim (pour dimension), qui définit sa structure bidimensionnelle (lignes et colonnes).

Pour créer une matrice, on utilise la fonction matrix(), qui permet de spécifier les données ainsi que le nombre de lignes (nrow, par défaut nrow = 1) et de colonnes (ncol, par défaut ncol = 1) souhaité.

matrix(1:6)
     [,1]
[1,]    1
[2,]    2
[3,]    3
[4,]    4
[5,]    5
[6,]    6
matrix(1:6, ncol = 2)
     [,1] [,2]
[1,]    1    4
[2,]    2    5
[3,]    3    6
matrix(1:6, nrow = 2)
     [,1] [,2] [,3]
[1,]    1    3    5
[2,]    2    4    6

Par défaut, le remplissage d’une matrice se fait par colonne. Si vous voulez un remplissage par ligne, il faut utiliser l’option byrow = TRUE.

matrix(1:6, nrow = 2, byrow = TRUE)
     [,1] [,2] [,3]
[1,]    1    2    3
[2,]    4    5    6

Comme pour les vecteurs, tous les éléments d’une matrice doivent être de même classe (numérique, caractère ou logique). Aussi, comme les vecteurs, R utilise le principe de recyclage lors de la construction d’une matrice. Par exemple, le code suivant :

xm <- matrix(1:6, nrow = 5, ncol = 10)
xm
     [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10]
[1,]    1    6    5    4    3    2    1    6    5     4
[2,]    2    1    6    5    4    3    2    1    6     5
[3,]    3    2    1    6    5    4    3    2    1     6
[4,]    4    3    2    1    6    5    4    3    2     1
[5,]    5    4    3    2    1    6    5    4    3     2

génère un Warning (non affiché ici) car le nombre d’éléments fournis (ici 1:6, soit 6 éléments) ne correspond pas au nombre total de cases que la matrice devrait contenir (ici 5 × 10 = 50 éléments). R essaie de remplir la matrice en répétant les valeurs du vecteur initial autant de fois que nécessaire. Mais ici, 6 ne divise pas exactement 50, donc R recycle les valeurs de manière incomplète, ce qui déclenche le Warning.

On peut consulter ou modifier les dimensions d’une matrice à l’aide de la fonction dim(). On peut également utiliser str() pour obtenir une description plus complète de la structure de l’objet, y compris ses dimensions et le type de données qu’il contient :

dim(xm) # voir aussi nrow() et ncol()
str(xm)
[1]  5 10
 int [1:5, 1:10] 1 2 3 4 5 6 1 2 3 4 ...

Avec les fonctions colnames() et rownames(), on peut consulter ou modifier les noms des colonnes et des lignes d’une matrice. Cela permet de rendre les données plus lisibles et de faciliter leur manipulation :

colnames(xm) <- LETTERS[1:10]
rownames(xm) <- paste0("Ligne", 1:5, sep = "")
xm
       A B C D E F G H I J
Ligne1 1 6 5 4 3 2 1 6 5 4
Ligne2 2 1 6 5 4 3 2 1 6 5
Ligne3 3 2 1 6 5 4 3 2 1 6
Ligne4 4 3 2 1 6 5 4 3 2 1
Ligne5 5 4 3 2 1 6 5 4 3 2

Accéder aux éléments d’une matrice

Voici quelques exemples qui illustrent les différents façons d’accéder aux éléments (lignes, colonnes) d’une matrice.

xm[1, ]  # ligne 1 (ici l'output est un vecteur)
A B C D E F G H I J 
1 6 5 4 3 2 1 6 5 4 
xm[c(1, 3), ] #<-- lignes 1 et 3 (indexation numérique)
xm[c("Ligne1", "Ligne3"), ] #<-- indexation par nom
xm[c(TRUE, FALSE, TRUE, FALSE, FALSE), ] #<-- indexation logique
       A B C D E F G H I J
Ligne1 1 6 5 4 3 2 1 6 5 4
Ligne3 3 2 1 6 5 4 3 2 1 6
xm[, 1:2]  # colonnes 1 et 2 (indexation numérique)
xm[, c("A", "B")] #<-- indexation par nom
xm[, c(TRUE, TRUE, rep(FALSE, 8))] #<-- indexation logique
       A B
Ligne1 1 6
Ligne2 2 1
Ligne3 3 2
Ligne4 4 3
Ligne5 5 4
xm[, -c(1, 3)] # toutes les colonnes sauf 1 et 3
       B D E F G H I J
Ligne1 6 4 3 2 1 6 5 4
Ligne2 1 5 4 3 2 1 6 5
Ligne3 2 6 5 4 3 2 1 6
Ligne4 3 1 6 5 4 3 2 1
Ligne5 4 2 1 6 5 4 3 2
xm[1, 2] # ligne 1, colonne 2 (ici l'output est un vecteur de longueur 1)
xm[1, ][2] # idem
xm[, 2][1] # idem
[1] 6
xm[c(1, 3), c(3, 4)] # lignes 1,3 et colonnes 3 et 4
xm[c(1, 3), ][, c(3, 4)] # idem
       C D
Ligne1 5 4
Ligne3 1 6

Vous avez certainement remarqué que quand c’est possible, R simplifie automatiquement le résultat d’une extraction et renvoie un vecteur comme sortie. On peut empêcher cette simplification et forcer R à garder le résultat sous forme de matrice à l’aide de l’argument l’option drop = FALSE.

xm[1, 2, drop = FALSE]
       B
Ligne1 6

On peut confirmer qu’il agit bien d’une matrice à l’aide de la fonction is.matrix() :

xm[1, 2, drop = FALSE] |> is.matrix()
xm[1, 2] |> is.matrix()
[1] TRUE
[1] FALSE

Une autre façon d’accéder aux éléments d’une matrice en R consiste à utiliser ce qu’on appelle l’index linéaire. Cela revient à considérer la matrice comme un vecteur formé en concaténant les colonnes les unes après les autres. Autrement dit, R parcourt les éléments colonne par colonne, et attribue à chaque élément un numéro d’ordre selon cette logique. Cette méthode permet d’accéder aux éléments avec une seule valeur d’indice, comme dans l’exemple suivant :

xm[8]
[1] 2

qui renvoie 11, car c’est le 8e élément de xm en comptant colonne par colonne.

Modifier une matrice

On peut facilement modifier les éléments d’une matrice. Voici quelques exemples :

xm[2, 2] <- 11 # modifier un élément spécifique
xm[1, ] <- c(7, 8) # modifier une ligne entière
xm[c(1, 3), c(3, 4)] <- c(77, 11, 88, 66) # modifier plusieurs éléments à la fois
xm[-1, 10] <- c(55, 66, 11, 22) # modifier plusieurs éléments à la fois
xm
       A  B  C  D E F G H I  J
Ligne1 7  8 77 88 7 8 7 8 7  8
Ligne2 2 11  6  5 4 3 2 1 6 55
Ligne3 3  2 11 66 5 4 3 2 1 66
Ligne4 4  3  2  1 6 5 4 3 2 11
Ligne5 5  4  3  2 1 6 5 4 3 22

Lorsque l’on souhaite regrouper plusieurs vecteurs ou matrices pour former une structure plus grande, R propose deux fonctions très pratiques : rbind() et cbind(). Ces fonctions permettent d’assembler des objets :

  • rbind() (row bind) : ajoute des lignes à une matrice ou crée une matrice en empilant des vecteurs verticalement.
  • cbind() (column bind) : ajoute des colonnes à une matrice ou crée une matrice en alignant des vecteurs horizontalement.
v1 <- 1:10
v2 <- 11:20
cbind(v1, v2)
      v1 v2
 [1,]  1 11
 [2,]  2 12
 [3,]  3 13
 [4,]  4 14
 [5,]  5 15
 [6,]  6 16
 [7,]  7 17
 [8,]  8 18
 [9,]  9 19
[10,] 10 20
rbind(v1, v2)
   [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10]
v1    1    2    3    4    5    6    7    8    9    10
v2   11   12   13   14   15   16   17   18   19    20
rbind(xm, v1)
       A  B  C  D E F G H I  J
Ligne1 7  8 77 88 7 8 7 8 7  8
Ligne2 2 11  6  5 4 3 2 1 6 55
Ligne3 3  2 11 66 5 4 3 2 1 66
Ligne4 4  3  2  1 6 5 4 3 2 11
Ligne5 5  4  3  2 1 6 5 4 3 22
v1     1  2  3  4 5 6 7 8 9 10
matrix(1:25, nrow = 5, byrow = TRUE) |> cbind(xm)
                      A  B  C  D E F G H I  J
Ligne1  1  2  3  4  5 7  8 77 88 7 8 7 8 7  8
Ligne2  6  7  8  9 10 2 11  6  5 4 3 2 1 6 55
Ligne3 11 12 13 14 15 3  2 11 66 5 4 3 2 1 66
Ligne4 16 17 18 19 20 4  3  2  1 6 5 4 3 2 11
Ligne5 21 22 23 24 25 5  4  3  2 1 6 5 4 3 22

Notez que ces dernières opérations ne modifient pas la matrice xm. Elles produisent un nouvel objet, sans altérer l’original. Si l’on souhaite que la matrice xm intègre réellement les modifications, il faut la redéfinir explicitement, comme dans l’exemple suivant :

xm <- rbind(xm, v1)

Opérations matricielles

R sait aussi faire toute sorte d’opérations matricielles mathématiques. Voici quelques exemples élémentaires.

X <- matrix(c(9, 2, -3, 2, 4, -2, -3, -2, 16), 3, byrow = TRUE)
Y <- matrix(0:8, ncol = 3)
X + Y # addition
     [,1] [,2] [,3]
[1,]    9    5    3
[2,]    3    8    5
[3,]   -1    3   24
X - Y # soustraction
     [,1] [,2] [,3]
[1,]    9   -1   -9
[2,]    1    0   -9
[3,]   -5   -7    8
X * Y # multiplication (élément par élément)
     [,1] [,2] [,3]
[1,]    0    6  -18
[2,]    2   16  -14
[3,]   -6  -10  128
X / Y # division
     [,1]   [,2]   [,3]
[1,]  Inf  0.667 -0.500
[2,]  2.0  1.000 -0.286
[3,] -1.5 -0.400  2.000
X %*% Y # produit matriciel
     [,1] [,2] [,3]
[1,]   -4   20   44
[2,]    0   12   24
[3,]   30   63   96
t(X) # transposer
     [,1] [,2] [,3]
[1,]    9    2   -3
[2,]    2    4   -2
[3,]   -3   -2   16
det(X) # déterminant
[1] 464
solve(X) # inverse
        [,1]    [,2]   [,3]
[1,]  0.1293 -0.0560 0.0172
[2,] -0.0560  0.2909 0.0259
[3,]  0.0172  0.0259 0.0690
colMeans(X) # moyenne par colonne (il y a aussi colSums(), rowSums() et rowMeans())
[1] 2.67 1.33 3.67
rep(1, 3) |> diag() # matrice diagonale
     [,1] [,2] [,3]
[1,]    1    0    0
[2,]    0    1    0
[3,]    0    0    1

Sachez également qu’il est tout à fait possible de réaliser des opérations logiques sur les matrices en R, comme le montrent les exemples suivants. Ces opérations permettent de filtrer, comparer ou extraire des éléments selon des conditions précises.

X > 0
      [,1]  [,2]  [,3]
[1,]  TRUE  TRUE FALSE
[2,]  TRUE  TRUE FALSE
[3,] FALSE FALSE  TRUE
X[X > 0] # les éléments de X > 0
[1]  9  2  2  4 16
X[X > 0] <- 100 # remplacer les éléments > 0 par 100
X
     [,1] [,2] [,3]
[1,]  100  100   -3
[2,]  100  100   -2
[3,]   -3   -2  100
X[X[, 1] > 0, ] # lignes où la première colonne est > 0
     [,1] [,2] [,3]
[1,]  100  100   -3
[2,]  100  100   -2
which(X > 0)  # position, en index linéaire, des éléments > 0
[1] 1 2 4 5 9
X[which(X > 0)] # idem que X[X > 0]
[1] 100 100 100 100 100
which(X > 0, arr.ind = TRUE) # coordonnées (ligne, colonne) des éléments > 0
     row col
[1,]   1   1
[2,]   2   1
[3,]   1   2
[4,]   2   2
[5,]   3   3