Wir sind es also gewohnt, jedem neuen R-Benutzer zu sagen, dass " applynicht vektorisiert ist, sehen Sie sich den Patrick Burns R Inferno Circle 4 an ", in dem steht (ich zitiere):
Ein häufiger Reflex ist die Verwendung einer Funktion in der Apply-Familie. Dies ist keine Vektorisierung, sondern ein Schleifenverstecken . Die Apply-Funktion hat eine for-Schleife in ihrer Definition. Die Lapply-Funktion vergräbt die Schleife, aber die Ausführungszeiten entsprechen in der Regel ungefähr einer expliziten for-Schleife.
In der Tat zeigt ein kurzer Blick auf den applyQuellcode die Schleife:
grep("for", capture.output(getAnywhere("apply")), value = TRUE)
## [1] " for (i in 1L:d2) {" " else for (i in 1L:d2) {"
Ok, aber ein Blick auf lapplyoder vapplyzeigt tatsächlich ein ganz anderes Bild:
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>
Anscheinend forversteckt sich dort keine R- Schleife, sondern sie rufen die interne C-Schreibfunktion auf.
Ein kurzer Blick in das Kaninchenloch zeigt so ziemlich das gleiche Bild
Nehmen wir colMeanszum Beispiel die Funktion, die niemals beschuldigt wurde, nicht vektorisiert worden zu sein
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>
Huh? Es ruft auch nur an, .Internal(colMeans(...was wir auch im Kaninchenbau finden können . Wie unterscheidet sich das von .Internal(lapply(..?
Tatsächlich zeigt ein schneller Benchmark, dass eine Schleife für einen großen Datensatz sapplynicht schlechter colMeansund viel besser als eine forSchleife ist
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
Mit anderen Worten, ist es richtig, das zu sagen lapplyund vapply tatsächlich vektorisiert zu sein (im Vergleich zu applyeiner forSchleife, die auch aufruft lapply), und was wollte Patrick Burns wirklich sagen?
*applyFunktionen rufen wiederholt R-Funktionen auf, wodurch sie Schleifen bilden. In Bezug auf die gute Leistung vonsapply(m, mean): Möglicherweise wird der C-Code vonlapplynur einmal versendet und die Methode dann wiederholt aufgerufen?mean.defaultist ziemlich optimiert.