19

I have a list which contains list entries, and I need to transpose the structure. The original structure is rectangular, but the names in the sub-lists do not match.

Here is an example:

ax <- data.frame(a=1,x=2)
ay <- data.frame(a=3,y=4)
bw <- data.frame(b=5,w=6)
bz <- data.frame(b=7,z=8)
before <- list(  a=list(x=ax, y=ay),   b=list(w=bw, z=bz))

What I want:

after  <- list(w.x=list(a=ax, b=bw), y.z=list(a=ay, b=bz))

I do not care about the names of the resultant list (at any level).

Clearly this can be done explicitly:

after <- list(x.w=list(a=before$a$x, b=before$b$w), y.z=list(a=before$a$y, b=before$b$z))

but this is ugly and only works for a 2x2 structure. What's the idiomatic way of doing this?

Matthew Lundberg
  • 42,009
  • 6
  • 90
  • 112
  • You can transpose a data frame `d` by collecting the rows, say, with `apply(d, 1L, identity)`. So try coercing `before` into a data frame; one way to do that would be with `tibble::as_tibble()`, from the tibble package. That said, using `purrr::transpose()`, as @alistaire suggested, is faster than using base R's `apply()`. (It's implemented in C.) – egnha Apr 29 '17 at 11:44
  • @egnha Not possible here. The list entries themselves are data frames. – Matthew Lundberg Apr 29 '17 at 15:45
  • Try it with `d <- tibble::as_tibble(before)`. – egnha Apr 30 '17 at 16:46

5 Answers5

22

The following piece of code will create a list with i-th element of every list in before:

lapply(before, "[[", i)

Now you just have to do

n <- length(before[[1]]) # assuming all lists in before have the same length
lapply(1:n, function(i) lapply(before, "[[", i))

and it should give you what you want. It's not very efficient (travels every list many times), and you can probably make it more efficient by keeping pointers to current list elements, so please decide whether this is good enough for you.

Victor K.
  • 4,054
  • 3
  • 25
  • 38
13

The purrr package now makes this process really easy:

library(purrr)

before %>% transpose()

## $x
## $x$a
##   a x
## 1 1 2
## 
## $x$b
##   b w
## 1 5 6
## 
## 
## $y
## $y$a
##   a y
## 1 3 4
## 
## $y$b
##   b z
## 1 7 8
alistaire
  • 42,459
  • 4
  • 77
  • 117
5

Here's a different idea - use the fact that data.table can store data.frame's (in fact, given your question, maybe you don't even need to work with lists of lists and could just work with data.table's):

library(data.table)

dt = as.data.table(before)
after = as.list(data.table(t(dt)))
eddi
  • 49,088
  • 6
  • 104
  • 155
  • This is an interesting approach. I've done some investigating on `data.table`, but haven't used it nearly enough. – Matthew Lundberg Apr 23 '13 at 22:30
  • 6
    This doesn't actually require data.table `as.list(data.frame(do.call(rbind, before)))` will work as well. – mnel Apr 24 '13 at 01:18
3

While this is an old question, i found it while searching for the same problem, and the second hit on google had a much more elegant solution in my opinion:

list_of_lists <- list(a=list(x="ax", y="ay"), b=list(w="bw", z="bz"))
new <- do.call(rbind, list_of_lists) 

new is now a rectangular structure, a strange object: A list with a dimension attribute. It works with as many elements as you wish, as long as every sublist has the same length. To change it into a more common R-Object, one could for example create a matrix like this:

new.dims <- dim(new)
matrix(new,nrow = new.dims[1])

new.dims needed to be saved, as the matrix() function deletes the attribute of the list. Another way:

new <- do.call(c, new) 
dim(new) <- new.dims

You can now for example convert it into a data.frame with as.data.frame() and split it into columns or do column wise operations. Before you do that, you could also change the dim attribute of the matrix, if it fits your needs better.

zerweck
  • 657
  • 7
  • 14
  • 1
    Except it gives deceiving results. All matrix elements are lists – Rich Scriven Oct 05 '15 at 22:11
  • Hmm, not quite but similar. The result is a list of 4 single character elements (True matrices can only contain atomic types) with a dimension attribute. But depending on the use case, this is not that bad, it just needs to be converted to a "true" matrix. I will edit my solution to reflect that. – zerweck Oct 06 '15 at 16:47
  • `rbind` on the list of lists is a solid idea. – Neal Fultz Oct 28 '17 at 20:08
1

I found myself with this problem but I needed a solution that kept the names of each element. The solution I came up with should also work when the sub lists are not all the same length.

invertList = function(l){
  elemnames = NULL
  for (i in seq_along(l)){
    elemnames = c(elemnames, names(l[[i]]))
  }

  elemnames = unique(elemnames)

  res = list()
  for (i in seq_along(elemnames)){
    res[[elemnames[i]]] = list()
    for (j in seq_along(l)){
      if(exists(elemnames[i], l[[j]], inherits = F)){
        res[[i]][[names(l)[j]]] = l[[names(l)[j]]][[elemnames[i]]]
      }
    }
  }
  res
}
Chechy Levas
  • 2,206
  • 1
  • 13
  • 28