Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to aggregate matrices within a list based on vector of names?

Tags:

list

r

aggregate

I want to aggregate (sum) matrices within a list according to the names stored in a vector. Here some example data:

lst <- list("111"=matrix(c(1, 0, 6, NA, 1, 0),
                              nrow = 1, byrow = T),
            "112"=matrix(c(6, 2, 2, 0, 3, NA),
                              nrow = 1, byrow = T),
            "113"=matrix(c(2, 3, 0, 0, 1, 1),
                         nrow = 1, byrow = T))
agg.nam <- c(111,113)

My expected result is:

> res
$
     [,1] [,2] [,3] [,4] [,5] [,6]
[1,]    3    3    6    0    2    1

So, the first and third matrices are summed up (with na.rm=TRUE).

I tried first to subset the agg.nam:

lapply(lst, function(x) x[, which(names(x) %in% agg.nam)] )

but I already failed in this point, without aggregating.

like image 496
N.Varela Avatar asked Jan 19 '16 21:01

N.Varela


2 Answers

You can grab the relevant list elements into a matrix with:

do.call(rbind, lst[as.character(agg.nam)])
#      [,1] [,2] [,3] [,4] [,5] [,6]
# [1,]    1    0    6   NA    1    0
# [2,]    2    3    0    0    1    1

All that is then required is calling colSums with na.rm=TRUE (thanks to @docendodiscimus for pointing out this simplification):

colSums(do.call(rbind, lst[as.character(agg.nam)]), na.rm=TRUE)
# [1] 3 3 6 0 2 1

If the matrices had multiple rows, the above simplification wouldn't really work, and the following would do the trick better:

# Grab relevant list elements
mats <- lst[as.character(agg.nam)]

# Replace any instance of NA with 0
mats <- lapply(mats, function(x) {  x[is.na(x)] <- 0 ; x  })

# Sum them up
Reduce("+", mats)
#      [,1] [,2] [,3] [,4] [,5] [,6]
# [1,]    3    3    6    0    2    1
like image 160
josliber Avatar answered Nov 22 '22 03:11

josliber


1) abind This works even if the constituent matrices are not single row matrices. abind creates a 3 dimensional array out of the sublist L and then sum is applied along parallel elements using na.rm = TRUE.

library(abind)

L <- lst[as.character(agg.nam)]
apply(abind(L, along = 3), 1:2, sum, na.rm = TRUE)

In the case of the input data of the question we get the following output matrix:

     [,1] [,2] [,3] [,4] [,5] [,6]
[1,]    3    3    6    0    2    1

2) array This also works and does not use any packages. It works the same except it reshapes L into a 3d array using array. L is from above.

make3d <- function(List) array(unlist(List), c(dim(List[[1]]), length(List)))
apply(make3d(L), 1:2, sum, na.rm = TRUE)

3) mapply Using mapply this defines a parallel sum that removes NAs and then applies it using Reduce. No packages are used. L is from (1).

psum <- function(x, y) array(mapply(sum, x, y, MoreArgs = list(na.rm = TRUE)), dim(x))
Reduce(psum, L)

3a) A variation of (3) is:

sumNA <- function(...) sum(..., na.rm = TRUE)
array(do.call(mapply, c(sumNA, L)), dim(L[[1]]))
like image 43
G. Grothendieck Avatar answered Nov 22 '22 03:11

G. Grothendieck