我希望实现一个简单的split-apply-combine
例程,Rcpp
其中将数据集(矩阵)分成组,然后返回分组列的总和。这是一个在 中很容易实现的过程R
,但通常需要相当长的时间。我已经设法实现了一个Rcpp
性能优于 的解决方案R
,但我想知道我是否可以进一步改进它。为了说明,这里有一些代码,先供使用R
:
n <- 50000
k <- 50
set.seed(42)
X <- matrix(rnorm(n*k), nrow=n)
g=rep(1:8,length.out=n )
use.for <- function(mat, ind){
sums <- matrix(NA, nrow=length(unique(ind)), ncol=ncol(mat))
for(i in seq_along(unique(ind))){
sums[i,] <- colSums(mat[ind==i,])
}
return(sums)
}
use.apply <- function(mat, ind){
apply(mat,2, function(x) tapply(x, ind, sum))
}
use.dt <- function(mat, ind){ # based on Roland's answer
dt <- as.data.table(mat)
dt[, cvar := ind]
dt2 <- dt[,lapply(.SD, sum), by=cvar]
as.matrix(dt2[,cvar:=NULL])
}
事实证明,for
-loops 实际上非常快,并且是最容易(对我来说)实现的Rcpp
. 它的工作原理是为每个组创建一个子矩阵,然后调用colSums
该矩阵。这是使用实现的RcppArmadillo
:
#include <RcppArmadillo.h>
// [[Rcpp::depends(RcppArmadillo)]]
using namespace Rcpp;
using namespace arma;
// [[Rcpp::export]]
arma::mat use_arma(arma::mat X, arma::colvec G){
arma::colvec gr = arma::unique(G);
int gr_n = gr.n_rows;
int ncol = X.n_cols;
arma::mat out = zeros(gr_n, ncol);
for(int g=0; g<gr_n; g++){
int g_id = gr(g);
arma::uvec subvec = find(G==g_id);
arma::mat submat = X.rows(subvec);
arma::rowvec res = sum(submat,0);
out.row(g) = res;
}
return out;
}
但是,根据对这个问题的回答,我了解到创建副本的成本很高C++
(就像在 中一样R
),但循环并不像在 中那样糟糕R
。由于- 解决arma
方案依赖于submat
为每个组创建矩阵(在代码中),我的猜测是避免这种情况会进一步加快进程。因此,这里是基于Rcpp
仅使用循环的第二个实现:
#include <Rcpp.h>
using namespace Rcpp;
// [[Rcpp::export]]
NumericMatrix use_Rcpp(NumericMatrix X, IntegerVector G){
IntegerVector gr = unique(G);
std::sort(gr.begin(), gr.end());
int gr_n = gr.size();
int nrow = X.nrow(), ncol = X.ncol();
NumericMatrix out(gr_n, ncol);
for(int g=0; g<gr_n; g++){
int g_id = gr(g);
for (int j = 0; j < ncol; j++) {
double total = 0;
for (int i = 0; i < nrow; i++) {
if (G(i) != g_id) continue; // not sure how else to do this
total += X(i, j);
}
out(g,j) = total;
}
}
return out;
}
对这些解决方案进行基准测试,包括use_dt
@Roland 提供的版本(我以前的版本不公平地歧视data.table
),以及dplyr
@beginneR 建议的解决方案,产生以下结果:
library(rbenchmark)
benchmark(use.for(X,g), use.apply(X,g), use.dt(X,g), use.dplyr(X,g), use_arma(X,g), use_Rcpp(X,g),
+ columns = c("test", "replications", "elapsed", "relative"), order = "relative", replications = 1000)
test replications elapsed relative
# 5 use_arma(X, g) 1000 29.65 1.000
# 4 use.dplyr(X, g) 1000 42.05 1.418
# 3 use.dt(X, g) 1000 56.94 1.920
# 1 use.for(X, g) 1000 60.97 2.056
# 6 use_Rcpp(X, g) 1000 113.96 3.844
# 2 use.apply(X, g) 1000 301.14 10.156
我的直觉(use_Rcpp
优于use_arma
)结果并不正确。话虽如此,我猜if (G(i) != g_id) continue;
我use_Rcpp
函数中的行会减慢一切。我很高兴了解设置它的替代方法。
我很高兴我用一半的时间完成了同样的任务R
,但也许这几个Rcpp is much faster than R
例子已经超出了我的期望,我想知道我是否可以加快速度。有人有想法吗?我也欢迎任何一般的编程/编码评论,因为我对Rcpp
和C++
.