Dlaczego „vapply” jest bezpieczniejsze niż „sapply”?

84

Dokumentacja mówi

vapplyjest podobny do sapply, ale ma wstępnie określony typ wartości zwracanej, więc może być [...] bezpieczniejszy w użyciu.

Czy mógłbyś wyjaśnić, dlaczego jest to ogólnie bezpieczniejsze, może podając przykłady?


PS: Znam odpowiedź i staram się już tego unikać sapply. Chciałbym tylko, żeby była tu miła odpowiedź, więc mogę wskazać ją moim współpracownikom. Prosimy o brak odpowiedzi „przeczytaj instrukcję”.

flodel
źródło
1
Jest bardziej przewidywalny, dzięki czemu kod jest mniej niejednoznaczny i bardziej niezawodny. Jest to istotne zwłaszcza w przypadku większych projektów, na przykład dużego pakietu.
Paul Hiemstra
1
Przykłady instrukcji vapply dla FUN.VALUE są bardzo złożone i onieśmielające dla użytkowników sapply.
jsta

Odpowiedzi:

73

Jak już wspomniano, vapplyrobi dwie rzeczy:

  • Nieznaczna poprawa prędkości
  • Poprawia spójność, zapewniając ograniczone kontrole typu powrotu.

Druga kwestia to większa zaleta, ponieważ pomaga wychwycić błędy, zanim się pojawią, i prowadzi do bardziej niezawodnego kodu. To sprawdzanie wartości zwracanej można przeprowadzić osobno, używając, sapplya następnie, stopifnotaby upewnić się, że wartości zwracane są zgodne z oczekiwanymi, ale vapplyjest trochę łatwiejsze (jeśli jest bardziej ograniczone, ponieważ niestandardowy kod sprawdzania błędów może sprawdzać wartości w granicach itp.) ).

Oto przykład vapplyupewnienia się, że wynik jest zgodny z oczekiwaniami. Jest to analogia do czegoś, nad czym właśnie pracowałem podczas skrobania plików PDF, gdzie findDużyłbym plikuaby dopasować wzorzec w nieprzetworzonych danych tekstowych (np. miałbym listę splitwedług encji i wyrażenie regularne do dopasowania adresów w każdej encji. Czasami plik PDF był konwertowany poza kolejnością i były dwa adresy dla podmiot, który spowodował zło).

> 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

Jak mówię moim studentom, częścią stania się programistą jest zmiana sposobu myślenia z „błędy są irytujące” na „błędy są moim przyjacielem”.

Wejścia o zerowej długości
Jedną z powiązanych kwestii jest to, że jeśli długość danych wejściowych wynosi zero, sapplyzawsze zwróci pustą listę, niezależnie od typu wejścia. Porównać:

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)

Dzięki temu vapplymasz gwarancję określonego typu danych wyjściowych, więc nie musisz pisać dodatkowych sprawdzeń dla danych wejściowych o zerowej długości.

Benchmarki

vapply może być nieco szybszy, ponieważ już wie, w jakim formacie powinien oczekiwać wyników.

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
źródło
15

Dodatkowe naciśnięcia klawiszy vapplymogą zaoszczędzić czas późniejszego debugowania mylących wyników. Jeśli funkcja, którą wywołujesz, może zwracać różne typy danych, z vapplypewnością należy jej użyć.

Jednym z przykładów, które przychodzi na myśl byłoby sqlQueryw RODBCopakowaniu. Jeśli podczas wykonywania zapytania wystąpi błąd, ta funkcja zwraca characterwektor z komunikatem. Załóżmy na przykład, że próbujesz wykonać iterację po wektorze nazw tabel tnamesi wybierz maksymalną wartość z kolumny numerycznej „NumCol” w każdej tabeli za pomocą:

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

Jeśli wszystkie nazwy tabel są prawidłowe, dałoby to numericwektor. Ale jeśli zdarzy się, że jedna z nazw tabel zmieni się w bazie danych i zapytanie nie powiedzie się, wyniki zostaną przekształcone w tryb character. Jednak użycie vapplywith FUN.VALUE=numeric(1)zatrzyma błąd w tym miejscu i zapobiegnie jego pojawieniu się gdzieś w dół - lub, co gorsza, wcale.

Matthew Plourde
źródło
13

Jeśli zawsze chcesz, aby wynik był czymś szczególnym ... np. Wektorem logicznym. vapplyupewnia się, że tak się dzieje, ale sapplyniekoniecznie to robi.

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

is.logical(a)
is.logical(b)
user1317221_G
źródło
4
Myślę, że najbardziej oczywistą rzeczą jest to logical(1)w tym przypadku, ponieważ FALSE wygląda na to, że ustawia opcję na „OFF” zamiast określać typ
latająca owca