Warum ist "vapply" sicherer als "sapply"?

83

Die Dokumentation sagt

vapplyähnelt sapply, hat jedoch einen vorgegebenen Rückgabewert, sodass die Verwendung [...] sicherer sein kann.

Könnten Sie bitte erläutern, warum es im Allgemeinen sicherer ist, und vielleicht Beispiele nennen?


PS: Ich kenne die Antwort und vermeide sie bereits sapply. Ich wünschte nur, es gäbe hier auf SO eine nette Antwort, damit ich meine Kollegen darauf hinweisen kann. Bitte keine Antwort "Handbuch lesen".

flodel
quelle
1
Es ist vorhersehbarer, wodurch der Code weniger mehrdeutig und robuster wird. Dies ist insbesondere bei größeren Projekten, beispielsweise einem großen Paket, relevant.
Paul Hiemstra
1
Die vapply manuellen Beispiele für FUN.VALUE sind sehr komplex und einschüchternd für sapply Benutzer.
Jsta

Antworten:

72

Wie bereits erwähnt, werden vapplyzwei Dinge ausgeführt:

  • Leichte Geschwindigkeitsverbesserung
  • Verbessert die Konsistenz durch begrenzte Überprüfung der Rückgabetypen.

Der zweite Punkt ist der größere Vorteil, da er hilft, Fehler zu erkennen, bevor sie auftreten, und zu robusterem Code führt. Diese Rückgabewertprüfung kann separat durchgeführt werden, indem sapplygefolgt von verwendet wird stopifnot, um sicherzustellen, dass die Rückgabewerte mit Ihren Erwartungen übereinstimmen. Sie ist jedoch vapplyetwas einfacher (wenn sie eingeschränkter sind, da der benutzerdefinierte Fehlerprüfcode nach Werten innerhalb von Grenzen usw. suchen kann). ).

Hier ist ein Beispiel, wie Sie vapplysicherstellen können, dass Ihr Ergebnis den Erwartungen entspricht. Dies entspricht etwas, an dem ich gerade beim PDF-Scraping gearbeitet habe, wo findDeinZum Abgleichen eines Musters in Rohtextdaten (z. B. hätte ich eine Liste splitnach Entitäten und einen regulären Ausdruck zum Abgleichen von Adressen innerhalb jeder Entität. Gelegentlich wurde das PDF nicht in der richtigen Reihenfolge konvertiert, und es gab zwei Adressen für eine Entität Entität, die Schlechtigkeit verursachte).

> 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

Wie ich meinen Schülern erzähle, besteht ein Teil des Werdens eines Programmierers darin, Ihre Einstellung von "Fehler sind ärgerlich" zu "Fehler sind mein Freund" zu ändern.

Eingaben
mit der Länge Null Ein verwandter Punkt ist, dass bei einer Eingangslänge von Null sapplyunabhängig vom Eingabetyp immer eine leere Liste zurückgegeben wird. Vergleichen Sie:

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)

Mit vapplyhaben Sie garantiert einen bestimmten Ausgabetyp, sodass Sie keine zusätzlichen Prüfungen für Eingaben mit der Länge Null schreiben müssen.

Benchmarks

vapply kann etwas schneller sein, da es bereits weiß, in welchem ​​Format die Ergebnisse erwartet werden sollen.

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

Ari B. Friedman
quelle
15

Durch die zusätzlichen Tastenanschläge können vapplySie später Zeit beim Debuggen verwirrender Ergebnisse sparen. Wenn die von Ihnen aufgerufene Funktion unterschiedliche Datentypen zurückgeben kann, vapplysollte sie auf jeden Fall verwendet werden.

Ein Beispiel, das mir sqlQueryin den Sinn kommt, ist das RODBCPaket. Wenn beim Ausführen einer Abfrage ein Fehler auftritt, gibt diese Funktion einen characterVektor mit der Nachricht zurück. tnamesAngenommen, Sie möchten einen Vektor von Tabellennamen durchlaufen und den Maximalwert aus der numerischen Spalte 'NumCol' in jeder Tabelle auswählen mit:

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

Wenn alle Tabellennamen gültig sind, würde dies zu einem numericVektor führen. Wenn sich jedoch einer der Tabellennamen in der Datenbank ändert und die Abfrage fehlschlägt, werden die Ergebnisse in den Modus gezwungen character. Die Verwendung vapplymit FUN.VALUE=numeric(1)stoppt jedoch den Fehler hier und verhindert, dass er irgendwo auf der ganzen Linie auftaucht - oder schlimmer noch, überhaupt nicht.

Matthew Plourde
quelle
13

Wenn Sie immer möchten, dass Ihr Ergebnis etwas Besonderes ist ... z. B. ein logischer Vektor. vapplystellt sicher, dass dies geschieht, sapplytut dies aber nicht unbedingt.

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

is.logical(a)
is.logical(b)
user1317221_G
quelle
4
Ich denke, das offensichtlichste, was zu tun ist, ist logical(1)in diesem Fall, da FALSE so aussieht, als würde es eine Option auf "OFF" setzen, anstatt einen Typ anzugeben
fliegende Schafe