Mémo des fonctions lapply, sapply, tapply, apply

Image par Gerd Altmann de Pixabay

Ces fonctions lapply, sapply, tapply et lapply permettent d’appliquer une fonction (mean, par exemple, pour calculer une moyenne) sur des données, de façon itérative. Autrement dit, elles font la même chose qu’une boucle for(), tout en ayant une syntaxe concise, puisque ça se passe en une ligne de commande, et en étant plus rapide.

Néanmoins,  de mon côté, j’ai toujours eu des difficultés à les employer parce que je ne me souviens jamais laquelle utiliser selon :

  • la structure de mes données d’entrées (data frame, vecteur, liste),
  • ce que je veux faire (appliquer une fonction par sous-groupe de données, appliquer une fonction sur les marges (sur chaque ligne ou chaque colonne) d’un data frame),
  • ce que je souhaite obtenir en sortie (un vecteur, une liste).

Savoir utiliser ces fonctions peut cependant s’avérer très utile. Alors,  j’ai fini par me faire un petit mémo, que je vous partage ici.

La fonction lapply

Elle réalise une boucle sur une structure de type liste, en appliquant une fonction sur chaque élément de cette liste. La lettre l devant le apply correspond à `list`

Par défaut, les résultats sont également fournis sous forme d’une liste :

maliste <- list(E1=rnorm(10),E2=1:10, E3=runif(10))
maliste
## $E1
##  [1] -1.7984349  0.6276849  0.7310556  1.1642278 -1.0313113  0.1958217
##  [7] -1.9018991 -1.8122020 -0.3482781 -1.2713203
## 
## $E2
##  [1]  1  2  3  4  5  6  7  8  9 10
## 
## $E3
##  [1] 0.723830546 0.838541188 0.845484439 0.039995958 0.615807877 0.917093245
##  [7] 0.867372951 0.224336368 0.001643635 0.081938347


lapply(maliste, mean)
## $E1
## [1] -0.5444656
## 
## $E2
## [1] 5.5
## 
## $E3
## [1] 0.5156045
 

Si on utilise une fonction qui nécessite des arguments, il faut les indiquer après la fonction, comme ici avec la fonction quantile() et l’argument probs.

lapply(maliste, quantile,probs=c(0.25,0.75))
## $E1
##        25%        75% 
## -1.6666563  0.5197191 
## 
## $E2
##  25%  75% 
## 3.25 7.75 
## 
## $E3
##       25%       75% 
## 0.1175379 0.8437486 

On peut également obtenir les résultats sous la forme d’un vecteur en employant la fonction unlist() en amont :

unlist(lapply(maliste, mean))
##         E1         E2         E3 
## -0.5444656  5.5000000  0.5156045
unlist(lapply(maliste, quantile,probs=c(0.25,0.75)))
##     E1.25%     E1.75%     E2.25%     E2.75%     E3.25%     E3.75% 
## -1.6666563  0.5197191  3.2500000  7.7500000  0.1175379  0.8437486 

Et il est aussi possible d’utiliser un vecteur en entrée, plutôt qu’une liste :

nom <- names(iris)
nom
## [1] "Sepal.Length" "Sepal.Width"  "Petal.Length" "Petal.Width"  "Species"
class(nom)
## [1] "character"
NOM <- unlist(lapply(nom, toupper))
NOM
## [1] "SEPAL.LENGTH" "SEPAL.WIDTH"  "PETAL.LENGTH" "PETAL.WIDTH"  "SPECIES" 

Donc, si on résume : lapply permet d’appliquer une fonction sur tous les éléments d’une liste, et fournit les résultats sous forme de liste.

MAIS…, on peut facilement transformer la liste de sortie en vecteur, grâce à la fonction unlist(). Et, on peut aussi donner en entrée un vecteur d’éléments !

Ce n’est donc pas pour rien que j’ai toujours eu du mal à m’y retrouver !

La fonction sapply

Le s est pour simplify ( de la sortie) ! Après ce qu’on vient de voir, on se dit forcément que c’est une bonne idée ! 

Allez, on regarde de plus près comment ça fonctionne :

maliste <- list(E1=rnorm(10),E2=1:10, E3=runif(10))
res <- sapply(maliste,mean)
res
##         E1         E2         E3 
## -0.6635282  5.5000000  0.4673550
class(res)
## [1] "numeric" 

La fonction sapply fait donc la même chose que la fonction lapply, mais en fournissant directement un vecteur en sortie !

Un vecteur….ou une matrice, si la fonction renvoie plusieurs éléments :

res <- sapply(maliste,quantile,probs=c(0.25,0.75))
res
##              E1   E2        E3
## 25% -1.20998298 3.25 0.2139582
## 75%  0.04138477 7.75 0.7128085
class(res)
## [1] "matrix" "array" 

Pas super simplifié, quand même !

La fonction tapply

La fonction tapply permet d’appliquer une fonction sur une variable, par sous-groupe de données, que l’on spécifie en argument. Et les résultats sont fournis sous une structure de type array.

Par exemple, on peut obtenir la moyenne des longueurs de sépale pour chaque espèce d’iris :

res <- tapply(iris$Sepal.Length,iris$Species, mean )
res
##     setosa versicolor  virginica 
##      5.006      5.936      6.588
class(res)
## [1] "array" 

C’est l’équivalent de la fonction by(), mais sans la mise en forme:

by(iris$Sepal.Length,iris$Species, mean )
## iris$Species: setosa
## [1] 5.006
## ------------------------------------------------------------ 
## iris$Species: versicolor
## [1] 5.936
## ------------------------------------------------------------ 
## iris$Species: virginica
## [1] 6.588 

On peut aussi employer cette syntaxe :

with(iris, tapply(Sepal.Length, Species, mean))
##     setosa versicolor  virginica 
##      5.006      5.936      6.588 

Et comme précédemment, si la fonction employée nécessite des arguments supplémentaires, on les ajoute après la virgule :

res <- with(iris, tapply(Sepal.Length, Species, quantile, probs=c(0.25,0.75)))
res
## $setosa
## 25% 75% 
## 4.8 5.2 
## 
## $versicolor
## 25% 75% 
## 5.6 6.3 
## 
## $virginica
##   25%   75% 
## 6.225 6.900
class(res)
## [1] "array" 

La fonction apply

La fonction apply permet d’appliquer une fonction sur toutes les lignes ou toutes les colonnes d’un data frame (ou une matrice). Si on souhaite appliquer la fonction sur les lignes, on va spécifier l’argument MARGIN=1 (en pratique, on utilise que le 1 en second argument de la fonction).

De la même manière, si on souhaite appliquer la fonction sur les colonnes, on va spécifier l’argument MARGIN=2(là encore, en pratique, on utilise que le 2 en second argument de la fonction)

Par exemple ici, si on souhaite faire la moyenne des 4 premières variables du jeu de données iris , sur les 10 premières lignes :

res <- apply(iris[1:10,1:4],1, mean, na.rm=TRUE)
res
##     1     2     3     4     5     6     7     8     9    10 
## 2.550 2.375 2.350 2.350 2.550 2.850 2.425 2.525 2.225 2.400
class(res)
## [1] "numeric" 

C’est l’équivalent de la fonction rowMeans().

res <- rowMeans(iris[1:10,1:4])
res
##     1     2     3     4     5     6     7     8     9    10 
## 2.550 2.375 2.350 2.350 2.550 2.850 2.425 2.525 2.225 2.400
class(res)
## [1] "numeric" 

Mais l’intérêt de apply, c’est qu’on peut utiliser n’importe quelle fonction :

res <- apply(iris[1:10,1:4],1, summary)
res
##            1     2     3     4    5     6     7     8     9   10
## Min.    0.20 0.200 0.200 0.200 0.20 0.400 0.300 0.200 0.200 0.10
## 1st Qu. 1.10 1.100 1.025 1.175 1.10 1.375 1.125 1.175 1.100 1.15
## Median  2.45 2.200 2.250 2.300 2.50 2.800 2.400 2.450 2.150 2.30
## Mean    2.55 2.375 2.350 2.350 2.55 2.850 2.425 2.525 2.225 2.40
## 3rd Qu. 3.90 3.475 3.575 3.475 3.95 4.275 3.700 3.800 3.275 3.55
## Max.    5.10 4.900 4.700 4.600 5.00 5.400 4.600 5.000 4.400 4.90
class(res)
## [1] "matrix" "array" 

Comme la sortie summary() renvoie plusieurs éléments, la fonction apply renvoie, en sortie, une matrice.

Idem, pour les colonnes, en employant l’argument 2. Par exemple, ici, on calcule la moyenne des colonnes 1 à 4, c’est-à-dire les variables “Sepal.Length”, “Sepal.Width” , “Petal.Length”, “Petal.Width” :

res <- apply(iris[,1:4],2, mean, na.rm=TRUE)
res
## Sepal.Length  Sepal.Width Petal.Length  Petal.Width 
##     5.843333     3.057333     3.758000     1.199333
class(res)
## [1] "numeric"
res <- apply(iris[,1:4],2, quantile,probs=c(0.25,0.75))
res
##     Sepal.Length Sepal.Width Petal.Length Petal.Width
## 25%          5.1         2.8          1.6         0.3
## 75%          6.4         3.3          5.1         1.8
class(res)
## [1] "matrix" "array" 

Pour aller plus loin

Si ces fonctions  *apply() vous intéressent vous pouvez aussi explorer les fonctions vapply() et mapply(). Vous trouverez des informations ici, et là.

Je vous recommande aussi de regarder du côté du package purrr et ces fonctions map() qui permettent aussi d’appliquer une même fonction (ou une même série de fonctions) à chaque élément d’un ensemble de données.

Vous trouverez une introduction au package purrr et une liste de ressources pour apprendre à l’utiliser,  dans mon article : Liste de ressources pour le package purrr 

Ce petit mémo m’est très utile, et j’espère qu’il en sera de même pour vous. N’hésitez pas à me partager en commentaire des exemples de vos propres utilisations de ces fonctions apply. Cela aidera certainement ceux qui débutent !

 

Si cet article vous a plu, ou vous a été utile, et si vous le souhaitez, vous pouvez soutenir ce blog en faisant un don sur sa page Tipeee 🙏

Poursuivez votre lecture

3 réponses

  1. Bonjour Claire,

    Je profite régulièrement de vos articles pour améliorer ma programmation en R (plutôt artisanale) d’analyses multivariées. J’utilise des boucles, doubles ou triples, ce qui entraîne de long calculs. J’ai déjà un peu simplifié en créant une fonction mais je voudrais aussi utiliser “apply” ou encore “fpurrr” mais je ne vois pas comment y arriver dans cet exeple :

    #nsca with permutations Guy BOUXIN 2021
    filename<-tailfer5
    don<-as.matrix(filename)
    nper=1000
    nax=12
    nbrsp= nrow(don)
    nbrrel= ncol(don)
    nomsp<-rownames(don)
    nomrel<-colnames(don)
    nrang= 1+nbrsp+nbrrel
    nbrcol=nax*3
    nomrang<-list(1:nrang)
    nomcol<-list(1:1+nax*3)
    for(j in 1:nax)
    {
    nomcol[1+(j-1)*3]<-paste("coord",j)
    nomcol[2+(j-1)*3] <-paste("Cr%",j)
    nomcol[3+(j-1)*3] <-paste("P",j)
    }
    resulnsca<-matrix(1:nrang*nbrcol, nrow=nrang,ncol=nbrcol,byrow=TRUE)
    colnames(resulnsca)<- nomcol
    #calculation of the transformed matrix
    don1<-matrix(1 :nbrsp*nbrrel, nrow=nbrsp,ncol=nbrrel)
    somme = 0
    hr<-matrix(1:nbrsp)
    hc<-matrix(1:nbrrel)
    hr<-rowSums(don)
    hc<-colSums(don)
    somme=sum(don)
    for(i in 1:nbrsp)
    {
    somrow<-hr[i]
    for(j in 1:nbrrel)
    {
    somcol<-hc[j]
    donn<-don[i,j]
    don1[i,j]<-dontrans(donn,somcol,somrow,somme,nbrsp)
    }
    }
    #calculation of the "covariance" matrix
    covar1<-matrix(rep(0,nbrrel*nbrrel),nbrrel,nbrrel)
    for(j1 in 1:nbrrel)
    {
    hc1<-hc[j1]
    for(j2 in 1:nbrrel)
    {
    hc2<-hc[j2]
    for (i in 1:nbrsp)
    {
    donn1<-don1[i,j1]
    donn2<- don1[i,j2]
    covar1[j1,j2]= covar1[j1,j2]+covarf(donn1,donn2,hc1,hc2,nbrsp,somme)
    }
    }
    }

    Voici le script de la fonction

    dontrans<-function(donn,somcol,somrow,somme,nbrsp)
    {
    resul<- (donn/somcol-somrow/somme)*nbrsp
    }

    Merci d'avance pour les conseils

    Guy

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Bonjour !

vous venez souvent ?

Identifiez-vous pour avoir accès à toutes les fontionnalités !

Aide mémoire off'R ;)

Enregistrez vous pour recevoir gratuitement mes fiches “aide mémoire” (ou cheat sheets) qui vous permettront de réaliser facilement les principales analyses biostatistiques avec le logiciel R et pour être informés des mises à jour du site.