50

I've got a list of lists, call it listHolder, which has length 5.

Every element in listHolder is a list of numeric data, with 160 or so elements.

I need to turn this list of lists into a data.frame of length 5, with each element being a numeric vector with 160 or so elements.

But everything I've tried, from iterating through the list of lists and turning each element with as.numeric(unlist(listHolder[[i]])), to

data.frame(matrix(unlist(listHolder), nrow = length(totalKeywords), byrow = T))

ends up creating a data frame of length 160 or so, with each element being a numeric vector with 5 or so elements.

How do I do what I want?

Attempting data.frame(matrix(unlist(totalKeywords), nrow=132, byrow=T)) yields the opposite of what I want - 160 small items each 5 elements long.

Léo Léopold Hertz 준영
  • 134,464
  • 179
  • 445
  • 697
Bacter
  • 623
  • 1
  • 5
  • 6
  • 17
    Try `do.call(rbind, listHolder)`. – dimitris_ps Apr 16 '15 at 12:26
  • 1
    possible duplicate of [R list to data frame](http://stackoverflow.com/questions/4227223/r-list-to-data-frame) –  Apr 16 '15 at 12:28
  • I think he wants to turn each listHolder item in a column, so it would be `cbind` instead of `rbind`. – Molx Apr 16 '15 at 12:28
  • 2
    Why not `as.data.frame(listHolder)` – shadow Apr 16 '15 at 12:35
  • Could you provide sample data for `listHolder`? – Hack-R Apr 16 '15 at 12:47
  • Sure: print(listHolder) = [[1]] [1] 0 0 0 0 1 1 0 1 0 1... (on for 160 or so), [[2]] [1] 4 0 2 4 23 0 3 2 4... (on for 160 or so), [[3]] [1] 2 3 1 3 3.... and so on. I tried the solution in "R list to data frame", and ended up with 160 objects called "X1" "X2" "X3" and so on, each of them with five members. doing do.call(cbind, listHolder) gives me what looks like a matrix - the columns labeled with [,1] [,2] [,3] and the rows with [1,], [2,], [3,] and so on – Bacter Apr 16 '15 at 14:51

6 Answers6

51

AS @dimitris_ps mentioned earlier, the answer could be:

do.call(rbind, listHolder)

Since do.call naturally "strips" 1 level of the "list of list", obtaining a list, not a list of lists.

After that, rbind can handle the elements on the list and create a matrix.

Camilo Abboud
  • 879
  • 7
  • 7
  • 4
    This extremely useful when the "rows" were named lists, as it keeps the names of the list (assuming the names are consistent) – tmrlvi Nov 02 '18 at 12:07
  • ...and it does not change data types like 'unlist' above. This is the Answer! – ned Feb 25 '21 at 23:59
  • Super useful! However, is there a way to incude the number/name of each list into the final dataframe? – vog Nov 16 '21 at 18:40
32

The value of nrow needs to be fixed. I fixed your code as follows:

dd  <-  as.data.frame(matrix(unlist(listHolder), nrow=length(unlist(listHolder[1]))))
Eray Balkanli
  • 7,752
  • 11
  • 48
  • 82
Noha Elprince
  • 1,924
  • 1
  • 16
  • 10
  • 6
    Is there anything I could do to preserve types? Following this approach, if one column was `POSIXct`, it converts it to `chr`. – Rafael Jul 12 '17 at 14:05
  • Types can be preserved by using `Reduce` (as in my answer). – luco00 Jul 13 '21 at 10:21
18

This is the easiest solution I've found.

library(jsonlite)
library(purrr)
library(data.table)

dt_list <- map(list_of_lists, as.data.table)
dt <- rbindlist(dt_list, fill = TRUE, idcol = T)

dt
David
  • 432
  • 3
  • 10
  • The only way that worked for me. Still, does not preserve `POSIXct` as was asked earlier. – Marco Sep 13 '21 at 09:21
  • Do you really need the `library(jsonlite)`? You can replace that with `library(magrittr)` and have it in one go: `map(list_of_lists, as.data.table) %>% rbindlist(...)` – Valentin_Ștefan Aug 19 '22 at 15:16
13

I keep coming to this question and usually end up adapting the current answers to my need.

Current answers either mess up with types of variables, or do not handle well proper list of lists (note the plural).

tl;dr: use the following:

This works when each element in listHolder contains a column of the dataframe

df <- data.frame(lapply(listHolder, function(x) Reduce(c, x)))

This works when each element in listHolder contains a row of the dataframe

df <- do.call(rbind, lapply(listHolder, data.frame))

Minimal Working Example (list elements are columns)

The following code provides a MWE and reviews other answers. The #General Approach is what I would recommend to use.

listHolder <- list(
  A = rep(list(1, 2), 80),
  B = rep(c(3, 4), 80),
  C = rep(c("a", "b"), 80),
  D = rep(list("c", "d"), 80),
  E = rep(as.POSIXct(10485849600, origin = "1582-10-14", tz = "GMT"), 160)
)


# @Noha's Answer
data1  <-  as.data.frame(matrix(unlist(listHolder), nrow=length(unlist(listHolder[1]))))
# Try this (mess up with types)
str(data1)

# @Camilo's Answer
data2 <- data.frame(do.call(cbind, listHolder))
# Try this (each column becomes a list)
str(data2)

# General Approach
data3 <- data.frame(lapply(listHolder, function(x) Reduce(c, x)))
str(data3)

Minimal Working Example (list elements are rows)

This code should be used when each element in the list is supposed to hold a row in the dataframe

listHolder <- list(
  row1 = list(name = "foo", surname = "bar", age = 90),
  row2 = list(name = "foo", surname = "foo", age = 29),
  row3 = list(name = "bar", surname = "foo", age = 45),
  row4 = list(name = "bar", surname = "bar", age = 10)
)

# A simple rbind won't work (each column is still a list)
data1 <- do.call(rbind, listHolder)
str(data1)

# General Approach (now it's better)
data2 <- do.call(rbind, lapply(listHolder, data.frame))
str(data2)
luco00
  • 501
  • 5
  • 9
  • thanks for this answer. It is exactly what I am looking for but some of my data has a catch. There are NULL values for some elements in some lists. These values are ignored so the lists are of different lengths and it trips over. Do you have a solution that can handle this? – Soccerama Aug 11 '22 at 03:59
  • I guess you would need to clean the lists first. For example you can try this: `listHolder <- lapply(listHolder, function(x) { Map(function(z){ifelse(is.null(z), NA, z)}, x) })`. This will replace all NULL values in your sublists with an NA. You can then replace this in your data.frame as you wish, or use other replacement values. This will not work however for vectors. I.e. if your main list contains vector of different lengths, you would need further cleaning (and I would probably go with a loop in this case). – luco00 Aug 11 '22 at 15:50
11

This achieves a similar result, but is more intuitive (to me at least)

#Generate fake data 
listoflists=list(c(1,'a',3,4),c(5,'b',6,7))

#Convert to a dataframe, transpose, and convert the resulting matrix back to a dataframe
df= as.data.frame(t(as.data.frame(listoflists)))

#Strip out the rownames if desired
rownames(df)<-NULL

#Show result
df
sean
  • 520
  • 7
  • 17
-1

I think this is easier than the previous solutions:

mydf = data.frame(x1 = c('a', 'b', 'c'))
mylist = list(c(4, 5), c(4, 5), c(4, 5))
mydf$x2 = mylist
print(mydf)
  x1   x2
1  a 4, 5
2  b 4, 5
3  c 4, 5
Zhaochen He
  • 610
  • 4
  • 12