Wir sind es also gewohnt, jedem neuen R-Benutzer zu sagen, dass " apply
nicht 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 apply
Quellcode 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 lapply
oder vapply
zeigt 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 for
versteckt 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 colMeans
zum 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 sapply
nicht schlechter colMeans
und viel besser als eine for
Schleife 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 lapply
und vapply
tatsächlich vektorisiert zu sein (im Vergleich zu apply
einer for
Schleife, die auch aufruft lapply
), und was wollte Patrick Burns wirklich sagen?
*apply
Funktionen rufen wiederholt R-Funktionen auf, wodurch sie Schleifen bilden. In Bezug auf die gute Leistung vonsapply(m, mean)
: Möglicherweise wird der C-Code vonlapply
nur einmal versendet und die Methode dann wiederholt aufgerufen?mean.default
ist ziemlich optimiert.