web-dev-qa-db-fra.com

Pourquoi «vapply» est-il plus sûr que «sapply»?

La documentation dit

vapply est similaire à sapply, mais a un type de valeur de retour prédéfini, il peut donc être plus sûr [...] à utiliser.

Pourriez-vous expliquer pourquoi il est généralement plus sûr, peut-être en fournissant des exemples?


P.S .: Je connais la réponse et j'ai déjà tendance à éviter sapply. Je souhaite juste qu'il y ait une bonne réponse ici sur SO afin que je puisse pointer mes collègues vers elle. S'il vous plaît, pas de réponse "lire le manuel".

81
flodel

Comme cela a déjà été noté, vapply fait deux choses:

  • Légère amélioration de la vitesse
  • Améliore la cohérence en fournissant des contrôles de type de retour limités.

Le deuxième point est le plus grand avantage, car il permet de détecter les erreurs avant qu'elles ne se produisent et conduit à un code plus robuste. Cette vérification de la valeur de retour peut être effectuée séparément en utilisant sapply suivi de stopifnot pour vous assurer que les valeurs de retour sont cohérentes avec ce que vous attendiez, mais vapply est un peu plus facile ( s'il est plus limité, car le code de vérification d'erreur personnalisé peut vérifier les valeurs dans les limites, etc.).

Voici un exemple de vapply garantissant que votre résultat est comme prévu. Cela correspond à quelque chose sur lequel je travaillais tout en PDF scraping, où findD utiliserait un regex pour faire correspondre un modèle dans les données de texte brut (par exemple, je aurait une liste qui était split par entité, et une expression régulière pour faire correspondre les adresses au sein de chaque entité. Parfois, le PDF avait été converti dans le désordre et il y aurait deux adresses pour une entité, ce qui a causé la méchanceté).

> 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

Comme je le dis à mes élèves, une partie de la programmation consiste à changer votre état d'esprit de "les erreurs sont ennuyeuses" à "les erreurs sont mon ami".

Entrées de longueur nulle
Un point connexe est que si la longueur d'entrée est nulle, sapply renverra toujours une liste vide, quel que soit le type d'entrée. Comparer:

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)

Avec vapply, vous avez la garantie d'avoir un type de sortie particulier, vous n'avez donc pas besoin d'écrire des vérifications supplémentaires pour les entrées de longueur nulle.

Repères

vapply peut être un peu plus rapide car il sait déjà dans quel format il devrait attendre les résultats.

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

68
Ari B. Friedman

Les frappes de touches supplémentaires impliquées avec vapply pourraient vous faire gagner du temps lors du débogage de résultats confus ultérieurement. Si la fonction que vous appelez peut renvoyer différents types de données, vapply doit certainement être utilisé.

Un exemple qui me vient à l'esprit serait sqlQuery dans le package RODBC. S'il y a une erreur lors de l'exécution d'une requête, cette fonction renvoie un vecteur character avec le message. Par exemple, supposons que vous essayez d'itérer sur un vecteur de noms de table tnames et sélectionnez la valeur maximale dans la colonne numérique 'NumCol' de chaque table avec:

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

Si tous les noms de table sont valides, cela entraînerait un vecteur numeric. Mais si l'un des noms de table change dans la base de données et que la requête échoue, les résultats vont être contraints en mode character. Cependant, l'utilisation de vapply avec FUN.VALUE=numeric(1) arrêtera l'erreur ici et l'empêchera d'apparaître quelque part sur la ligne --- ou pire, pas du tout.

15
Matthew Plourde

Si vous voulez toujours que votre résultat soit quelque chose de particulier ... par exemple. un vecteur logique. vapply s'assure que cela se produit mais sapply ne le fait pas nécessairement.

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

is.logical(a)
is.logical(b)
13
user1317221_G