我正在寻找C++ / Rcpp / Eigen或Armadillo中R函数rowsum的快速替代品。
其目的是根据分组向量b获得向量a中元素的和。例如:
> a
[1] 2 2 2 2 2 2 2 2 2 2
> b
[1] 1 1 1 1 1 2 2 2 2 2
> rowsum(a,b)
[,1]
1 10
2 10用Rcpp编写一个简单的for循环非常慢,但可能我的代码效率很低。
我还尝试在Rcpp中调用函数rowsum,但是,rowsum的速度不是很快。
发布于 2013-06-08 02:09:57
下面是我使用Rcpp完成此操作的尝试(这是我第一次使用这个包,所以请指出我的低效):
library(inline)
library(Rcpp)
rowsum_helper = cxxfunction(signature(x = "numeric", y = "integer"), '
NumericVector var(x);
IntegerVector factor(y);
std::vector<double> sum(*std::max_element(factor.begin(), factor.end()) + 1,
std::numeric_limits<double>::quiet_NaN());
for (int i = 0, size = var.size(); i < size; ++i) {
if (sum[factor[i]] != sum[factor[i]]) sum[factor[i]] = var[i];
else sum[factor[i]] += var[i];
}
return NumericVector(sum.begin(), sum.end());
', plugin = "Rcpp")
rowsum_fast = function(x, y) {
res = rowsum_helper(x, y)
elements = which(!is.nan(res))
list(elements - 1, res[elements])
}对于马丁的示例数据来说,它非常快,但只有当因子由非负整数组成,并且将消耗因子向量中最大整数量级的内存时,它才会起作用(上面的一个明显改进是从max中减去min以减少内存使用量-这可以在R函数或C++函数中完成)。
n = 1e7; x = runif(n); f = sample(n/2, n, T)
system.time(rowsum(x,f))
# user system elapsed
# 14.241 0.170 14.412
system.time({tabulate(f); sum(x)})
# user system elapsed
# 0.216 0.027 0.252
system.time(rowsum_fast(x,f))
# user system elapsed
# 0.313 0.045 0.358还要注意的是,R代码中发生了很多减速(与tabulate相比),所以如果您将其转移到C++中,您应该会看到更多的改进:
system.time(rowsum_helper(x,f))
# user system elapsed
# 0.210 0.018 0.228下面是一个泛化,它可以处理几乎所有的y,但会稍微慢一点(实际上我更喜欢在Rcpp中这样做,但不知道如何处理那里的任意R类型):
rowsum_fast = function(x, y) {
if (is.numeric(y)) {
y.min = min(y)
y = y - y.min
res = rowsum_helper(x, y)
} else {
y = as.factor(y)
res = rowsum_helper(x, as.numeric(y))
}
elements = which(!is.nan(res))
if (is.factor(y)) {
list(levels(y)[elements-1], res[elements])
} else {
list(elements - 1 + y.min, res[elements])
}
}发布于 2013-06-07 20:36:28
这不是一个答案,但可能对问题的框架有帮助。似乎最坏的情况是将许多短群相加,并且这似乎与向量的大小成线性关系
> n = 100000; x = runif(n); f = sample(n/2, n, TRUE)
> system.time(rowsum(x, f))
user system elapsed
0.228 0.000 0.229
> n = 1000000; x = runif(n); f = sample(n/2, n, TRUE)
> system.time(rowsum(x, f))
user system elapsed
1.468 0.040 1.514
> n = 10000000; x = runif(n); f = sample(n/2, n, TRUE)
> system.time(rowsum(x, f))
user system elapsed
17.369 0.748 18.166 似乎有两条捷径可用,避免重新排序
> n = 10000000; x = runif(n); f = sample(n/2, n, TRUE)
> system.time(rowsum(x, f, reorder=FALSE))
user system elapsed
16.501 0.476 17.025 并避免对角色的内部强制
> n = 10000000; x = runif(n); f = as.character(sample(n/2, n, TRUE));
> system.time(rowsum(x, f, reorder=FALSE))
user system elapsed
8.652 0.268 8.949 然后似乎涉及到的基本操作--计算出分组因子的唯一值(以预先分配结果向量)并进行求和
> n = 10000000; x = runif(n); f = sample(n/2, n, TRUE)
> system.time({ t = tabulate(f); sum(x) })
user system elapsed
0.640 0.000 0.643 因此,是的,似乎有相当大的范围来实现更快的单一用途。这对于data.table来说似乎很自然,而且在C中实现起来也不是太难。这是一个混合的解决方案,使用R来制表,使用“经典”的C界面来计算总和
library(inline)
rowsum1.1 <- function(x, f) {
t <- tabulate(f)
crowsum1(x, f, t)
}
crowsum1 = cfunction(c(x_in="numeric", f_in="integer", t_in = "integer"), "
SEXP res_out;
double *x = REAL(x_in), *res;
int len = Rf_length(x_in), *f = INTEGER(f_in);
res_out = PROTECT(Rf_allocVector(REALSXP, Rf_length(t_in)));
res = REAL(res_out);
memset(res, 0, Rf_length(t_in) * sizeof(double));
for (int i = 0; i < len; ++i)
res[f[i] - 1] += x[i];
UNPROTECT(1);
return res_out;
")使用
> system.time(r1.1 <- rowsum1.1(x, f))
user system elapsed
1.276 0.092 1.373 要实际返回与rowsum相同的结果,需要将其塑造为具有适当dim名称的矩阵
rowsum1 <- function(x, f) {
t <- tabulate(f)
r <- crowsum1(x, f, t)
keep <- which(t != 0)
matrix(r[keep], ncol=1, dimnames=list(keep, NULL))
}
> system.time(r1 <- rowsum1(x, f))
user system elapsed
9.312 0.300 9.641因此,对于所有这些工作,我们只快了两倍(而且不太通用-- x必须是数字,f必须是整数;没有nA值)。是的,存在效率低下的问题,例如,分配没有计数的空间级别(尽管这避免了对名称的字符向量进行昂贵的强制)。
发布于 2013-06-08 18:27:46
为了补充Martin的代码,这里有一些基于Rcpp的版本。
int increment_maybe(int value, double vec_i){
return vec_i == 0 ? value : ( value +1 ) ;
}
// [[Rcpp::export]]
NumericVector cpprowsum2(NumericVector x, IntegerVector f){
std::vector<double> vec(10) ;
vec.reserve(1000);
int n=x.size();
for( int i=0; i<n; i++){
int index=f[i];
while( index >= vec.size() ){
vec.resize( vec.size() * 2 ) ;
}
vec[ index ] += x[i] ;
}
// count the number of non zeros
int s = std::accumulate( vec.begin(), vec.end(), 0, increment_maybe) ;
NumericVector result(s) ;
CharacterVector names(s) ;
std::vector<double>::iterator it = vec.begin() ;
for( int i=0, j=0 ; j<s; j++ ,++it, ++i ){
// move until the next non zero value
while( ! *it ){ i++ ; ++it ;}
result[j] = *it ;
names[j] = i ;
}
result.attr( "dim" ) = IntegerVector::create(s, 1) ;
result.attr( "dimnames" ) = List::create(names, R_NilValue) ;
return result ;
}C++代码处理所有事情,包括格式化成rowsum提供的矩阵格式,并显示(稍微)更好的性能(至少在示例中是这样)。
# from Martin's answer
> system.time(r1 <- rowsum1(x, f))
user system elapsed
0.014 0.001 0.015
> system.time(r3 <- cpprowsum2(x, f))
user system elapsed
0.011 0.001 0.013
> identical(r1, r3)
[1] TRUEhttps://stackoverflow.com/questions/16975034
复制相似问题