我正在尝试加速/矢量化时间序列中的一些计算。 我可以在for循环中矢量化计算,这可能取决于早期迭代的结果吗?例如:
z <- c(1,1,0,0,0,0)
zi <- 2:6
for (i in zi) {z[i] <- ifelse (z[i-1]== 1, 1, 0) }
使用前面步骤中更新的z [i]值:
> z
[1] 1 1 1 1 1 1
在我的矢量化
中z <- c(1,1,0,0,0,0)
z[zi] <- ifelse( z[zi-1] == 1, 1, 0)
逐个元素的操作不使用操作中更新的结果:
> z
[1] 1 1 1 0 0 0
因此,这种向量化操作以“并行”而非迭代方式运行。有没有办法我可以写/向量化这个来获得for循环的结果?
答案 0 :(得分:20)
ifelse
是矢量化的,如果你在for循环中一次在一个元素上使用它,会有一点点惩罚。在您的示例中,使用if
代替ifelse
可以获得相当快的加速。
fun1 <- function(z) {
for(i in 2:NROW(z)) {
z[i] <- ifelse(z[i-1]==1, 1, 0)
}
z
}
fun2 <- function(z) {
for(i in 2:NROW(z)) {
z[i] <- if(z[i-1]==1) 1 else 0
}
z
}
z <- c(1,1,0,0,0,0)
identical(fun1(z),fun2(z))
# [1] TRUE
system.time(replicate(10000, fun1(z)))
# user system elapsed
# 1.13 0.00 1.32
system.time(replicate(10000, fun2(z)))
# user system elapsed
# 0.27 0.00 0.26
通过编译,您可以从fun2
获得额外的速度提升。
library(compiler)
cfun2 <- cmpfun(fun2)
system.time(replicate(10000, cfun2(z)))
# user system elapsed
# 0.11 0.00 0.11
所以没有矢量化就有10倍的加速。正如其他人所说(有些人已经说明),有一些方法可以对你的例子进行矢量化,但这可能无法转化为你的实际问题。希望这足以适用。
filter
功能也可能对您有用,如果您可以了解如何根据自回归或移动平均过程来表达您的问题。
答案 1 :(得分:18)
这是Rcpp可以发光的一个很好而简单的例子。
因此,让我们首先重新编写函数1和2以及它们编译的对应函数:
library(inline)
library(rbenchmark)
library(compiler)
fun1 <- function(z) {
for(i in 2:NROW(z)) {
z[i] <- ifelse(z[i-1]==1, 1, 0)
}
z
}
fun1c <- cmpfun(fun1)
fun2 <- function(z) {
for(i in 2:NROW(z)) {
z[i] <- if(z[i-1]==1) 1 else 0
}
z
}
fun2c <- cmpfun(fun2)
我们非常轻松地编写Rcpp变体:
funRcpp <- cxxfunction(signature(zs="numeric"), plugin="Rcpp", body="
Rcpp::NumericVector z = Rcpp::NumericVector(zs);
int n = z.size();
for (int i=1; i<n; i++) {
z[i] = (z[i-1]==1.0 ? 1.0 : 0.0);
}
return(z);
")
这使用inline包来动态编译,加载和链接五线程。
现在我们可以定义我们的测试日期,我们的测试日期比原始日期长一些(因为只运行原始的太少次导致无法测量的时间):
R> z <- rep(c(1,1,0,0,0,0), 100)
R> identical(fun1(z),fun2(z),fun1c(z),fun2c(z),funRcpp(z))
[1] TRUE
R>
所有答案都被视为相同。
最后,我们可以进行基准测试:
R> res <- benchmark(fun1(z), fun2(z),
+ fun1c(z), fun2c(z),
+ funRcpp(z),
+ columns=c("test", "replications", "elapsed",
+ "relative", "user.self", "sys.self"),
+ order="relative",
+ replications=1000)
R> print(res)
test replications elapsed relative user.self sys.self
5 funRcpp(z) 1000 0.005 1.0 0.01 0
4 fun2c(z) 1000 0.466 93.2 0.46 0
2 fun2(z) 1000 1.918 383.6 1.92 0
3 fun1c(z) 1000 10.865 2173.0 10.86 0
1 fun1(z) 1000 12.480 2496.0 12.47 0
对于最好的R版本,编译版本赢得了近400倍,而对于其字节编译版本,编译版本几乎达到100。对于函数1,字节编译的重要性要小得多,并且两个变体都以超过两千的因子跟踪C ++。
编写C ++版本花了大约一分钟。速度提升表明它花了一分钟。
为了进行比较,这里是更经常调用的原始短向量的结果:
R> z <- c(1,1,0,0,0,0)
R> res2 <- benchmark(fun1(z), fun2(z),
+ fun1c(z), fun2c(z),
+ funRcpp(z),
+ columns=c("test", "replications",
+ "elapsed", "relative", "user.self", "sys.self"),
+ order="relative",
+ replications=10000)
R> print(res2)
test replications elapsed relative user.self sys.self
5 funRcpp(z) 10000 0.046 1.000000 0.04 0
4 fun2c(z) 10000 0.132 2.869565 0.13 0
2 fun2(z) 10000 0.271 5.891304 0.27 0
3 fun1c(z) 10000 1.045 22.717391 1.05 0
1 fun1(z) 10000 1.202 26.130435 1.20 0
定性排名保持不变:Rcpp版本占主导地位,function2排名第二。字节编译版本的速度大约是普通R版本的两倍,但仍然比C ++版本快三倍。并且相对差异较小:相对而言,函数调用开销较少,实际循环更重要:C ++在较长向量中的实际循环操作中获得更大的优势。这是一个重要的结果,因为它表明更多真实大小的数据,编译版本可以获得更大的好处。
编辑纠正代码示例中的两个小疏忽。并再次编辑,感谢Josh捕获相对于fun2c的设置错误。
答案 2 :(得分:5)
我认为这是作弊而不是概括,但是:根据你上面的规则,向量中出现1将使所有后续元素1(通过递归:z[i]
1设置为1如果z[i-1]
等于1;因此如果z[i]
等于1,则z[i-2]
将设置为1;依此类推)。根据您真正想要做的事情,如果您仔细考虑,可能可以成为这样一种递归解决方案......
z <- c(1,1,0,0,0,0)
first1 <- min(which(z==1))
z[seq_along(z)>first1] <- 1
编辑:这是错误的,但我不肯承认我的错误。基于一点点的游戏(并且思路较少),我认为这种递归的实际解决方案更加对称,甚至更简单:
rep(z[1],length(z))
测试用例:
z <- c(1,1,0,0,0,0)
z <- c(0,1,1,0,0,0)
z <- c(0,0,1,0,0,0)
答案 3 :(得分:3)
查看rollapply
中的zoo
功能。
我不是很熟悉它,但我认为这就是你想要的:
> c( 1, rollapply(z,2,function(x) x[1]) )
[1] 1 1 1 1 1 1
我通过使用2的窗口然后仅使用该窗口的第一个元素来克服它。
对于更复杂的例子,你可以在x [1]上执行一些计算并返回它。
答案 4 :(得分:0)
有时你只需要完全不同地思考它。您正在做的是创建一个向量,其中每个项目与第一个项目相同,否则为1或0。
z <- c(1,1,0,0,0,0)
if (z[1] != 1) z[1] <- 0
z[2:length(z)] <- z[1]
答案 5 :(得分:0)
有一个功能可以执行此特定计算:cumprod
(累积产品)
> cumprod(z[zi])
[1] 1 0 0 0 0
> cumprod(c(1,2,3,4,0,5))
[1] 1 2 6 24 0 0
否则,如其他答案所示,使用Rccp进行矢量化。
答案 6 :(得分:-2)
使用&#34; apply&#34;也可以这样做。使用原始向量和向量的滞后版本作为数据框的组成列。