Dlaczego "vapply" jest bezpieczniejsze niż "sapply"?

Dokumentacja mówi

vapply jest podobna do sapply, ale ma wstępnie określony typ zwracanej wartości, więc może być bezpieczniejsza [...] do użycia.

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

P. S.: znam odpowiedź i już mam tendencję do unikania sapply. Szkoda tylko, że nie ma tu ładnej odpowiedzi, żebym mógł wskazać współpracowników. Proszę, żadnej odpowiedzi "przeczytaj instrukcję".

Author: MichaelChirico, 2012-09-09

3 answers

Jak już wspomniano, vapply robi dwie rzeczy:

  • nieznaczna poprawa prędkości
  • poprawia spójność poprzez zapewnienie ograniczonych kontroli typu zwrotu.

Druga kwestia to większa zaleta, ponieważ pomaga wychwycić błędy, zanim się pojawią i prowadzi do bardziej solidnego kodu. To sprawdzanie wartości zwracanej może być wykonane oddzielnie za pomocą sapply, a następnie stopifnot, aby upewnić się, że wartości zwracane są zgodne z oczekiwanymi wartościami, ale vapply jest trochę łatwiejsze (jeśli bardziej ograniczone, ponieważ niestandardowy kod sprawdzania błędów może sprawdzać wartości w granicach, itp.).

Oto przykład vapply zapewnienia, że wynik jest zgodny z oczekiwaniami. To równoległe coś, nad czym właśnie pracowałem podczas skrobania plików PDF, gdzie findDużywałoby regex do dopasowania wzorca w surowych danych tekstowych (np. miałbym listę split według encji i regex do dopasowania adresów w każdej encji. Czasami plik PDF był konwertowany poza kolejnością i nie było dwa adresy dla podmiotu, co spowodowało 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ą zostania programistą jest zmiana sposobu myślenia z "błędy są denerwujące" na " błędy są moim przyjacielem."

Wejścia o zerowej długości
Jednym z powiązanych punktów jest to, że jeśli długość wejścia wynosi zero, sapply zawsze zwróci pustą listę, niezależnie od typu wejścia. Porównaj:

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)

Z vapply, masz gwarancję, że masz określony typ wyjścia, więc nie musisz wypisuje dodatkowe kontrole dla wejść 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

 59
Author: Ari B. Friedman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-08 11:00:47

Dodatkowe naciski klawiszy związane z {[1] } mogą zaoszczędzić czas na debugowaniu mylących wyników później. Jeśli wywołana funkcja może zwracać różne typy danych, vapply powinna być z pewnością użyta.

Jeden przykład, który przychodzi na myśl, to sqlQuery w RODBC pakiecie. Jeśli wystąpi błąd podczas wykonywania zapytania, funkcja ta zwraca wektor character z Komunikatem. Na przykład, powiedzmy, że próbujesz iterować nad wektorem nazw tabel tnames i wybrać wartość maksymalną z liczby kolumna "NumCol" w każdej tabeli zawierająca:

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

Jeśli wszystkie nazwy tabel są poprawne, spowoduje to powstanie wektora numeric. Ale jeśli jedna z nazw tabel zmieni się w bazie danych i zapytanie nie powiedzie się, wyniki zostaną wymuszone w trybie character. Jednak użycie vapply z FUN.VALUE=numeric(1) zatrzyma błąd i zapobiegnie jego pojawieniu się gdzieś w dół linii - - - lub, co gorsza, wcale.

 13
Author: Matthew Plourde,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-09-09 20:27:19

Jeśli zawsze chcesz, aby twój wynik był czymś szczególnym...np. wektor logiczny. vapply upewnia się, że tak się dzieje, ale sapply niekoniecznie tak się dzieje.

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

is.logical(a)
is.logical(b)
 12
Author: user1317221_G,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-20 22:44:37