Nous avons donc l'habitude de dire à chaque nouvel utilisateur R que " apply
n'est pas vectorisé, regardez le Patrick Burns R Inferno Circle 4 " qui dit (je cite):
Un réflexe courant consiste à utiliser une fonction de la famille apply. Ce n'est pas de la vectorisation, c'est un masquage de boucle . La fonction apply a une boucle for dans sa définition. La fonction lapply enterre la boucle, mais les temps d'exécution ont tendance à être à peu près égaux à une boucle for explicite.
En effet, un rapide coup d'œil sur le apply
code source révèle la boucle:
grep("for", capture.output(getAnywhere("apply")), value = TRUE)
## [1] " for (i in 1L:d2) {" " else for (i in 1L:d2) {"
Ok jusqu'à présent, mais un regard lapply
ou vapply
révèle en fait une image complètement différente:
lapply
## function (X, FUN, ...)
## {
## FUN <- match.fun(FUN)
## if (!is.vector(X) || is.object(X))
## X <- as.list(X)
## .Internal(lapply(X, FUN))
## }
## <bytecode: 0x000000000284b618>
## <environment: namespace:base>
Donc, apparemment, il n'y a pas de for
boucle R cachée là-bas, ils appellent plutôt une fonction écrite interne en C.
Un rapide coup d' oeil dans le lapin trou révèle à peu près la même image
De plus, prenons la colMeans
fonction par exemple, qui n'a jamais été accusée de ne pas être vectorisée
colMeans
# function (x, na.rm = FALSE, dims = 1L)
# {
# if (is.data.frame(x))
# x <- as.matrix(x)
# if (!is.array(x) || length(dn <- dim(x)) < 2L)
# stop("'x' must be an array of at least two dimensions")
# if (dims < 1L || dims > length(dn) - 1L)
# stop("invalid 'dims'")
# n <- prod(dn[1L:dims])
# dn <- dn[-(1L:dims)]
# z <- if (is.complex(x))
# .Internal(colMeans(Re(x), n, prod(dn), na.rm)) + (0+1i) *
# .Internal(colMeans(Im(x), n, prod(dn), na.rm))
# else .Internal(colMeans(x, n, prod(dn), na.rm))
# if (length(dn) > 1L) {
# dim(z) <- dn
# dimnames(z) <- dimnames(x)[-(1L:dims)]
# }
# else names(z) <- dimnames(x)[[dims + 1]]
# z
# }
# <bytecode: 0x0000000008f89d20>
# <environment: namespace:base>
Hein? Il appelle aussi juste .Internal(colMeans(...
que nous pouvons également trouver dans le terrier du lapin . Alors, en quoi est-ce différent .Internal(lapply(..
?
En fait, un benchmark rapide révèle que les sapply
performances ne sont pas pires colMeans
et bien meilleures qu'une for
boucle pour un ensemble de données volumineuses
m <- as.data.frame(matrix(1:1e7, ncol = 1e5))
system.time(colMeans(m))
# user system elapsed
# 1.69 0.03 1.73
system.time(sapply(m, mean))
# user system elapsed
# 1.50 0.03 1.60
system.time(apply(m, 2, mean))
# user system elapsed
# 3.84 0.03 3.90
system.time(for(i in 1:ncol(m)) mean(m[, i]))
# user system elapsed
# 13.78 0.01 13.93
En d'autres termes, est-il correct de dire cela lapply
et vapply
sont en fait vectorisés (par rapport à apply
laquelle est une for
boucle qui appelle également lapply
) et que voulait vraiment dire Patrick Burns?
*apply
les fonctions appellent à plusieurs reprises les fonctions R, ce qui les rend boucles. En ce qui concerne les bonnes performances desapply(m, mean)
: Peut-être le code C de lalapply
méthode envoie-t-il une seule fois et appelle-t-il ensuite la méthode à plusieurs reprises?mean.default
est assez optimisé.