Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bind vectors across lists to single list of matrices

Tags:

list

r

I'd like to join multiple vectors across separate lists and output a single list of matrices. The idea is that all items of the list with the same name, for example all the a items, are joined by rows as a matrix. The added complication is that these vectors can be of different lengths, so rbind is not straightforward to implement; the missing values in the matrix can be appended with NAs.

Example

Input lists:

list1 <- list(a = 1:5, b = 6:10, c = 11:15)
list2 <- list(a = 1:4, b = 6:9, c = 11:14)
list3 <- list(a = 1:3, b = 6:8, c = 11:13)

list1
# $a
# [1] 1 2 3 4 5
# 
# $b
# [1]  6  7  8  9 10
# 
# $c
# [1] 11 12 13 14 15
# 

The desired output I'm hoping to obtain is a list with as many matrices as there are unique list items, where each matrix consists of the vectors of differing lengths bound by rows:

# $a
#      [,1] [,2] [,3] [,4] [,5]
# [1,]    1    2    3    4    5
# [2,]    1    2    3    4   NA
# [3,]    1    2    3   NA   NA
# 
# $b
#      [,1] [,2] [,3] [,4] [,5]
# [1,]    6    7    8    9   10
# [2,]    6    7    8    9   NA
# [3,]    6    7    8   NA   NA
# 
# $c
#      [,1] [,2] [,3] [,4] [,5]
# [1,]   11   12   13   14   15
# [2,]   11   12   13   14   NA
# [3,]   11   12   13   NA   NA

How would I go about writing a function that does this that also scales up to merging longer lists with vectors of varying lengths?

like image 480
sebpardo Avatar asked Aug 07 '19 19:08

sebpardo


People also ask

How to bind two matrices with different columns?

Use do.call (rbind,...) You may also be interested in the rbind.fill.matrix () function from the "plyr" package, which will also let you bind matrices with differing columns, filling in with NA where necessary. Show activity on this post. Another option, if you have data.frame and not matrix , is to use rbindlist from data.table package.

How do I construct a matrix containing two vectors in R?

The following R code explains how to construct a matrix containing our two vectors. For this, we can use the cbind function: Note that all variables of matrices need to have the same data class. For that reason our numeric vector was automatically converted to the character class.

How many character elements does the second vector have?

The second vector consists of ten character elements. Note that both vectors have the same length (i.e. 10). This is important if we want to merge our two vectors in a data frame or matrix later on. This example shows how to create a data frame based on our two vectors using the data.frame function:

How many numeric values are there in a vector?

It consists of ten numeric values ranging from 1 to 10. Let’s create a second vector object: The second vector consists of ten character elements. Note that both vectors have the same length (i.e. 10).


Video Answer


5 Answers

One option is to transpose the list of lists, then reduce the list elements to a single dataset with cbind.fill, get the transpose (t) and assign the row names to NULL

library(tidyverse)
library(rowr)
list(list1, list2, list3) %>% 
    transpose %>% 
    map(~ reduce(.x, cbind.fill, fill = NA) %>% 
          t %>% 
         `row.names<-`(NULL))
#$a
#     [,1] [,2] [,3] [,4] [,5]
#[1,]    1    2    3    4    5
#[2,]    1    2    3    4   NA
#[3,]    1    2    3   NA   NA

#$b
#     [,1] [,2] [,3] [,4] [,5]
#[1,]    6    7    8    9   10
#[2,]    6    7    8    9   NA
#[3,]    6    7    8   NA   NA

#$c
#     [,1] [,2] [,3] [,4] [,5]
#[1,]   11   12   13   14   15
#[2,]   11   12   13   14   NA
#[3,]   11   12   13   NA   NA

Or using base R

do.call(Map, c(f = function(...) {l1 <- list(...)
   do.call(rbind, lapply(l1, `length<-`, max(lengths(l1))))},  
      mget(paste0("list", 1:3))))
like image 59
akrun Avatar answered Oct 23 '22 09:10

akrun


a data.table solution, just for fun:

plouf <- list(list1,list2,list3)

lapply(names(list1),function(name){
  lapply(plouf,function(x){
     as.data.table(t(x[[name]]))
     }) %>% 
    rbindlist(.,fill =T) %>%
`colnames<-`(NULL)
}) %>% setNames(names(list1))


$a

1:  1  2  3  4  5
2:  1  2  3  4 NA
3:  1  2  3 NA NA

$b

1:  6  7  8  9 10
2:  6  7  8  9 NA
3:  6  7  8 NA NA

$c

1: 11 12 13 14 15
2: 11 12 13 14 NA
3: 11 12 13 NA NA

the first loop is on the list name. The second loop loop is on the list of list, and extract the element of each list, transpose it into a data.table with unique row, to be able to use rbindlist which can fill missing columns.

without data.table, so similar but less good than what akrun proposed:

library(plyr)

lapply(names(list1),function(name){
  lapply(plouf,function(x){
    t(x[[name]])%>%
      as.data.frame
  }) %>% 
   rbind.fill %>%
    `colnames<-`(NULL)
}) %>% setNames(names(list1))
like image 4
denis Avatar answered Oct 23 '22 07:10

denis


You may use 1. rapply to adjust the lengths of the sublists, and 2. t(mapply) to get the matrices by selecting with '[['.

listn <- list(list1, list2, list3)

setNames(lapply(seq(listn), function(x) 
  t(mapply(`[[`, rapply(listn, `length<-`, value=5, how="list"), x))), names(el(listn)))
# $a
#      [,1] [,2] [,3] [,4] [,5]
# [1,]    1    2    3    4    5
# [2,]    1    2    3    4   NA
# [3,]    1    2    3   NA   NA
# 
# $b
#      [,1] [,2] [,3] [,4] [,5]
# [1,]    6    7    8    9   10
# [2,]    6    7    8    9   NA
# [3,]    6    7    8   NA   NA
# 
# $c
#      [,1] [,2] [,3] [,4] [,5]
# [1,]   11   12   13   14   15
# [2,]   11   12   13   14   NA
# [3,]   11   12   13   NA   NA

In case the lengths are unknown use this code:

max(rapply(listn, length))
# [1] 5
like image 2
jay.sf Avatar answered Oct 23 '22 07:10

jay.sf


Just a doodle of mine:

library(magrittr)
list(list1, list2, list3) %>% 
  do.call("rbind", .) %>%
  as.data.frame() %>% 
  sapply(., function(x) lapply(x, `length<-`, max(lengths(x)))) %>% 
  apply(., 2, as.list) %>% 
  lapply(., function(x) do.call(rbind, x))
# $a
#      [,1] [,2] [,3] [,4] [,5]
# [1,]    1    2    3    4    5
# [2,]    1    2    3    4   NA
# [3,]    1    2    3   NA   NA
# 
# $b
#      [,1] [,2] [,3] [,4] [,5]
# [1,]    6    7    8    9   10
# [2,]    6    7    8    9   NA
# [3,]    6    7    8   NA   NA
# 
# $c
#      [,1] [,2] [,3] [,4] [,5]
# [1,]   11   12   13   14   15
# [2,]   11   12   13   14   NA
# [3,]   11   12   13   NA   NA
like image 2
M-- Avatar answered Oct 23 '22 09:10

M--


Using base R, we can concatenate all the lists together at same level (list_df). Loop through unique names in list_df and subset them and create a list of matrices of similar named elements.

list_df <- c(list1, list2, list3)

lapply(unique(names(list_df)), function(x) {
     temp <- list_df[names(list_df) == x]
     t(sapply(temp, `[`, seq_len(max(lengths(temp)))))
})

#[[1]]
#  [,1] [,2] [,3] [,4] [,5]
#a    1    2    3    4    5
#a    1    2    3    4   NA
#a    1    2    3   NA   NA

#[[2]]
#  [,1] [,2] [,3] [,4] [,5]
#b    6    7    8    9   10
#b    6    7    8    9   NA
#b    6    7    8   NA   NA

#[[3]]
#  [,1] [,2] [,3] [,4] [,5]
#c   11   12   13   14   15
#c   11   12   13   14   NA
#c   11   12   13   NA   NA
like image 1
Ronak Shah Avatar answered Oct 23 '22 09:10

Ronak Shah