Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

cbind with partially nested list

I'm trying to cbind or unnest or as.data.table a partially nested list.

id <- c(1,2)
A <- c("A1","A2","A3")
B <- c("B1")
AB <- list(A=A,B=B)
ABAB <- list(AB,AB)
nested_list <- list(id=id,ABAB=ABAB)

The length of id is the same as ABAB (2 in this case). I don't know how to unlist a part of this list (ABAB) and cbind another part (id). Here's my desired result as a data.table:

data.table(id=c(1,1,1,2,2,2),A=c("A1","A2","A3","A1","A2","A3"),B=rep("B1",6))
   id  A  B
1:  1 A1 B1
2:  1 A2 B1
3:  1 A3 B1
4:  2 A1 B1
5:  2 A2 B1
6:  2 A3 B1
like image 533
Pierre Lapointe Avatar asked Jul 17 '17 18:07

Pierre Lapointe


4 Answers

I haven't tested for more general cases, but this works for the OP example:

library(data.table)

as.data.table(nested_list)[, lapply(ABAB, as.data.table)[[1]], id]
#   id  A  B
#1:  1 A1 B1
#2:  1 A2 B1
#3:  1 A3 B1
#4:  2 A1 B1
#5:  2 A2 B1
#6:  2 A3 B1

Or another option (which is probably faster, but is more verbose):

rbindlist(lapply(nested_list$ABAB, as.data.table),
          idcol = 'id')[, id := nested_list$id[id]]
like image 62
eddi Avatar answered Sep 18 '22 23:09

eddi


This is some super ugly base R, but produces the desired output.

Reduce(rbind, Map(function(x, y) setNames(data.frame(x, y), c("id", "A", "B")),
                  as.list(nested_list[[1]]),
                  lapply(unlist(nested_list[-1], recursive=FALSE),
                         function(x) Reduce(cbind, x))))
  id  A  B
1  1 A1 B1
2  1 A2 B1
3  1 A3 B1
4  2 A1 B1
5  2 A2 B1
6  2 A3 B1

lapply takes the a list of two elements (each containing the A and B variables) extracted with unlist and recursive=FALSE. It returns a list of character matrices with the B variable filled in by recycling. A list of the individual id variables from as.list(nested_list[[1]]) and the lit of matrices are fed to Map which converts corresponding pairs to a data.frame and gives the columns the desired names and returns a list of data.frames. Finally, this list of data.frames is fed to Reduce, which rbinds the results to a single data.frame.

The final Reduce(rbind, could be replaced by data.tables rbindlist if desired.

like image 42
lmo Avatar answered Sep 20 '22 23:09

lmo


Here's another hideous solution

max_length = max(unlist(lapply(nested_list, function(x) lapply(x, lengths))))
data.frame(id = do.call(c, lapply(nested_list$id, rep, max_length)), 
           do.call(rbind, lapply(nested_list$ABAB, function(x)
               do.call(cbind, lapply(x, function(y) {
                   if(length(y) < max_length) {
                       rep(y, max_length)
                   } else {
                       y
                   }
               })))))
#  id  A  B
#1  1 A1 B1
#2  1 A2 B1
#3  1 A3 B1
#4  2 A1 B1
#5  2 A2 B1
#6  2 A3 B1
like image 30
d.b Avatar answered Sep 20 '22 23:09

d.b


And one more, also inelegant- but I`d gone too far by the time I saw the other answers.

restructure <- function(nested_l) {
  ids <- as.numeric(max(unlist(lapply(unlist(nested_l, recursive = FALSE), function(x){
    lapply(x, length)
  }))))

  temp = data.frame(rep(nested_l$id, each = ids), 
             sapply(1:length(nested_l$id), function(x){
               out <-unlist(lapply(nested_l[[2]], function(y){
                 return(y[x])
               }))
             }))
  names(temp) <- c("id", unique(substring(unlist(nested_l[2]), first = 1, last = 1)))
  return(temp)
}

> restructure(nested_list)
  id  A  B
1  1 A1 B1
2  1 A2 B1
3  1 A3 B1
4  2 A1 B1
5  2 A2 B1
6  2 A3 B1
like image 41
Luke C Avatar answered Sep 20 '22 23:09

Luke C