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

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

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

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


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

Ответы

Ответ 1

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

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

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

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

> 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

Ответ 2

Дополнительные нажатия клавиш, связанные с 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), однако, остановит здесь ошибку и не позволит ей появиться где-то по линии --- или, что еще хуже, не на всех.

Ответ 3

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

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

is.logical(a)
is.logical(b)