Почему `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)
Дополнительные нажатия клавиш, связанные с 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)