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 :
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.
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 !
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
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
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"
Si ces fonctions *apply() v
ous 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
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.
3 Responses
“Bel article d’apprentissage! Aymeric Inpong”
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
Je n’arrive pas á m’inscrire