Подтвердить что ты не робот

Является ли `if` быстрее, чем ifelse?

Когда я недавно перечитывал Hadley Advanced R, я заметил, что в главе 6 он сказал, что `if` можно использовать как функцию `if`(i == 1, print("yes"), print("no")) (Если у вас есть физическая книга в руке, она находится на странице 80)

Мы знаем, что ifelse является медленным (Неужели ifelse действительно вычисляет оба своих вектора каждый раз? Он медленный?), когда он оценивает все аргументы. Будет ли `if` хорошей альтернативой этому, поскольку if, кажется, оценивает только аргументы TRUE (это только мое предположение)?


Обновление. На основе ответов от @Benjamin и @Roman и комментариев от @Gregor и многих других ifelse представляется лучшим решением для векторизованных вычислений. Я беру @Benjamin ответ здесь, поскольку это обеспечивает более полное сравнение и для благосостояния сообщества. Однако оба ответа (и комментарии) заслуживают внимания.

4b9b3361

Ответ 1

Это скорее расширенное построение комментариев по римскому ответу, но мне нужны утилиты кода для изложения:

Роман прав, что if быстрее, чем ifelse, но у меня создается впечатление, что ускорение скорости if не особенно интересно, так как это не то, что можно легко использовать с помощью векторизации. То есть if является преимущественным только при ifelse, когда аргумент cond/test имеет длину 1.

Рассмотрим следующую функцию, которая является, по общему признанию, слабой попыткой векторизации if без побочного эффекта оценки условий yes и no как ifelse.

ifelse2 <- function(test, yes, no){
 result <- rep(NA, length(test))
 for (i in seq_along(test)){
   result[i] <- `if`(test[i], yes[i], no[i])
 }
 result
}

ifelse2a <- function(test, yes, no){
  sapply(seq_along(test),
         function(i) `if`(test[i], yes[i], no[i]))
}

ifelse3 <- function(test, yes, no){
  result <- rep(NA, length(test))
  logic <- test
  result[logic] <- yes[logic]
  result[!logic] <- no[!logic]
  result
}


set.seed(pi)
x <- rnorm(1000)

library(microbenchmark)
microbenchmark(
  standard = ifelse(x < 0, x^2, x),
  modified = ifelse2(x < 0, x^2, x),
  modified_apply = ifelse2a(x < 0, x^2, x),
  third = ifelse3(x < 0, x^2, x),
  fourth = c(x, x^2)[1L + ( x < 0 )]
)

Unit: microseconds
           expr      min        lq       mean    median        uq       max neval cld
       standard  125.509  130.0545  809.50617  133.4265  136.5055 64762.881   100  ab
       modified 1120.197 1153.4795 1223.87912 1182.8040 1248.1980  1575.313   100   b
 modified_apply  902.022  921.6690  977.59717  934.4255  969.3220  2518.389   100  ab
          third   39.588   42.5210   58.72267   44.2810   46.1865  1427.224   100  a 
         fourth   15.542   17.5950   33.91116   18.6215   20.0875  1431.916   100  a

НЕКОТОРЫЕ РЕДАКТОРЫ: Спасибо Фрэнку и Ричарду Скривену за то, что заметили мои недостатки.

Как вы можете видеть, процесс разбиения вектора, который подходит для перехода на if, является трудоемким процессом и заканчивается медленнее, чем просто запуск ifelse (что, вероятно, почему никто не потрудился реализовать мое решение).

Если вы действительно отчаянно нуждаетесь в увеличении скорости, вы можете использовать подход ifelse3 выше. Или еще лучше, Фрэнк менее очевидный *, но блестящее решение.

  • "менее очевидным", я имею в виду, мне потребовалось две секунды, чтобы понять, что он сделал. Обратите внимание на то, что это работает только тогда, когда yes и no имеют длину 1, в противном случае вы захотите придерживаться ifelse3

Ответ 2

if - это примитивная (подчиненная) функция, вызываемая через интерфейс .Primitive, а ifelse - это байт-код R, поэтому кажется, что if будет быстрее. Запуск некоторых быстрых тестов

> microbenchmark(`if`(TRUE, "a", "b"), ifelse(TRUE, "a", "b"))
Unit: nanoseconds
                   expr  min   lq    mean median     uq   max neval cld
 if (TRUE) "a" else "b"   46   54  372.59   60.0   68.0 30007   100  a 
 ifelse(TRUE, "a", "b") 1212 1327 1581.62 1442.5 1617.5 11743   100   b

> microbenchmark(`if`(FALSE, "a", "b"), ifelse(FALSE, "a", "b"))
Unit: nanoseconds
                    expr  min   lq    mean median   uq   max neval cld
 if (FALSE) "a" else "b"   47   55   91.64   61.5   73  2550   100  a 
 ifelse(FALSE, "a", "b") 1256 1346 1688.78 1460.0 1677 17260   100   b

Похоже, что если не принимать во внимание код, который находится в реальных ветвях, if не менее чем на 20 раз быстрее, чем ifelse. Однако обратите внимание, что это не учитывает сложность тестируемого выражения и возможные оптимизации.

Обновить. Обратите внимание: этот быстрый тест представляет собой очень упрощенный и несколько предвзятый вариант использования if vs ifelse (как указано в комментариях). Несмотря на то, что он является правильным, он представляет собой прецеденты использования ifelse, поскольку этот ответ Бенджамина представляется более справедливым.