La famiglia di R sta applicando più dello zucchero sintattico?

riguardo al tempo di esecuzione e / o alla memoria.

Se questo non è vero, provalo con uno snippet di codice. Nota che l’accelerazione per vettorizzazione non conta. L’accelerazione deve venire dall’applicarsi ( tapply , sapply , …) stesso.

Le funzioni apply in R non offrono prestazioni migliorate rispetto ad altre funzioni di loop (ad esempio for ). Un’eccezione a questa è lapply che può essere un po ‘più veloce perché fa più lavoro nel codice C che in R (vedi questa domanda per un esempio di questo ).

Ma in generale, la regola è che dovresti usare una funzione apply per chiarezza, non per le prestazioni .

Vorrei aggiungere a questo che le funzioni applicative non hanno effetti collaterali , che è una distinzione importante quando si tratta di programmazione funzionale con R. Questo può essere sovrascritto usando assign o <<- , ma questo può essere molto pericoloso. Gli effetti collaterali rendono anche più difficile capire un programma poiché lo stato di una variabile dipende dalla cronologia.

Modificare:

Solo per enfatizzare questo con un banale esempio che calcola ricorsivamente la sequenza di Fibonacci; questo potrebbe essere eseguito più volte per ottenere una misura accurata, ma il punto è che nessuno dei metodi ha prestazioni significativamente diverse:

 > 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 

Modifica 2:

Per quanto riguarda l'uso di pacchetti paralleli per R (ad es. Rpvm, rmpi, snow), questi generalmente forniscono funzioni familiari apply (anche il pacchetto foreach è sostanzialmente equivalente, nonostante il nome). Ecco un semplice esempio della funzione sapply nella snow :

 library(snow) cl <- makeSOCKcluster(c("localhost","localhost")) parSapply(cl, 1:20, get("+"), 3) 

Questo esempio utilizza un cluster di socket, per il quale non è necessario installare alcun software aggiuntivo; altrimenti avrete bisogno di qualcosa come PVM o MPI (consultate la pagina di clustering di Tierney ). snow ha le seguenti funzioni applicabili:

 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, ...) 

È logico che apply funzioni di apply debbano essere utilizzate per l'esecuzione parallela poiché non hanno effetti collaterali . Quando si modifica un valore variabile all'interno di un ciclo for , viene impostato globalmente. D'altra parte, tutte apply funzioni di apply possono essere tranquillamente utilizzate in parallelo perché le modifiche sono locali alla chiamata di funzione (a meno che non si tenti di utilizzare assign o <<- , nel qual caso è ansible introdurre effetti collaterali). Inutile dire che è fondamentale prestare attenzione alle variabili locali e globali, specialmente quando si ha a che fare con l'esecuzione parallela.

Modificare:

Ecco un semplice esempio per dimostrare la differenza tra e per quanto riguarda gli effetti collaterali:

 > 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 

Nota come il df nell'ambiente genitore è alterato da for ma non *apply .

A volte l’accelerazione può essere notevole, ad esempio quando è necessario nidificare for-loops per ottenere la media in base a un raggruppamento di più di un fattore. Qui hai due approcci che ti danno lo stesso risultato esatto:

 set.seed(1) #for reproducability of the results # The data X <- rnorm(100000) Y <- as.factor(sample(letters[1:5],100000,replace=T)) Z <- as.factor(sample(letters[1:10],100000,replace=T)) # the function forloop that averages X over every combination of Y and Z forloop <- function(x,y,z){ # These ones are for optimization, so the functions #levels() and length() don't have to be called more than once. ylev <- levels(y) zlev <- levels(z) n <- length(ylev) p <- length(zlev) out <- matrix(NA,ncol=p,nrow=n) for(i in 1:n){ for(j in 1:p){ out[i,j] <- (mean(x[y==ylev[i] & z==zlev[j]])) } } rownames(out) <- ylev colnames(out) <- zlev return(out) } # Used on the generated data forloop(X,Y,Z) # The same using tapply tapply(X,list(Y,Z),mean) 

Entrambi danno esattamente lo stesso risultato, essendo una matrice 5 x 10 con le medie e le righe e le colonne con nome. Ma :

 > system.time(forloop(X,Y,Z)) user system elapsed 0.94 0.02 0.95 > system.time(tapply(X,list(Y,Z),mean)) user system elapsed 0.06 0.00 0.06 

Ecco qua. Cosa ho vinto? 😉

… e come ho appena scritto altrove, vapply è tuo amico! … è un po ‘sdolcinato, ma si specifica anche il tipo di valore di ritorno che lo rende molto più veloce.

 > system.time({z <- numeric(1e6); for(i in y) z[i] <- foo(i)}) user system elapsed 3.54 0.00 3.53 > system.time(z <- lapply(y, foo)) user system elapsed 2.89 0.00 2.91 > system.time(z <- vapply(y, foo, numeric(1))) user system elapsed 1.35 0.00 1.36 

Ho scritto altrove che un esempio come quello di Shane non mette realmente in risalto la differenza di prestazioni tra i vari tipi di syntax di looping perché il tempo viene speso interamente all’interno della funzione piuttosto che stressare effettivamente il ciclo. Inoltre, il codice confronta ingiustamente un ciclo for senza memoria e applica funzioni di famiglia che restituiscono un valore. Ecco un esempio leggermente diverso che sottolinea il punto.

 foo <- function(x) { x <- x+1 } y <- numeric(1e6) system.time({z <- numeric(1e6); for(i in y) z[i] <- foo(i)}) # user system elapsed # 4.967 0.049 7.293 system.time(z <- sapply(y, foo)) # user system elapsed # 5.256 0.134 7.965 system.time(z <- lapply(y, foo)) # user system elapsed # 2.179 0.126 3.301 

Se si prevede di salvare il risultato, applicare le funzioni familiari può essere molto più dello zucchero sintattico.

(la semplice lista di z è solo di 0.2 quindi il lapply è molto più veloce. Inizializzare la z nel ciclo for è abbastanza veloce perché sto dando la media delle ultime 5 di 6 esecuzioni così spostandole fuori dal sistema. difficilmente influenzano le cose)

Un'altra cosa da notare è che c'è un altro motivo per usare le funzioni familiari applicate indipendentemente dalle loro prestazioni, chiarezza o mancanza di effetti collaterali. Generalmente, for ciclo for promuove l'inserimento il più ansible all'interno del ciclo. Questo perché ogni ciclo richiede l'impostazione di variabili per memorizzare le informazioni (tra le altre possibili operazioni). Applica dichiarazioni tendono a essere di parte in altro modo. Spesso volte vuoi eseguire più operazioni sui tuoi dati, molti dei quali possono essere vettorizzati, ma alcuni potrebbero non essere in grado di essere. In R, a differenza di altri linguaggi, è meglio separare queste operazioni ed eseguire quelle che non sono vettorializzate in un'istruzione apply (o versione vettoriale della funzione) e quelle che sono vettorizzate come operazioni vettoriali reali. Questo spesso accelera enormemente le prestazioni.

Prendendo l'esempio di Joris Meys in cui sostituisce un loop tradizionale con una comoda funzione R, possiamo usarlo per mostrare l'efficienza della scrittura del codice in un modo più amichevole R per una simile accelerazione senza la funzione specializzata.

 set.seed(1) #for reproducability of the results # The data - copied from Joris Meys answer X <- rnorm(100000) Y <- as.factor(sample(letters[1:5],100000,replace=T)) Z <- as.factor(sample(letters[1:10],100000,replace=T)) # an R way to generate tapply functionality that is fast and # shows more general principles about fast R coding YZ <- interaction(Y, Z) XS <- split(X, YZ) m <- vapply(XS, mean, numeric(1)) m <- matrix(m, nrow = length(levels(Y))) rownames(m) <- levels(Y) colnames(m) <- levels(Z) m 

Questo si rivela molto più veloce del ciclo for e solo un po 'più lento della funzione tapply ottimizzata tapply . Non è perché vapply è molto più veloce rispetto for ma perché esegue solo un'operazione in ogni iterazione del ciclo. In questo codice tutto il resto è vettorializzato. In Joris Meys tradizionale for ciclo molte operazioni (7?) Si verificano in ogni iterazione e c'è un bel po 'di setup che deve essere eseguito. Nota anche quanto più compatto è questo rispetto alla versione for .

Quando si applicano le funzioni su sottoinsiemi di un vettore, tapply può essere molto più veloce di un ciclo for. Esempio:

 df <- data.frame(id = rep(letters[1:10], 100000), value = rnorm(1000000)) f1 <- function(x) tapply(x$value, x$id, sum) f2 <- function(x){ res <- 0 for(i in seq_along(l <- unique(x$id))) res[i] <- sum(x$value[x$id == l[i]]) names(res) <- l res } library(microbenchmark) > microbenchmark(f1(df), f2(df), times=100) Unit: milliseconds expr min lq median uq max neval f1(df) 28.02612 28.28589 28.46822 29.20458 32.54656 100 f2(df) 38.02241 41.42277 41.80008 42.05954 45.94273 100 

apply , tuttavia, nella maggior parte delle situazioni non fornisce alcun aumento di velocità, e in alcuni casi può essere anche molto più lento:

 mat <- matrix(rnorm(1000000), nrow=1000) f3 <- function(x) apply(x, 2, sum) f4 <- function(x){ res <- 0 for(i in 1:ncol(x)) res[i] <- sum(x[,i]) res } > microbenchmark(f3(mat), f4(mat), times=100) Unit: milliseconds expr min lq median uq max neval f3(mat) 14.87594 15.44183 15.87897 17.93040 19.14975 100 f4(mat) 12.01614 12.19718 12.40003 15.00919 40.59100 100 

Ma per queste situazioni abbiamo colSums e rowSums :

 f5 <- function(x) colSums(x) > microbenchmark(f5(mat), times=100) Unit: milliseconds expr min lq median uq max neval f5(mat) 1.362388 1.405203 1.413702 1.434388 1.992909 100