Факториал вектора

как новичок я попытался определить свою собственную функцию для вычисления факториала. Мне удалось построить функцию, которая отлично работает с числами.

fact1 = function(x){
    a=1 
    for(i in 1:x){
        a = a*i
    }
    return(a)
}   

factorial = function(x){
    ifelse(x>=0 & round(x) == x , fact1(as.integer(x)),"NA")
}

Однако как я могу улучшить его, чтобы вы могли ввести в него вектор и вычислить факториал каждого элемента?


person Lukas Tomek    schedule 24.02.2019    source источник


Ответы (5)


Кажется, это идеальный случай для Vectorize: просто используйте Vectorize вокруг определения вашей factorial функции, чтобы сделать ее векторизованной поверх ее ввода.

fact1 = function(x){
  a=1 
  for(i in 1:x){
    a = a*i
  }
  return(a)
}   

factorial = Vectorize(function(x){
  ifelse(x>=0 & round(x) == x , fact1(as.integer(x)),"NA")
})

factorial(c(1,2,3))
#> [1] 1 2 6
person plant    schedule 24.02.2019

Добавляя к комментарию lapply выше, вы также можете использовать vapply или sapply для возврата вектора, а не списка:

vapply(c(1, 2, 3),
       factorial, 
       FUN.VALUE = numeric(1))

[1] 1 2 6
person Andrew Royal    schedule 24.02.2019

Ответы на вопросы кажутся немного сложными. Факториал - это уже существующая функция, и она векторизована как таковая, если у вас есть какие-то данные, вы можете просто поместить их в функцию. Если вы хотите определить отрицательные числа для возврата 0, это также можно включить с помощью логического оператора. Обратите внимание, что я использую функцию buildin factorial ниже, а не тот, который указан в вопросе.

dat <- round(runif(1000, -10, 10))
dat_over_zero <- dat > 0 
fact_vector <- numeric(1000)
fact_vector <- factorial(dat[dat_over_zero])

Теперь, если вы просто создаете упражнение для изучения, вы можете очень просто векторизовать функцию, избегая ненужных циклов for, используя ту же идею. Просто используйте один цикл и перебирайте каждый элемент вектора во время этого цикла.

R_factorial <- function(x){
  if(!is.numeric(x) || length(dim(x)))
    stop("X must be a numeric vector!")
  #create an output vector
  output <- numeric(NROW(x))
  #set initial value
  output[x >= 1] <- 1
  output[x < 1] <- NA
  #Find the max factor (using only integer values, not gamma approximations)
  mx <- max(round(x))
  #Increment each output by multiplying the next factor (only on those which needs to be incremented) 
  for(i in seq(2, mx)){
    output[x >= i] <- output[x >= i] * i
  }
  #return output
  output
}

Несколько замечаний:

  1. Сначала выделите весь вектор, используя output <- numeric(length), где длина - это количество выходов (например, здесь length(x) или в более общем смысле NROW(x)).
  2. Используйте константу R NA для отсутствия числовых значений вместо "NA". Первый распознается как число, а последний изменяет ваш вектор в векторе символов.

Теперь альтернативные ответы предполагают lapply или vapply. Это более или менее похоже на перебор каждого значения в векторе и использование функции для каждого значения. По сути, это часто медленный (но очень читаемый!) Способ векторизации функции. Однако, если этого можно избежать, вы часто можете получить прирост скорости. Для циклов и применения это не обязательно плохо, но в целом намного медленнее по сравнению с векторизованными функциями. См. эту страницу stackoverflow, где очень легко понять, почему. Дополнительная альтернатива - использование предложенной функции Vectorize. Это быстрое и грязное решение. По моему опыту, это часто медленнее, чем выполнение простого цикла, и может иметь некоторые неожиданные побочные эффекты для функций с несколькими аргументами. Это не обязательно плохо, так как часто становится понятнее базовый код.


Сравнение скорости

Теперь векторизованная версия намного быстрее по сравнению с альтернативными ответами. Используя функцию microbenchmark из пакета microbenchmark, мы можем точно увидеть, насколько быстрее. Ниже показано, сколько (обратите внимание, здесь я использую факториальную функцию в описании вопроса):

microbenchmark::microbenchmark(R_factorial = R_factorial(x),
                               Vapply = vapply(x,
                                              factorial, 
                                              FUN.VALUE = numeric(1)),
                               Lapply = lapply(x, factorial),
                               Vfactorial = Vfactorial(x))
Unit: microseconds
        expr       min        lq      mean    median       uq       max neval
 R_factorial   186.525   197.287  232.2394  212.9565  241.464   395.706   100
      Vapply  2209.982  2354.596 3004.9264 2428.7905 3842.265  6165.144   100
      Lapply  2182.041  2299.092 2584.3881 2374.9855 2430.867  5061.852   100
Vfactorial(x) 2381.027 2505.4395 2842.9820 2595.3040 2669.310  5920.094   100

Как видно, R_factorial примерно в 11-12 раз быстрее по сравнению с vapply или lapply (2428,8 / 212,96 = 11,4). Это довольно большой прирост скорости. Можно было бы сделать дополнительные улучшения, чтобы еще больше ускорить его (например, используя алгоритмы факториальной аппроксимации, Rcpp и другие параметры), но для этого примера этого может быть достаточно.

person Oliver    schedule 24.02.2019

Используйте функцию lapply

lapply(c(1,2,3),factorial)
[[1]]
[1] 1

[[2]]
[1] 2

[[3]]
[1] 6

Документация R для функции lapply

person Stupid_Intern    schedule 24.02.2019

Вы также можете использовать безопасную функцию purrr :: map_dbl:

purrr::map_dbl(c(1,2,3), fact1)

[1] 1 2 6

person ChristianL    schedule 24.02.2019