Чому `vapply` безпечніше, ніж` sapply`?


84

У документації сказано

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

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


PS: Я знаю відповідь і вже схильний уникати sapply. Я просто хотів би, щоб тут була така приємна відповідь, щоб я міг вказати на це своїх колег. Будь ласка, жодної відповіді "прочитати посібник".


1
Це більш передбачувано, робить код менш однозначним та більш надійним. Особливо у великих проектах, скажімо у великому пакеті, це актуально.
Paul Hiemstra,

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. Однак використання vapplyз 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)

4
я думаю, що найочевидніше, що потрібно зробити, це logical(1)в цьому випадку, оскільки FALSE виглядає так, що він встановлює опцію “OFF” замість того, щоб вказати тип
літаюча вівця
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.