问题的答案似乎有点过于复杂。
阶乘已经是一个存在的函数,如果你有一些数据,你可以简单地将它放入函数中,因此它是矢量化的。如果您想定义负数以返回 0,这也可以通过使用逻辑语句来合并。请注意,我使用的是下面的内置函数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
}
需要注意的几点:
- 首先使用
output <- numeric(length)分配整个向量,其中length是输出的数量(例如length(x)这里或更一般的NROW(x))。
- 使用 R 常量
NA 代替 "NA" 表示无数值。第一个被识别为数字,而后者会将您的向量更改为字符向量。
现在替代答案建议 lapply 或 vapply。这与遍历向量中的每个值并在每个值上使用函数或多或少相同。因此,向量化函数通常是一种缓慢(但非常易读!)的方式。但是,如果可以避免这种情况,您通常可以获得速度提升。 For 循环和 apply 不一定是坏事,但与矢量化函数相比,它通常要慢很多。请参阅this stackoverflow page,它以非常容易理解的方式解释了原因。
另一种选择是使用建议的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
正如我们所见,与 vapply 或 lapply (2428.8 / 212.96 = 11.4) 相比,R_factorial 大约快 11 - 12 倍。这是一个相当大的速度提升。可以进行其他改进以进一步加快速度(例如,使用阶乘逼近算法、Rcpp 和其他选项),但对于本示例来说可能就足够了。