R - 向量化条件替换

use*_*926 2 logic for-loop r vectorization conditional-statements

嗨我正在尝试操作一个数字列表,我想这样做没有for循环,在R中使用快速本机操作.操作的伪代码是:

默认情况下,起始总数为100(对于零内的每个块)

从第一个零到下一个零,累计总数下降超过2%的时刻将所有后续数字替换为零.

在零之内完成所有数字块

累积总和每次都重置为100

例如,如果以下是我的数据:

d <- c(0,0,0,1,3,4,5,-1,2,3,-5,8,0,0,-2,-3,3,5,0,0,0,-1,-1,-1,-1);
Run Code Online (Sandbox Code Playgroud)

结果将是:

0 0 0 1 3 4 5 -1 2 3 -5 0 0 0 -2 -3 0 0 0 0 0 -1 -1 -1 0
Run Code Online (Sandbox Code Playgroud)

目前我有一个for循环的实现,但由于我的向量很长,性能很糟糕.

提前致谢.

这是一个运行的示例代码:

d <- c(0,0,0,1,3,4,5,-1,2,3,-5,8,0,0,-2,-3,3,5,0,0,0,-1,-1,-1,-1);
ans <- d;
running_total <- 100;
count <- 1;
max <- 100;
toggle <- FALSE;
processing <- FALSE;

for(i in d){
  if( i != 0 ){  
       processing <- TRUE; 
       if(toggle == TRUE){
          ans[count] = 0;  
       }
       else{
         running_total = running_total + i;

          if( running_total > max ){ max = running_total;}
          else if ( 0.98*max > running_total){
              toggle <- TRUE;  
          }
      }
   }

   if( i == 0 && processing == TRUE )
   { 
       running_total = 100; 
       max = 100;
       toggle <- FALSE;
   }
   count <- count + 1;
}
cat(ans)
Run Code Online (Sandbox Code Playgroud)

Jos*_*hua 7

我不知道如何将循环转换为矢量化操作.但是,有两个相当容易的选项可以实现大的性能改进.第一个是简单地将循环放入R函数中,并使用compiler包预编译它.第二个稍微复杂的选项是将R循环转换为c++循环并使用Rcpp包将其链接到R函数.然后你调用一个R函数将它传递给c++快速的代码.我展示了这些选项和时间.我真的要感谢来自Rcpp listserv的Alexandre Bujard的帮助,他帮我解决了我不明白的指针问题.

首先,这是你的R循环作为一个函数,foo.r.

## Your R loop as a function
foo.r <- function(d) {
  ans <- d
  running_total <- 100
  count <- 1
  max <- 100
  toggle <- FALSE
  processing <- FALSE

  for(i in d){
    if(i != 0 ){
      processing <- TRUE
      if(toggle == TRUE){
        ans[count] <- 0
      } else {
        running_total = running_total + i;
        if (running_total > max) {
          max <- running_total
        } else if (0.98*max > running_total) {
          toggle <- TRUE
        }
      }
    }
    if(i == 0 && processing == TRUE) {
      running_total <- 100
      max <- 100
      toggle <- FALSE
    }
    count <- count + 1
  }
  return(ans)
}
Run Code Online (Sandbox Code Playgroud)

现在我们可以加载compiler包并编译函数并调用它foo.rcomp.

## load compiler package and compile your R loop
require(compiler)
foo.rcomp <- cmpfun(foo.r)
Run Code Online (Sandbox Code Playgroud)

这就是编译路线所需的全部内容.这一切都R显而易见.现在,对于这种c++方法,我们使用Rcpp包以及inline允许我们"内联" c++代码的包.也就是说,我们不必创建源文件并对其进行编译,我们只需将其包含在R代码中,然后为我们处理编译.

## load Rcpp package and inline for ease of linking
require(Rcpp)
require(inline)

## Rcpp version
src <- '
  const NumericVector xx(x);
  int n = xx.size();
  NumericVector res = clone(xx);
  int toggle = 0;
  int processing = 0;
  int tot = 100;
  int max = 100;

  typedef NumericVector::iterator vec_iterator;
  vec_iterator ixx = xx.begin();
  vec_iterator ires = res.begin();
  for (int i = 0; i < n; i++) {
    if (ixx[i] != 0) {
      processing = 1;
      if (toggle == 1) {
        ires[i] = 0;
      } else {
        tot += ixx[i];
        if (tot > max) {
          max = tot;
        } else if (.98 * max > tot) {
            toggle = 1;
          }
      }
    }

   if (ixx[i] == 0 && processing == 1) {
     tot = 100;
     max = 100;
     toggle = 0;
   }
  }
  return res;
'

foo.rcpp <- cxxfunction(signature(x = "numeric"), src, plugin = "Rcpp")
Run Code Online (Sandbox Code Playgroud)

现在我们可以测试我们得到了预期的结果:

## demonstrate equivalence
d <- c(0,0,0,1,3,4,5,-1,2,3,-5,8,0,0,-2,-3,3,5,0,0,0,-1,-1,-1,-1)
all.equal(foo.r(d), foo.rcpp(d))
Run Code Online (Sandbox Code Playgroud)

最后,d通过重复10e4次来创建更大的版本.然后我们可以运行三个不同的函数,纯R代码,编译R代码和R链接到c++代码的函数.

## make larger vector to test performance
dbig <- rep(d, 10^5)

system.time(res.r <- foo.r(dbig))
system.time(res.rcomp <- foo.rcomp(dbig))
system.time(res.rcpp <- foo.rcpp(dbig))
Run Code Online (Sandbox Code Playgroud)

在我的系统上,给出:

> system.time(res.r <- foo.r(dbig))
   user  system elapsed 
  12.55    0.02   12.61 
> system.time(res.rcomp <- foo.rcomp(dbig))
   user  system elapsed 
   2.17    0.01    2.19 
> system.time(res.rcpp <- foo.rcpp(dbig))
   user  system elapsed 
   0.01    0.00    0.02 
Run Code Online (Sandbox Code Playgroud)

编译后的R代码大约需要1/6的时间,未编译的R代码只需2秒即可对250万的向量进行操作.该c++代码是快几个数量级,甚至编译后的R代码仅需0.02秒完成.除了初始设置之外,基本循环的语法几乎完全相同R,c++因此您甚至不会失去清晰度.我怀疑,即使你的部分或全部循环都可以被矢量化R,你也会感到痛苦,以击败R链接到的函数的性能c++.最后,仅用于证明:

> all.equal(res.r, res.rcomp)
[1] TRUE
> all.equal(res.r, res.rcpp)
[1] TRUE
Run Code Online (Sandbox Code Playgroud)

不同的函数返回相同的结果.