Perché "vapply" è più sicuro di "sapply"?


84

La documentazione dice

vapplyè simile a sapply, ma ha un tipo di valore di ritorno pre-specificato, quindi può essere [...] più sicuro da usare.

Potresti spiegare perché è generalmente più sicuro, magari fornendo degli esempi?


PS: conosco la risposta e già tendo ad evitare sapply. Vorrei solo che ci fosse una bella risposta qui su SO, così posso indicarla ai miei colleghi. Per favore, nessuna risposta "leggi il manuale".


1
È più prevedibile, rendendo il codice meno ambiguo e più robusto. Particolarmente nei progetti più grandi, diciamo un grande pacchetto, questo è rilevante.
Paul Hiemstra

1
Gli esempi di manuali vapply per FUN.VALUE sono molto complessi e intimidatori per gli utenti scemi.
jsta

Risposte:


73

Come è già stato notato, vapplyfa due cose:

  • Leggero miglioramento della velocità
  • Migliora la coerenza fornendo controlli limitati del tipo restituito.

Il secondo punto è il vantaggio maggiore, poiché aiuta a rilevare gli errori prima che si verifichino e porta a un codice più robusto. Questo controllo del valore restituito può essere eseguito separatamente utilizzando sapplyseguito da stopifnotper assicurarsi che i valori restituiti siano coerenti con ciò che ci si aspettava, ma vapplyè un po 'più semplice (se più limitato, poiché il codice di controllo degli errori personalizzato potrebbe verificare i valori entro i limiti, ecc. ).

Ecco un esempio per vapplyassicurarti che il risultato sia quello previsto. Questo è parallelo a qualcosa su cui stavo lavorando durante lo scraping del PDF, dove findDuserei un fileper abbinare un modello in dati di testo non elaborati (ad esempio, avrei un elenco che era splitper entità e un'espressione regolare per abbinare gli indirizzi all'interno di ciascuna entità. A volte il PDF era stato convertito fuori ordine e c'erano due indirizzi per un entità, che ha causato cattiveria).

> input1 <- list( letters[1:5], letters[3:12], letters[c(5,2,4,7,1)] )
> input2 <- list( letters[1:5], letters[3:12], letters[c(2,5,4,7,15,4)] )
> findD <- function(x) x[x=="d"]
> sapply(input1, findD )
[1] "d" "d" "d"
> sapply(input2, findD )
[[1]]
[1] "d"

[[2]]
[1] "d"

[[3]]
[1] "d" "d"

> vapply(input1, findD, "" )
[1] "d" "d" "d"
> vapply(input2, findD, "" )
Error in vapply(input2, findD, "") : values must be length 1,
 but FUN(X[[3]]) result is length 2

Come dico ai miei studenti, parte del diventare un programmatore è cambiare la propria mentalità da "gli errori sono fastidiosi" a "gli errori sono miei amici".

Input di lunghezza zero
Un punto correlato è che se la lunghezza di input è zero, sapplyrestituirà sempre un elenco vuoto, indipendentemente dal tipo di input. Confrontare:

sapply(1:5, identity)
## [1] 1 2 3 4 5
sapply(integer(), identity)
## list()    
vapply(1:5, identity)
## [1] 1 2 3 4 5
vapply(integer(), identity)
## integer(0)

Con vapply, si ha la garanzia di avere un particolare tipo di output, quindi non è necessario scrivere controlli aggiuntivi per input di lunghezza zero.

Punti di riferimenti

vapply può essere un po 'più veloce perché sa già in quale formato dovrebbe aspettarsi i risultati.

input1.long <- rep(input1,10000)

library(microbenchmark)
m <- microbenchmark(
  sapply(input1.long, findD ),
  vapply(input1.long, findD, "" )
)
library(ggplot2)
library(taRifx) # autoplot.microbenchmark is moving to the microbenchmark package in the next release so this should be unnecessary soon
autoplot(m)

autoplot


15

I colpi di tasto aggiuntivi coinvolti vapplypotrebbero farti risparmiare tempo nel debug di risultati confusi in seguito. Se la funzione che stai chiamando può restituire diversi tipi di dati, vapplydovrebbe essere sicuramente usata.

Un esempio che mi viene in mente sarebbe sqlQuerynel RODBCpacchetto. Se si verifica un errore durante l'esecuzione di una query, questa funzione restituisce un charactervettore con il messaggio. Quindi, ad esempio, supponiamo che tu stia tentando di iterare su un vettore di nomi di tabelle tnamese selezionare il valore massimo dalla colonna numerica "NumCol" in ciascuna tabella con:

sapply(tnames, 
   function(tname) sqlQuery(cnxn, paste("SELECT MAX(NumCol) FROM", tname))[[1]])

Se tutti i nomi di tabella sono validi, ciò risulterebbe in un numericvettore. Ma se uno dei nomi di tabella cambia nel database e la query non riesce, i risultati verranno forzati in modalità character. L'uso vapplycon FUN.VALUE=numeric(1), tuttavia, interromperà l'errore qui e impedirà che compaia da qualche parte lungo la linea --- o peggio, per niente.


13

Se vuoi sempre che il tuo risultato sia qualcosa in particolare ... ad es. Un vettore logico. vapplysi assicura che ciò accada, ma sapplynon necessariamente.

a<-vapply(NULL, is.factor, FUN.VALUE=logical(1))
b<-sapply(NULL, is.factor)

is.logical(a)
is.logical(b)

4
Penso che la cosa più ovvia da fare sia logical(1)in questo caso, poiché sembra che FALSE imposti un'opzione su "OFF" invece di specificare un tipo
pecora volante
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.