1
votes

I have a list of matrices of equal size:

a <- matrix(data = c(1,2,3,4,5,6,7,8,9), ncol = 3, nrow = 3)

b <- matrix(data = c(9,8,7,6,5,4,3,2,1), ncol = 3, nrow = 3)

c <- matrix(data = c(1,2,3,4,5,6,7,8,9), ncol = 3, nrow = 3)

d <- matrix(data = seq(from = 1, to = 9, by = 1), ncol = 3, nrow = 3)

e <- matrix(data = seq(from = 10, to = 90, by = 10), ncol = 3, nrow = 3)

f <- matrix(data = seq(from = 9, to = 1, by = -1), ncol = 3, nrow = 3)


test_list <- list(a, b, c, d, e, f)

How can I sum every set of three matrices so that the output is two matrices, the first being the sum of a, b and c (output_1) and the second the sum of d, e and f (output_2)? Ideally the output would be a new list of two matrices, e.g.

output_1 <- structure(c(11, 12, 13, 14, 15, 16, 17, 18, 19), .Dim = c(3L, 
3L)) 

output_2 <- structure(c(3L, 6L, 9L, 12L, 15L, 18L, 21L, 24L, 27L), .Dim = c(3L, 
3L))
5
The last three vectors illustrate a typical error in the use of seq. The seq function is intended to take a single integer for its first argument. When it is presented only a vector of length greater than 1 it gets interpreted as seq_along - IRTFM
Eeesht, that is not what i wanted, a careless edit. Fixing - John L. Godlee
It's going to change what the answer will be. Looking at what I originally thought would be teh [1,1] value for the second matrix (1+10+9) and seeing instead 3 was what made me write my first comment. - IRTFM

5 Answers

4
votes
library(purrr)
map(split(test_list, ceiling(seq_along(test_list)/3)), ~reduce(.x , `+`))

$`1`
     [,1] [,2] [,3]
[1,]   11   14   17
[2,]   12   15   18
[3,]   13   16   19

$`2`
     [,1] [,2] [,3]
[1,]    3   12   21
[2,]    6   15   24
[3,]    9   18   27

Credit to this answer for the neat splitting code.

2
votes

Just a doodle with data.table:

data.table::rbindlist(lapply(test_list, function(x) as.data.frame(x)))[, as.list(colSums(.SD)), 
                                     by = gl(ceiling(length(test_list)*nrow(test_list[[1]])/3), 
                                     3, length(test_list)*nrow(test_list[[1]]))]
2
votes

43 seconds ago @iod commented the same thought I had. I present this as a base equivalent to the map-reduce-logic in zack's answer:

lapply( split( test_list, cumsum( rep( c(T,F,F), 2))), function(x){ Reduce( "+", x)})
$`1`
     [,1] [,2] [,3]
[1,]   11   14   17
[2,]   12   15   18
[3,]   13   16   19

$`2`
     [,1] [,2] [,3]
[1,]    3   12   21
[2,]    6   15   24
[3,]    9   18   27

Mine used "Reduce" which is described on a help page with a group of "funprog"-functions entitled "Common Higher-Order Functions in Functional Programming Languages". Like iod, I've sometimes had some difficulty with the Map, and Reduce functions. In this case one needs to use Reduce rather than do.call because "+" is a binary operator and cannot handle vectors with 3 items. Internally its not really that different than the double loop answer of r.user.05apr. Both lapply-split and Reduce are really loops in disguise.

There are now several examples of different mechanisms for construction of "groups of 3". My approach was to construct a logical vector and then run cumsum to make a numeric vector. Masoud used gl. zack used ceiling(seq_along(test_list)/3) and politely credited its inspiration.

1
votes

Transform into an array and apply rowSums:

test_array<-array(c(a,b,c,d,e,f),c(3,3,6))
apply(test_array[,1:3,1:3],2,rowSums)

     [,1] [,2] [,3]
[1,]   11   14   17
[2,]   12   15   18
[3,]   13   16   19

apply(test_array[,1:3,4:6],2,rowSums)

     [,1] [,2] [,3]
[1,]    3   12   21
[2,]    6   15   24
[3,]    9   18   27
1
votes

Or:

Matrix_Number <- 6

res <- vector("list", Matrix_Number / 3)
for (i in (1:(Matrix_Number/3))) {
  res[[i]] <- test_list[[(i-1)*3 + 1]] + test_list[[(i-1)*3 + 2]] + test_list[[(i-1)*3 + 3]]
}
res