Ответы на вопрос кажутся немного сложными.Факториал - это уже существующая функция, и она векторизована как таковая, если у вас есть некоторые данные, которые вы можете просто поместить в функцию.Если вы хотите определить отрицательные числа для возврата 0, это также может быть включено с помощью логического утверждения.Обратите внимание, что я использую встроенную функцию factorial
ниже, а не ту, которая указана в вопросе.
dat <- round(runif(1000, -10, 10))
dat_over_zero <- dat > 0
fact_vector <- numeric(1000)
fact_vector <- factorial(dat[dat_over_zero])
Теперь, если вы просто создаете упражнение для обучения, вы можете довольно просто векторизовать функцию, избегая ненужныхдля циклов, используя ту же идею.Просто используйте один цикл и повторяйте каждый элемент вектора во время этого цикла.
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
}
Несколько замечаний:
- Сначала выделите весь вектор, используя
output <- numeric(length)
, гдедлина - это количество выходов (например, length(x)
здесь или, в более общем случае, NROW(x)
). - Используйте постоянную R
NA
для числовых значений вместо "NA"
.Первый признается как число, а последний изменит ваш вектор в символьном векторе.
Теперь альтернативные ответы предлагают lapply или vapply.Это примерно то же самое, что циклически повторять каждое значение в векторе и использовать функцию для каждого значения.Таким образом, это часто медленный (но очень читаемый!) Способ векторизации функции.Однако, если этого можно избежать, вы часто можете увеличить скорость.Для циклов и применять это не обязательно плохо, но в целом это намного медленнее по сравнению с векторизованными функциями.См. на этой странице, описывающей стековый поток , которая очень легко объясняет, почемуДополнительной альтернативой является использование предложенной функции 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 и другие опции), но для этого примера этого может быть достаточно.