rbinding a list of list of data.table

Viewed 100

This sounds stupid, but I have a list of list of data.table like this

list(list(table1=data.table(A1=1:3, B1=2:4),
          table2=data.table(A2=3:1, B2=4:2)),
     list(table1=data.table(A1=-1:3, B1=-2:2),
          table2=data.table(A2=-3:1, B2=-4:0)),
     list(table1=data.table(A1=1:3, B1=2:4),
          table2=data.table(A2=3:1, B2=4:2)))

I would like to rbind each tableX together, and return a list of data.table, i.e.

list(table1=data.table(A1=c(1:3, -1:3, 1:3), B1=c(2:4, -2:2, 2:4)),
     table2=data.table(A2=c(3:1, -3:1, 3:1), B2=c(4:2, -4:0, 4:2)))

In the real world scenario, the input would be a list of thousands of lists of tens of data.tables with hundreds of columns each, so hard-coding won't be an option.

5 Answers

An option would be to purrr::transpose your list first and then use rbindlist and Map

out <- Map(data.table::rbindlist, purrr::transpose(l))

Check the output

identical(out,
          list(table1=data.table(A1=c(1:3, -1:3, 1:3), B1=c(2:4, -2:2, 2:4)),
               table2=data.table(A2=c(3:1, -3:1, 3:1), B2=c(4:2, -4:0, 4:2))))
#[1] TRUE

data

l <- list(list(table1=data.table(A1=1:3, B1=2:4),
          table2=data.table(A2=3:1, B2=4:2)),
     list(table1=data.table(A1=-1:3, B1=-2:2),
          table2=data.table(A2=-3:1, B2=-4:0)),
     list(table1=data.table(A1=1:3, B1=2:4),
          table2=data.table(A2=3:1, B2=4:2)))

Consider base R apply family solution with extract function, [[:

table_names <- unlist(unique(lapply(my_original_list, names)))

final_list <- sapply(table_names, function(t) 
                       rbindlist(lapply(my_original_list, `[[`, t)),
                     simplify=FALSE)

final_list

It may not be the most elegant or efficient solution, but my suggestion is to first create a list of just tableXs for a given number X. Then do rbind on this list, and put the output into a new list properly indexed. You may need to modify depending on the specifics of your actual data -- I am assuming that each list within your list has every tableX, and that they are ordered.

See below:

library(data.table)
library(testthat)

dt.list <- list(list(table1=data.table(A1=1:3, B1=2:4),
          table2=data.table(A2=3:1, B2=4:2)),
     list(table1=data.table(A1=-1:3, B1=-2:2),
          table2=data.table(A2=-3:1, B2=-4:0)),
     list(table1=data.table(A1=1:3, B1=2:4),
          table2=data.table(A2=3:1, B2=4:2)))

dt.output <- 
  list(table1=data.table(A1=c(1:3, -1:3, 1:3), B1=c(2:4, -2:2, 2:4)),
     table2=data.table(A2=c(3:1, -3:1, 3:1), B2=c(4:2, -4:0, 4:2)))


# Extract number of tableX's -- assuming same number in each list element.
mylist <- vector(mode = "list", length = length(dt.list[[1]]))
for (li in seq(length(mylist))) {
  # Extract tableXs, put into list.
  list.tableX <- lapply(dt.list, function(x) {
                    return(x[[li]])
                  })
  # Use rbind to put together
  mylist[[li]] <- do.call("rbind", list.tableX)
  names(mylist)[li] <- paste0("table", li)
}

testthat::expect_identical(dt.output, mylist)

Here's a base solution (with and rbindlist)

library(data.table)

apply(simplify2array(lst), 1, rbindlist)

The result is the same as intended:

identical(list(table1=data.table(A1=c(1:3, -1:3, 1:3), B1=c(2:4, -2:2, 2:4)),
              table2=data.table(A2=c(3:1, -3:1, 3:1), B2=c(4:2, -4:0, 4:2))),
          apply(simplify2array(lst), 1, rbindlist))

# [1] TRUE

My first thought was to transpose first, but transposing can be computationally expensive. It's possible to succinctly use normal indexing with purrr::map_dfr:

library(purrr)
map(1:2, ~ map_dfr(l, .))
Related