前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >For循环与向量化(Vectorization)

For循环与向量化(Vectorization)

作者头像
用户7652506
发布2022-04-14 12:02:17
1.8K0
发布2022-04-14 12:02:17
举报
文章被收录于专栏:大猫的R语言课堂

For循环与向量化(Vectorization)

写在前面

感谢水友们积极的提问,大猫和村长在此再次表示衷心的感谢。通过对水友们问题的汇总,我们发现大多数水友存在一些R语言的应用误区,在此出一期关于该问题的解读。

问题提出

首先思考一个典型的增长率的计算的例子。假设我们有一列时间序列,每个都记录着时刻的值。现在我们希望针对每个计算当期的增长率,其公式如下:

大家可能首先想到的是利用For循环来做。假如一个向量长度为,那么我们就把上面的增长率公式应用遍。这种思路以标量(scalar)的角度考虑问题。这样是否真的有效率?除此之外,能否有其他的思路? ”

解决方法

For循环

首先我们用R语言最底层的For循环进行函数的编写。我们定义向量长度为10000,重复运行1000次,测定计算其运行的中位数时长为了防止极值对结果的影响)。在计算时间方面,运用microbenchmark包,生成每次运行的时间,最后计算中位数,代码与结果如下:

代码语言:javascript
复制
library(microbenchmark)
growthRBL <- function(x) {
    growth <- vector(mode = "numeric")
    for (i in seq_along(x)) {
        growth[i] <- x[i]/x[i-1] - 1
    }
    growth
}
time1 <- microbenchmark(growthRBL(1:10000), times = 1000) %>% as.data.table()
time1[, median(time)/1e6]

3.3792015

通过结果可以发现,平均每次运行所需要耗费的时长为3.3秒左右。有没有更快的方法呢?我们来看下面的思路。

Vectorized(向量化)

根据Hadley Wickham在其著作Advanced R中第一章所涉及到的内容,R最底层的数据结构只有两种:向量vector)和列表list),其他所有的数据格式都是通过这两种最基本的数据结构衍化而来。向量作为最基本的数据结构,其在进行底层编写的时候,进行了很大程度的优化设计。向量有时候作为一种基本的编写思路,是具有很高效率的。有鉴于此,我们通过R语言最底层的向量思维进行函数编写。

代码语言:javascript
复制
library(microbenchmark)
growthRBV <- function(x) {
    shift <- function(y) {
        c(NA, y[1:(length(y)-1)])
    }
    x/shift(x) - 1
}
time2 <- microbenchmark(growthRBV(1:10000), times = 1000) %>% as.data.table()
time2[, median(time)/1e6]

0.084901

我们在函数中编写了另一个函数,名曰shift由于我们需要做的是向量中某一个元素与前一个元素的处理结果,那么只需要将元素往后进行移位,与原来的向量进行一一对应的处理即可,这样便达到了以向量进行处理的模式。也可称之为向量化vectorization)。

上述的运行结果更能反映这种编写的效率,可以看到运行速度提升了将近40倍,运行时间变成了0.08s左右。

关于For循环和Vectorization的深入思考

Vectorization在更多包的拓展

现在有很多的R包会对底层的一些函数进行优化,也即是对向量化的进一步优化,我们选择效率较为强大的data.table,看看其对shift函数的优化情况。

代码语言:javascript
复制
library(microbenchmark)
growthRDV <- function(x) {
    x/data.table::shift(x) - 1
}
time3 <- microbenchmark(growthRDV(1:10000), times = 1000) %>% as.data.table()
time3[, median(time)/1e6]

0.0406515

通过结果可以发现,data.table对于shift函数的优化又使其效率翻倍!!运行时间继续降低至0.04s!!

更底层的For循环

R语言本身的For循环效率相对低下,究其原因在于R作为高级语言,循环本身需要先进行编译,再放入底层进行处理。更为直接的做法,如果想提升效率,则可以直接将循环放入底层进行运行。有鉴于此,C++可作为一种比较好的替代手段。R语言提供了一个很好的C++语言的接口,Rcpp包能够比较方便调用C++的语句进行操作。(若有对Rcpp感兴趣的同学可以戳这里进行了解)

代码语言:javascript
复制
library(microbenchmark)
Rcpp::cppFunction('NumericVector growthRCL(NumericVector x){
    int n = x.size();
    NumericVector y(n);
    y[0] = NA_REAL;
    for(int i = 1; i < n; i++) {
        y[i] = x[i]/x[i-1]-1;
    }
    return y;
}')
time4 <- microbenchmark(growthRCL(1:10000), times = 1000) %>% as.data.table()
time4[, median(time)/1e6]

0.029601

如上所示,使用Rcpp包中的cppFunction进行C++语句的调用。在这里会自动调用已经配置好的C++头文件,并自动编译而后运行。调用的C++语句,在R语言中皆有相对应的数据格式。通过运行结果可以发现,Rcpp调用的底层循环略优于data.table的向量化,运行时间在0.03s左右。

总结

通过上面的运行效率排序可以发现:

我们也可以总结出以下两点:

  1. 在R语言中一般意义上的数据操作,能够向量化尽量进行向量化,For循环尽量避免使用。
  2. 利用data.table进行数据操作有着比R本身向量化更好的效率表现,如果自身对效率的要求更高,可以利用更底层的语言接口进行编写。
  • 最后还有一点需要注意:向量化并不能解决一切问题。当遇到一些特殊情况,比如函数嵌套调用过多,或者数据迭代问题,对更为底层的语言进行调用,则会显得更为有效。
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-04-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 大猫的R语言课堂 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • For循环与向量化(Vectorization)
    • 写在前面
      • 问题提出
        • 解决方法
          • For循环
          • Vectorized(向量化)
        • 关于For循环和Vectorization的深入思考
          • Vectorization在更多包的拓展
          • 更底层的For循环
        • 总结
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档