La version originale de ce chapitre a été écrite par Ewen Gallic dans le cadre de son support de cours d’Ewen Gallic intitulé Logiciel R et programmation, chapitre 4 Boucles et calculs vectoriels.

Les boucles sont des opérations lentes en R. Il est cependant possible, dans de nombreux cas, d’éviter de les employer, en ayant recours à la vectorisation : au lieu d’appliquer une fonction à un scalaire, on l’applique à un vecteur. En fait, nous avons déjà eu recours à maintes reprises aux calculs vectoriels. En effet, lorsque nous avons procédé à des additions, des multiplications, etc. sur des vecteurs, nous avons effectué des calculs vectoriels.

Empruntons un exemple à Burns (2011) : dans des langages comme le C, pour effectuer la somme des logarithmes naturels des n premiers entiers, voici une manière de faire :

# Somme des logarithmes des 10 premiers entiers
somme_log <- 0
for (i in seq_len(10)) {
  somme_log <- somme_log + log(i)
}
somme_log
[1] 15.10441

Il est possible d’obtenir le même résultat, à la fois d’une manière plus élégante, mais surtout plus efficace en vectorisant le calcul :

sum(log(seq_len(10)))
[1] 15.10441

Derrière ce code, la fonction log applique la fonction logarithme sur toutes les valeurs du vecteur donné en paramètre. La fonction sum, quant à elle, se charge d’additionner tous les éléments du vecteur qui lui est donné en paramètre. Ces deux fonctions utilisent la vectorisation, mais d’une manière différente : la fonction log applique une opération à chaque élément d’un vecteur, tandis que la fonction sum produit un résultat basé sur l’ensemble du vecteur. L’avantage d’utiliser des fonctions vectorielles plutôt que d’écrire une boucle pour effectuer le calcul, est que ces premières font appel à des fonctions rédigées en C ou FORTRAN, qui utilisent aussi des boucles, mais comme ce sont des langages compilés et non pas interprétés, les itérations sont réalisées dans un temps réduit.

Il existe des fonctions, rédigées en C qui effectuent des boucles for. On leur donne souvent le nom de “fonctions de la famille apply”. Il ne s’agit pas de la vectorisation, mais ces fonctions sont souvent mentionnées dès que l’on parle de ce sujet. Ce sont des fonctionnelles qui prennent une fonction en input et retournent un vecteur en output (Wickham, 2014). Ces fonctions sont très utilisées, mais elles souffrent d’un manque d’uniformité. En effet, elles ont été rédigées par des personnes différentes, ayant chacune leur convention. L’extension plyr remédie à ce problème, et ajoute par la même occasion des fonctions supplémentaires, pour couvrir plus de cas que les “fonctions de la famille apply”.

Nous allons donc présenter dans un premier temps les fonctions du package plyr. Les fonctions du même type du package base seront tout de même présentées par la suite.

Les fonctions de l’extension plyr

Les fonctions que nous allons aborder dans cette section possèdent des noms faciles à se remémorer : la première lettre correspond au format d’entrée des données, la seconde au format de sortie souhaité, et la fin du nom se termine par le suffixe ply. Ainsi, la fonction llpply prend en entrée une liste, effectue une opération sur les éléments, et retourne une liste (Anderson, 2012).

Les différentes fonctions que nous allons passer en revue sont consignées dans le tableau ci-après, où les lignes correspondent aux formats d’entrée, et les lignes aux formats de sortie. Pour y avoir accès, il faut charger le package :

library(plyr)
Format de sortie
array data.frame list
Format d’entée array aaply adply alply
data.frame daply ddply dlply
list laply ldply llply

Il est possible d’avoir plusieurs paramètres en input au lieu d’un seul objet. Les fonctions mlply, mdply et maply. Si à la place du m, la première lettre est un r, il s’agit alors de fonction de réplications. Enfin, si la seconde lettre est un trait de soulignement (_), alors le résultat retourné n’est pas affiché (le code utilise la fonction invisible.

Tous les paramètres de ces fonctions commencent par un point (.), afin d’éviter des incompatibilités avec la fonction à appliquer.

Array en input : a*ply

Les fonctions aaply, adply et alply appliquent une fonction à chaque portion d’un array et ensuitent joignent le résultat sous forme d’un array, d’un data.frame ou d’une list respectivement.

Un array peut être vu comme un vecteur à plusieurs dimensions. Comme pour un vecteur, toutes les valeurs doivent être du même type. Un vecteur n’est finalement qu’un array à une seule dimension. De même, un array à deux dimensions correspond à ce qu’on appelle usuelement une matrice.

Le paramètre .margins détermine la manière de découper le tableau. Il y en a quatre pour un tableau en deux dimensions :

  1. .margins = 1 : par lignes ;
  2. .margins = 2 : par colonnes ;
  3. .margins = c(1,2) : par cellule ;
  4. .margins = c() : ne pas faire de découpement.

Pour un tableau en trois dimensions, il y a trois découpages possibles en deux dimensions, trois en une dimension et une en zéro dimension (voir (Wickham, 2011)) au besoin.

tableau <- array(1:24, dim = c(3, 4, 2), dimnames = list(ligne = letters[1:3], colonne = LETTERS[1:4], 
  annee = 2001:2002))
tableau
, , annee = 2001

     colonne
ligne A B C  D
    a 1 4 7 10
    b 2 5 8 11
    c 3 6 9 12

, , annee = 2002

     colonne
ligne  A  B  C  D
    a 13 16 19 22
    b 14 17 20 23
    c 15 18 21 24
# La moyenne des valeurs pour chaque ligne
aaply(tableau, 1, mean)  # résultat sous forme de tableau
   a    b    c 
11.5 12.5 13.5 
adply(tableau, 1, mean)  # résultat sous forme de data.frame
alply(tableau, 1, mean)  # résultat sous forme de liste
$`1`
[1] 11.5

$`2`
[1] 12.5

$`3`
[1] 13.5

attr(,"split_type")
[1] "array"
attr(,"split_labels")
  ligne
1     a
2     b
3     c
# La moyenne des valeurs pour chaque colonne en ne simplifiant pas le résultat
aaply(tableau, 2, mean, .drop = FALSE)
       
colonne  1
      A  8
      B 11
      C 14
      D 17
# Par lignes et colonnes
aaply(tableau, c(1, 2), mean)
     colonne
ligne A  B  C  D
    a 7 10 13 16
    b 8 11 14 17
    c 9 12 15 18
adply(tableau, c(1, 2), mean)
# Avec une fonction définie par l'utilisateur
standardise <- function(x) (x - min(x))/(max(x) - min(x))
# Standardiser les valeurs par colonne
aaply(tableau, 2, standardise)
, , annee = 2001

       ligne
colonne a          b         c
      A 0 0.07142857 0.1428571
      B 0 0.07142857 0.1428571
      C 0 0.07142857 0.1428571
      D 0 0.07142857 0.1428571

, , annee = 2002

       ligne
colonne         a         b c
      A 0.8571429 0.9285714 1
      B 0.8571429 0.9285714 1
      C 0.8571429 0.9285714 1
      D 0.8571429 0.9285714 1