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

84

В документации говорится

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

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


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

Flodel
источник
1
Это более предсказуемо, делает код менее неоднозначным и более надежным. Это особенно актуально в крупных проектах, например, в большом пакете.
Пол Хиемстра,
1
Примеры руководства vapply для FUN.VALUE очень сложны и пугают пользователей sapply.
jsta

Ответы:

73

Как уже было отмечено, 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)

автопарк

Ари Б. Фридман
источник
15

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

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

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

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

Мэтью Плурд
источник
13

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

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

is.logical(a)
is.logical(b)
user1317221_G
источник
4
Я думаю, что наиболее очевидным является logical(1)этот случай, поскольку FALSE выглядит так, как будто он устанавливает параметр на «OFF» вместо указания типа
летающая овца