对Rcpp NumericMatrix的列进行排序以进行中值计算

时间:2013-04-04 20:10:48

标签: r rcpp

我一直在测试Rcpp和RcppArmadillo来计算大矩阵的汇总统计数据。这比基础R colMeans或犰狳在4百万行,45列上快得多(快5或10倍)。

colMeansRcpp <- cxxfunction(signature(X_="integer"), 
                            plugin='Rcpp',
                            body='
                            Rcpp::IntegerMatrix X = X_;
                            int ncol = X.ncol(); int nrow = X.nrow();                      
                            Rcpp::NumericVector out(ncol);
                            for(int col = 0; col < ncol; col++){
                              out[col]=Rcpp::sum(X(_, col));
                            }                             
                            return wrap(out/nrow);
                          ')

我真的想要计算绘图的中位数和其他分位数 - 因为它需要一种更加需要的C ++外包。犰狳似乎有点慢,所以我想做一个类似上面的代码排序,但我不能正确的语法...这是我正在尝试...

# OK I'm aware this floor(nrow/2) is not **absolutely** correct 
# I'm simplifying here
    colMedianRcpp <- cxxfunction(signature(X_="integer"), 
                          plugin='Rcpp',
                          body='
                          Rcpp::IntegerMatrix X = clone(X_);
                          int ncol = X.ncol(); int nrow = X.nrow();                           
                          Rcpp::NumericVector out(ncol);
                          for(int col = 0; col < ncol; col++){
                          X(_,col)= std::sort((X_,col).begin, (X_,col).end));
                          out[col]=X(floor(nrow/2), col));
                          }
                        return wrap(out);
                        ')

基本上是行

X(_,col)= std::sort((X_,col).begin, (X_,col).end));

我不知道如何使用Rcpp sugar和std C ++的混合物来表达“对柱子进行分类”。对不起,我可以看到我正在做的事情是错的,但是正确的语法提示很可爱。

ps我是对的我需要做这个clone()所以我不更改R对象吗?

修改 我添加了RcppArmadillo代码和基准比较来解决下面的答案/评论。基准测试只有50k行才能快速回复,但我记得它与更多类似。我知道你是Rcpp的作者..非常感谢你的时间!

我想这可能是因为我使用RcppArmadillo代码做了一些蠢事,使它比基本colMeans或Rcpp版本运行得慢得多?

colMeansRcppArmadillo <- cxxfunction(signature(X_="integer"), 
                                     plugin="RcppArmadillo",
                                      body='
                                      arma::mat X = Rcpp::as<arma::mat > (X_);
                                      arma::rowvec MD= arma::mean(X, 0);
                                      return wrap(MD);
                                    ')

基准是......

(mb = microbenchmark(
+                     colMeans(fqSmallMatrix), 
+                     colMeansRcpp(fqSmallMatrix), 
+                     colMeansRcppArmadillo(fqSmallMatrix),
+                     times=50))
Unit: milliseconds
                                 expr       min       lq    median        uq        max neval
              colMeans(fqSmallMatrix) 10.620919 10.63289 10.640819 10.648882  10.907145    50
          colMeansRcpp(fqSmallMatrix)  2.649038  2.66832  2.676709  2.700839   2.841012    50
 colMeansRcppArmadillo(fqSmallMatrix) 25.687067 26.23488 33.168589 33.792489 113.832495    50

2 个答案:

答案 0 :(得分:5)

您可以使用

将列复制到新的矢量中
NumericVector y = x(_,j);

完整示例:

library(Rcpp)
cppFunction('
  NumericVector colMedianRcpp(NumericMatrix x) {
    int nrow = x.nrow();
    int ncol = x.ncol();
    int position = nrow / 2; // Euclidian division
    NumericVector out(ncol);
    for (int j = 0; j < ncol; j++) {
      NumericVector y = x(_,j); // Copy the column -- the original will not be modified
      std::nth_element(y.begin(), y.begin() + position, y.end());
      out[j] = y[position];
    }
    return out;
  }
')
x <- matrix( sample(1:12), 3, 4 )
x
colMedianRcpp(x)
x   # Unchanged

答案 1 :(得分:2)

你实际上并没有展示RcppArmadillo代码 - 我对RcppArmadillo代码的性能非常满意,我需要行/列列子集。

您可以通过Rcpp实例化Armadillo矩阵(没有复制,重新使用R对象内存)所以我会尝试。

而且你:你希望clone()获得一份独特的副本,如果你使用默认的RcppArmadillo ctor(而不是更高效的两步),我认为你可以免费获得它。

几小时后编辑

你留下了一个关于为什么你的犰狳很慢的问题。与此同时,Vincent为您解决了这个问题,但这是一个使用您的代码以及Vincent的重新审视,更清晰的解决方案。

现在它如何在没有副本的情况下实例化Armadillo矩阵 - 所以它更快。它还避免了混合整数和数字矩阵。代码首先:

#include <RcppArmadillo.h> 

using namespace Rcpp;

// [[Rcpp::depends(RcppArmadillo)]]

// [[Rcpp::export]]
NumericVector colMedianRcpp(NumericMatrix x) {
    int nrow = x.nrow();
    int ncol = x.ncol();
    int position = nrow / 2; // Euclidian division
    NumericVector out(ncol);
    for (int j = 0; j < ncol; j++) { 
        NumericVector y = x(_,j); // Copy column -- original will not be mod
        std::nth_element(y.begin(), y.begin() + position, y.end()); 
        out[j] = y[position];  
    }
    return out;
}

// [[Rcpp::export]]
arma::rowvec colMeansRcppArmadillo(NumericMatrix x){
    arma::mat X = arma::mat(x.begin(), x.nrow(), x.ncol(), false); 
    return arma::mean(X, 0); 
}

// [[Rcpp::export]]
NumericVector colMeansRcpp(NumericMatrix X) {
    int ncol = X.ncol();
    int nrow = X.nrow(); 
    Rcpp::NumericVector out(ncol);
    for (int col = 0; col < ncol; col++){
        out[col]=Rcpp::sum(X(_, col)); 
    } 
    return wrap(out/nrow);
} 

/*** R
set.seed(42)
X <- matrix(rnorm(100*10), 100, 10)
library(microbenchmark)

mb <- microbenchmark(colMeans(X), colMeansRcpp(X), colMeansRcppArmadillo(X),
                     colMedianRcpp(X), times=50)  
print(mb)
*/

这是我的机器上的结果,简洁的犰狳版本和你的一样快,中位数稍慢,因为它需要做更多的工作:

R> sourceCpp("/tmp/stephen.cpp") 
R> set.seed(42)
R> X <- matrix(rnorm(100*10), 100, 10)
R> library(microbenchmark)
R> mb <- microbenchmark(colMeans(X), colMeansRcpp(X), colMeansRcppArmadillo(X),
+                      colMedianRcpp(X), times=50) 
R> print(mb)
Unit: microseconds
                     expr    min     lq  median     uq    max neval
              colMeans(X)  9.469 10.422 11.5810 12.421 30.597    50 
          colMeansRcpp(X)  3.922  4.281  4.5245  5.306 18.020    50 
 colMeansRcppArmadillo(X)  4.196  4.549  4.9295  5.927 11.159    50 
         colMedianRcpp(X) 15.615 16.291 16.7290 17.971 27.026    50 
R>