Почему `vapply` безопаснее, чем`sapply`?

В документации сказано

vapply похож на sapply, но имеет предопределенный тип возвращаемого значения, поэтому его можно [...] использовать безопаснее.

Не могли бы вы уточнить, почему это в целом безопаснее, может быть, приводить примеры?


PS: я знаю ответ и уже стараюсь избегать sapply, Я просто хотел бы получить хороший ответ здесь, чтобы я мог указать своим коллегам на это. Пожалуйста, не "читайте руководство" ответ.

3 ответа

Решение

Как уже было отмечено, vapply делает две вещи:

  • Небольшое улучшение скорости
  • Улучшает согласованность, предоставляя ограниченные проверки типов возвращаемых данных.

Вторым моментом является большее преимущество, так как оно помогает отлавливать ошибки до их появления и приводит к более надежному коду. Эта проверка возвращаемого значения может быть выполнена отдельно с помощью sapply с последующим stopifnot чтобы убедиться, что возвращаемые значения соответствуют ожидаемым, но vapply немного проще (если более ограничено, так как пользовательский код проверки ошибок может проверять значения в пределах границ и т. д.).

Вот пример vapply обеспечение вашего результата, как и ожидалось. Это параллель с чем-то, над чем я только что работал, просматривая PDF, где findD будет использовать регулярное выражение для сопоставления с шаблоном в необработанных текстовых данных (например, у меня был бы список, который был split по сущности и регулярное выражение для сопоставления адресов в каждой сущности. Изредка PDF-файл конвертировался не по порядку, и для сущности было два адреса, что приводило к ошибкам).

> 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

Как я говорю своим студентам, часть становления программистом меняет ваше мышление с "ошибки раздражают" на "ошибки - мой друг".

Входы нулевой длины
Один связанный момент заключается в том, что если длина ввода равна нулю, sapply всегда будет возвращать пустой список, независимо от типа ввода. Для сравнения:

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)

С vapplyвы гарантированно получите определенный тип выходных данных, поэтому вам не нужно писать дополнительные проверки для входных данных нулевой длины.

Ориентиры

vapply может быть немного быстрее, потому что он уже знает, в каком формате он должен ожидать результатов.

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

Дополнительные нажатия клавиш, связанные с vapply может сэкономить ваше время отладки запутанных результатов позже. Если вызываемая вами функция может возвращать разные типы данных, vapply безусловно, следует использовать.

Один пример, который приходит на ум, будет sqlQuery в RODBC пакет. Если при выполнении запроса произошла ошибка, эта функция возвращает character вектор с сообщением. Так, например, скажем, вы пытаетесь перебрать вектор имен таблиц tnames и выберите максимальное значение из числового столбца 'NumCol' в каждой таблице с помощью:

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

Если все имена таблиц действительны, это приведет к numeric вектор. Но если в базе данных произойдет изменение одного из имен таблиц и запрос не будет выполнен, результаты будут переведены в режим character, С помощью vapply с FUN.VALUE=numeric(1)однако, остановит ошибку здесь и предотвратит ее появление где-то вниз по линии - или, что еще хуже, совсем нет.

Если вы всегда хотите, чтобы ваш результат был чем-то конкретным... например, логическим вектором. vapply гарантирует, что это произойдет, но sapply не обязательно делать это.

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

is.logical(a)
is.logical(b)
Другие вопросы по тегам