Petas Zwegas
Petas Zwegas

Reputation: 85

Sequential adding of n elements

I ran into a problem in R where I need to manipulate a vector in R.

Lets say I have a vector of length 12:

vector <- c(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)

I now need to add the elements 1+2, 3+4, 5+6 etc. into a new vector, in the example that would be:

newvector <- c(3, 7, 11, 15, 19, 23) 

I need to do the same for longer sequences, such that it adds the first three, then 4-6, then 7-9 etc.

newvector <- c(6, 15, 24, 33) 

and so on.

Upvotes: 3

Views: 320

Answers (5)

jblood94
jblood94

Reputation: 16981

An RcppRoll solution with some benchmarking:

library(RcppRoll)
#> Warning: package 'RcppRoll' was built under R version 4.1.2

v <- as.numeric(1:12e4)
n <- 3L

fRoll <- function(v, n) roll_sum(v, n, by = n)
fMatrix <- function(v, n) colSums(matrix(v, nrow = n))
fCumsum <- function(v, n) diff(c(0, cumsum(v))[seq(1, length(v) + 1L, n)])
fReduce <- function(v, n) Reduce(`+`, split(v, 1:n))
fLoop <- function(v, n) {
  vOut <- numeric(ceiling(length(v)/n))
  idx <- 0:(n - 1)
  
  for (i in seq_along(vOut)) {
    vOut[i] <- sum(v[i*n - idx])
  }
  
  return(vOut)
}
fApply1 <- function(v, n) tapply(v, cumsum(seq_along(v) %% n == 1), sum)
fApply2 <- function(v, n) tapply(v, 0:(length(v) - 1) %/% n, sum)

microbenchmark::microbenchmark(fRoll(v, n), fMatrix(v, n), fCumsum(v, n), fReduce(v, n), fLoop(v, n), fApply1(v, n), fApply2(v, n))
#> Unit: microseconds
#>           expr     min       lq      mean   median       uq      max neval
#>    fRoll(v, n)   145.7   215.45   290.512   234.35   284.05   1559.6   100
#>  fMatrix(v, n)   282.0   360.85   428.385   382.20   437.70   1678.0   100
#>  fCumsum(v, n)  1724.5  1805.05  2060.008  1859.15  2094.30   6169.5   100
#>  fReduce(v, n)  1852.5  1943.15  2056.358  1985.75  2096.15   4335.6   100
#>    fLoop(v, n) 19976.5 22618.05 25725.492 23860.95 25300.10  73618.3   100
#>  fApply1(v, n) 69336.2 73841.35 77741.583 76639.80 80791.70 123253.8   100
#>  fApply2(v, n) 69178.3 73870.80 77691.152 76582.65 79066.60 101159.1   100

# check that all the functions return the same result
results <- lapply(list(fRoll, fMatrix, fCumsum, fReduce, fLoop, fApply1, fApply2), function(f) as.numeric(f(v, n)))
sum(duplicated.default(results)) == length(results) - 1L
#> [1] TRUE

Created on 2021-12-02 by the reprex package (v2.0.1)

Probably not going to beat colSums(matrix( in base r.

Upvotes: 0

Ma&#235;l
Ma&#235;l

Reputation: 52004

I would do this: split splits the vector into n groups, and Reduce sums the n-th elements of each group.

newvector <- Reduce(`+`, split(vector,c(1,2)))
[1]  3  7 11 15 19 23

As a function, you could have this:

splitSum <- function(v, n) Reduce(`+`, split(v, c(1:n))) 
splitSum(vector,3)
[1]  6 15 24 33

Upvotes: 3

Ronak Shah
Ronak Shah

Reputation: 388982

Put the vector in a matrix and then use colSums. Here's a function to do that.

vector <- c(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)

calculate_sum <- function(v, n) {
  colSums(matrix(v, nrow = n))
}

calculate_sum(vector, 2)
#[1]  3  7 11 15 19 23

calculate_sum(vector, 3)
#[1]  6 15 24 33

Upvotes: 4

Diego
Diego

Reputation: 356

v <- 1:12

v1 <- rep(0, ((length(v))/2))

for(i in 1:((length(v))/2))
v1[i] <- v[i]+v[i+1]

v1

I simply used a for loop. Perhaps checking if the vector length can be split into groups of 2 and 3 elements before with an if conditional may improve this...

Upvotes: 1

tmfmnk
tmfmnk

Reputation: 39858

One option could be:

tapply(x, cumsum(seq_along(x) %% 2 == 1), sum)

 1  2  3  4  5  6 
 3  7 11 15 19 23 

for n = 3:

tapply(x, cumsum(seq_along(x) %% 3 == 1), sum)

 1  2  3  4 
 6 15 24 33

Upvotes: 3

Related Questions