2

嗨,我正在尝试操作一个数字列表,我想在没有 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);

结果将是:

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

目前我有一个 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)
4

1 回答 1

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)
}

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

## load compiler package and compile your R loop
require(compiler)
foo.rcomp <- cmpfun(foo.r)

这就是编译路径所需的全部内容。这一切都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")

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

## 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))

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))

在我的系统上,给出:

> 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 

编译后的代码对 250 万向量进行运算R所需时间约为未编译代码的 1/6 。即使编译后的代码只需要 0.02 秒即可完成,R该代码的速度也快了几个数量级。除了初始设置之外,基本循环的语法几乎相同,因此您甚至不会失去清晰度。我怀疑即使您的部分或全部循环可以在. 最后,只是为了证明:c++RRc++RRc++

> all.equal(res.r, res.rcomp)
[1] TRUE
> all.equal(res.r, res.rcpp)
[1] TRUE

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

于 2012-07-08T00:22:29.097 回答