Here's my attempt at doing this using Rcpp
(first time using the package, so do point out my inefficiencies):
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])
}
It's pretty fast for Martin's example data, but will only work if the factor consists of non-negative integers and will consume memory on the order of the largest integer in the factor vector (one obvious improvement to the above is to subtract min from max to decrease memory usage - which can be done in either the R function or the C++ one).
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
Also note that a lot of the slowdown (as compared to tabulate
) happens in the R code, so if you move that to C++ instead, you should see more improvement:
system.time(rowsum_helper(x,f))
# user system elapsed
# 0.210 0.018 0.228
Here's a generalization that will handle almost any y
, but will be a little bit slower (I'd actually prefer doing this in Rcpp, but don't know how to handle arbitrary R types there):
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])
}
}