Les apply
fonctions de R ne fournissent pas de performances améliorées par rapport aux autres fonctions de bouclage (par exemple for
). Une exception à cela est ce lapply
qui peut être un peu plus rapide car cela fonctionne plus en code C qu'en R (voir cette question pour un exemple de ceci ).
Mais en général, la règle est que vous devez utiliser une fonction Apply pour plus de clarté, pas pour les performances .
J'ajouterais à cela que les fonctions apply n'ont aucun effet secondaire , ce qui est une distinction importante en matière de programmation fonctionnelle avec R. Cela peut être remplacé en utilisant assign
ou <<-
, mais cela peut être très dangereux. Les effets secondaires rendent également un programme plus difficile à comprendre car l'état d'une variable dépend de l'historique.
Éditer:
Juste pour souligner cela avec un exemple trivial qui calcule récursivement la séquence de Fibonacci; cela peut être exécuté plusieurs fois pour obtenir une mesure précise, mais le fait est qu'aucune des méthodes n'a des performances significativement différentes:
> fibo <- function(n) {
+ if ( n < 2 ) n
+ else fibo(n-1) + fibo(n-2)
+ }
> system.time(for(i in 0:26) fibo(i))
user system elapsed
7.48 0.00 7.52
> system.time(sapply(0:26, fibo))
user system elapsed
7.50 0.00 7.54
> system.time(lapply(0:26, fibo))
user system elapsed
7.48 0.04 7.54
> library(plyr)
> system.time(ldply(0:26, fibo))
user system elapsed
7.52 0.00 7.58
Modifier 2:
Concernant l'utilisation des packages parallèles pour R (par exemple rpvm, rmpi, snow), ceux-ci fournissent généralement apply
des fonctions familiales (même le foreach
package est essentiellement équivalent, malgré le nom). Voici un exemple simple de la sapply
fonction dans snow
:
library(snow)
cl <- makeSOCKcluster(c("localhost","localhost"))
parSapply(cl, 1:20, get("+"), 3)
Cet exemple utilise un cluster de sockets, pour lequel aucun logiciel supplémentaire ne doit être installé; sinon vous aurez besoin de quelque chose comme PVM ou MPI (voir la page de clustering de Tierney ). snow
a les fonctions d'application suivantes:
parLapply(cl, x, fun, ...)
parSapply(cl, X, FUN, ..., simplify = TRUE, USE.NAMES = TRUE)
parApply(cl, X, MARGIN, FUN, ...)
parRapply(cl, x, fun, ...)
parCapply(cl, x, fun, ...)
Il est logique que les apply
fonctions soient utilisées pour une exécution parallèle car elles n'ont aucun effet secondaire . Lorsque vous modifiez une valeur de variable dans une for
boucle, elle est définie globalement. D'autre part, toutes les apply
fonctions peuvent être utilisées en parallèle en toute sécurité car les modifications sont locales à l'appel de fonction (à moins que vous n'essayiez d'utiliser assign
ou <<-
, auquel cas vous pouvez introduire des effets secondaires). Inutile de dire qu'il est essentiel de faire attention aux variables locales et globales, en particulier lors de l'exécution parallèle.
Éditer:
Voici un exemple trivial pour démontrer la différence entre for
et en *apply
ce qui concerne les effets secondaires:
> df <- 1:10
> # *apply example
> lapply(2:3, function(i) df <- df * i)
> df
[1] 1 2 3 4 5 6 7 8 9 10
> # for loop example
> for(i in 2:3) df <- df * i
> df
[1] 6 12 18 24 30 36 42 48 54 60
Notez comment le df
dans l'environnement parent est modifié par for
mais pas *apply
.
apply
famille de fonctions. Par conséquent, la structuration des programmes pour qu'ils utilisent apply leur permet d'être parallélisés à un coût marginal très faible.snowfall
paquet et d'essayer les exemples dans leur vignette.snowfall
s'appuie sur lesnow
paquet et fait abstraction des détails de la parallélisation, ce qui rend plus simple l'exécution deapply
fonctions parallélisées .foreach
depuis, il est devenu disponible et semble être très demandé sur SO.lapply
est "un peu plus rapide" qu'unefor
boucle. Cependant, là, je ne vois rien qui le suggère. Vous mentionnez seulement quelapply
c'est plus rapide quesapply
, ce qui est un fait bien connu pour d'autres raisons (sapply
essaie de simplifier la sortie et doit donc faire beaucoup de vérification de la taille des données et des conversions potentielles). Rien de lié àfor
. Est-ce que je manque quelque chose?Parfois, l'accélération peut être importante, comme lorsque vous devez imbriquer des boucles for pour obtenir la moyenne basée sur un regroupement de plusieurs facteurs. Ici, vous avez deux approches qui vous donnent exactement le même résultat:
Les deux donnent exactement le même résultat, étant une matrice 5 x 10 avec les moyennes et les lignes et colonnes nommées. Mais :
Voilà. Qu'est-ce que j'ai gagné? ;-)
la source
*apply
est parfois plus rapide. Mais je pense que le point le plus important est les effets secondaires (mis à jour ma réponse avec un exemple).data.table
c'est encore plus rapide et je pense "plus facile".library(data.table)
dt<-data.table(X,Y,Z,key=c("Y,Z"))
system.time(dt[,list(X_mean=mean(X)),by=c("Y,Z")])
tapply
est une fonction spécialisée pour une tâche spécifique, c'est pourquoi elle est plus rapide qu'une boucle for. Il ne peut pas faire ce que peut faire une boucle for (alors que les standards leapply
peuvent). Vous comparez des pommes avec des oranges.... et comme je viens de l'écrire ailleurs, vapply est votre ami! ... c'est comme sapply, mais vous spécifiez également le type de valeur de retour qui le rend beaucoup plus rapide.
Mise à jour du 1er janvier 2020:
la source
for
les boucles sont plus rapides sur mon ordinateur Windows 10 à 2 cœurs. Je l'ai fait avec des5e6
éléments - une boucle était de 2,9 secondes contre 3,1 secondes pourvapply
.J'ai écrit ailleurs qu'un exemple comme celui de Shane ne met pas vraiment l'accent sur la différence de performance entre les différents types de syntaxe de boucle car le temps est entièrement passé dans la fonction plutôt que de stresser réellement la boucle. De plus, le code compare injustement une boucle for sans mémoire avec des fonctions de famille apply qui renvoient une valeur. Voici un exemple légèrement différent qui met l'accent sur ce point.
Si vous prévoyez d'enregistrer le résultat, appliquer des fonctions familiales peut être bien plus que du sucre syntaxique.
(la simple désinscription de z n'est que de 0,2 s, donc le lapply est beaucoup plus rapide. L'initialisation du z dans la boucle for est assez rapide car je donne la moyenne des 5 dernières des 6 exécutions de manière à ce que le déplacement en dehors du système soit n'affecte guère les choses)
Une autre chose à noter cependant est qu'il existe une autre raison d'utiliser les fonctions familiales d'application indépendamment de leurs performances, de leur clarté ou de l'absence d'effets secondaires. Une
for
boucle favorise généralement la mise autant que possible dans la boucle. En effet, chaque boucle nécessite la configuration de variables pour stocker des informations (entre autres opérations possibles). Les déclarations Apply ont tendance à être biaisées dans l'autre sens. Souvent, vous souhaitez effectuer plusieurs opérations sur vos données, dont plusieurs peuvent être vectorisées mais certaines peuvent ne pas l'être. Dans R, contrairement aux autres langages, il est préférable de séparer ces opérations et d'exécuter celles qui ne sont pas vectorisées dans une instruction apply (ou une version vectorisée de la fonction) et celles qui sont vectorisées comme de véritables opérations vectorielles. Cela accélère souvent considérablement les performances.Prenant l'exemple de Joris Meys où il remplace une boucle for traditionnelle par une fonction R pratique, nous pouvons l'utiliser pour montrer l'efficacité de l'écriture de code d'une manière plus conviviale pour R pour une accélération similaire sans la fonction spécialisée.
Cela finit par être beaucoup plus rapide que la
for
boucle et juste un peu plus lent que latapply
fonction optimisée intégrée. Ce n'est pas parce quevapply
c'est tellement plus rapide quefor
mais parce qu'il n'effectue qu'une seule opération à chaque itération de la boucle. Dans ce code, tout le reste est vectorisé. Dans lafor
boucle traditionnelle de Joris Meys, de nombreuses opérations (7?) Se produisent à chaque itération et il y a pas mal de configuration juste pour qu'elle s'exécute. Notez également combien il est plus compact que lafor
version.la source
2.798 0.003 2.803; 4.908 0.020 4.934; 1.498 0.025 1.528
, et vapply est encore mieux:1.19 0.00 1.19
sapply
50% plus lentfor
etlapply
deux fois plus rapide.y
sur1:1e6
, nonnumeric(1e6)
(un vecteur de zéros). Essayer d'allouerfoo(0)
àz[0]
plusieurs reprises n'illustre pas bien unefor
utilisation de boucle typique . Le message est par ailleurs parfait.Lors de l'application de fonctions sur des sous-ensembles d'un vecteur, cela
tapply
peut être assez rapide qu'une boucle for. Exemple:apply
, cependant, dans la plupart des cas, il ne fournit aucune augmentation de vitesse et, dans certains cas, peut être encore plus lent:Mais pour ces situations, nous avons
colSums
etrowSums
:la source
microbenchmark
c'est beaucoup plus précis quesystem.time
. Si vous essayez de comparersystem.time(f3(mat))
etsystem.time(f4(mat))
vous obtiendrez des résultats différents presque à chaque fois. Parfois, seul un test de référence approprié est capable de montrer la fonction la plus rapide.