Rolling Sums by Columns or Rows
Rcpp function for roll sums by column or row
Since it’s pretty useful to be able to do this by row or column, I
included the margin argument with the same usage seen in base::apply
(i.e. 1=rows, 2=columns).
#include <Rcpp.h>
using namespace Rcpp;
using namespace std;
// [[Rcpp::export]]
Rcpp::NumericMatrix matrix_rollsum(SEXP x, int n, int margin) {
Rcpp::NumericMatrix y(x);
int NR = y.nrow();
int NC = y.ncol();
NumericMatrix result(NR,NC);
std::fill( result.begin(), result.end(), NumericVector::get_na() ) ;
if(margin==1){
for(int i = 0; i < NR; ++i){
NumericVector tmpvec = y(i,_);
for(int j = 0; j < NC-n+1;++j){
double s=0.0;
for(int q=j; q<j+n;q++){
s+=tmpvec[q];
}
result(i,j+n-1) = s;
s = 0.0;
}}}
if(margin==2){
for(int i = 0; i < NC; ++i){
NumericVector tmpvec = y(i,_);
for(int j = 0; j < NR-n+1;++j){
double s=0.0;
for(int q=j; q<j+n;q++){
s+=tmpvec[q];
}
result(j+n-1,i) = s;
s = 0.0;
}}}
return result;
}
Benchmarks
mat_lg <- matrix(runif(1e6,1,1000),1e3,1e3)
res1 <- microbenchmark::microbenchmark(
matrix_rollsum = matrix_rollsum(mat_lg, 3,1),
rcpp_colum_roll = rcpp_column_roll(mat_lg,3),
apply_rollsum = apply_rollsum(mat_lg,3),
for_loop = for_loop(mat_lg,3),
row_roll_sum = row_roll_sum(mat_lg,width = 3),
times = 1000
)
knitr::kable(summary(res1))
| expr |
min |
lq |
mean |
median |
uq |
max |
neval |
cld |
| matrix_rollsum |
9.128677 |
10.38814 |
15.78466 |
13.43251 |
17.54006 |
71.10719 |
1000 |
a |
| rcpp_colum_roll |
23.195918 |
26.54276 |
33.65227 |
30.43353 |
38.11125 |
113.20687 |
1000 |
b |
| apply_rollsum |
58.027111 |
72.66437 |
87.12061 |
80.50741 |
94.53146 |
255.69353 |
1000 |
c |
| for_loop |
56.408078 |
71.78122 |
85.21565 |
79.10471 |
89.47916 |
269.55304 |
1000 |
c |
| row_roll_sum |
8.309067 |
10.40819 |
15.62686 |
12.93160 |
17.21942 |
81.76514 |
1000 |
a |
Benchmarks with memory allocation
res2 <- bench::mark(
matrix_rollsum = matrix_rollsum(mat_lg, 3,1),
rcpp_colum_roll = rcpp_column_roll(mat_lg,3),
apply_rollsum = apply_rollsum(mat_lg,3),
for_loop = for_loop(mat_lg,3),
row_roll_sum = row_roll_sum(mat_lg,width = 3),
iterations = 1000
)
summary(res2)[,1:9]
# A tibble: 5 x 6
expression min median `itr/sec` mem_alloc `gc/sec`
<bch:expr> <bch:tm> <bch:tm> <dbl> <bch:byt> <dbl>
1 matrix_rollsum 9.11ms 11.1ms 79.7 15.31MB 29.0
2 rcpp_colum_roll 23.2ms 28.6ms 32.2 7.63MB 3.74
3 apply_rollsum 53.94ms 67.1ms 13.7 52.18MB 188.
4 for_loop 55.18ms 69ms 13.2 33.13MB 17.8
5 row_roll_sum 8.28ms 10.5ms 78.3 22.87MB 51.5
Benchmark Plots
p1 <- ggplot2::autoplot(res1)
p2 <- ggplot2::autoplot(res2)
library(patchwork)
p1/p2

Edit
Cole brought up a great point. Why copy a large matrix? Wouldn't working on the original object utilize less memory? So I rewrote the Rcpp function to use the original object.
#include <Rcpp.h>
using namespace Rcpp;
using namespace std;
// [[Rcpp::export]]
Rcpp::NumericMatrix test(NumericMatrix x, int n, int margin) {
Rcpp::NumericMatrix result(x.nrow(),x.ncol());
std::fill( result.begin(), result.end(), NumericVector::get_na() ) ;
double s=0.0;
if(margin==1){
for(int i = 0; i < x.nrow(); ++i){
for(int j = 0; j < x.ncol()-n+1;++j){
for(int q=j; q<j+n;q++){
s+=x(i,q);
}
result(i,j+n-1) = s;
s = 0.0;
}}}
if(margin==2){
for(int i = 0; i < x.ncol(); ++i){
for(int j = 0; j < x.nrow()-n+1;++j){
for(int q=j; q<j+n;q++){
s+=x(i,q);
}
result(j+n-1,i) = s;
s = 0.0;
}}}
return result;
}
Benchmarks
As Cole suspected, the new function allocated half the memory as the original, yet it was suprisingly 3x slower.
| expr |
min |
lq |
mean |
median |
uq |
max |
neval |
cld |
| matrix_rollsum |
9.317332 |
10.84904 |
15.47414 |
13.75330 |
16.36336 |
101.6147 |
1000 |
a |
| test |
34.498511 |
40.08057 |
47.49839 |
43.26564 |
48.34093 |
211.3246 |
1000 |
b |
# A tibble: 2 x 6
expression min median `itr/sec` mem_alloc `gc/sec`
<bch:expr> <bch:tm> <bch:tm> <dbl> <bch:byt> <dbl>
1 matrix_rollsum 9.15ms 10.1ms 93.7 15.31MB 33.4
2 test 34.1ms 35.4ms 27.5 7.63MB 3.93
