17

I have list where the elementnames are ID-tags and contains a vector with numeric values. These are of unequal(!) length.

I want to transform it to a data frame where I have the ID in one column and the numeric values in another column. E.g.:

$`1`  
[1] 1 2   
$`2`  
[1] 1 2 3 
$`3`  
[1] 1   

To:

ID   Obs  
1    1  
1    2
2    1
2    2
2    3
3    1
Jaap
  • 81,064
  • 34
  • 182
  • 193
ego_
  • 1,409
  • 6
  • 21
  • 31

4 Answers4

20

Here is one way:

## your list
ll <- list("1" = 1:2, "2" = 1:3, "3" = 1:2)
## convert to data.frame
dl <- data.frame(ID = rep(names(ll), sapply(ll, length)),
                 Obs = unlist(ll))

This gives:

> dl
   ID Obs
11  1   1
12  1   2
21  2   1
22  2   2
23  2   3
31  3   1
32  3   2

The first line in the data.frame() call is just some code to repeat the names() of the list the required number of times. The second line just unlists the list converting it into a vector.

Gavin Simpson
  • 170,508
  • 25
  • 396
  • 453
  • I gave you the correct answer as your method was fastest:
    > system.time(melt(X))
    user system elapsed
    3.12 0.11 3.24
    > system.time(data.frame(ID = rep(names(X), sapply(X, length)), Obs = unlist(X)))
    user system elapsed
    0.08 0.00 0.07
    – ego_ Sep 28 '12 at 12:18
  • I cant seem to add line breaks, sorry for the mess :S – ego_ Sep 28 '12 at 12:20
  • You can use `lengths` to avoid the `rep(sapply())`. – sebastian-c Jul 20 '23 at 14:26
  • @sebastian-c good point, I don't think `lengths()` was part of the language back in 2012, but that would be better now. – Gavin Simpson Jul 20 '23 at 16:15
10

Use reshape2 and melt which has a method melt.list

.list <- list(`1` = 1:2, `2` = 1:3, `3` = 1:2)
library(reshape2)
melt(.list)
##   value L1
## 1     1  1
## 2     2  1
## 3     1  2
## 4     2  2
## 5     3  2
## 6     1  3
## 7     2  3
mnel
  • 113,303
  • 27
  • 265
  • 254
7

A good and still missing alternative to the already posted solutions is the stack-function:

df <- stack(ll)[2:1]

which gives:

> df
  ind values
1   1      1
2   1      2
3   2      1
4   2      2
5   2      3
6   3      1
7   3      2

Using setNames as well, you can get the exact desired format:

df <- setNames(stack(ll)[2:1], c('ID','Obs'))

which gives:

> df
  ID Obs
1  1   1
2  1   2
3  2   1
4  2   2
5  2   3
6  3   1
7  3   2

Used data:

ll <- list("1" = 1:2, "2" = 1:3, "3" = 1:2)
Jaap
  • 81,064
  • 34
  • 182
  • 193
1

A solution using base functions

List <- list('1'=c(1,2), '2'= c(1,2,3), '3'=1)
x <- unlist(List)  # as suggested by Gavin Simpson
data.frame(ID=substr(names(x),1,1), Obs=x)
   ID Obs
11  1   1
12  1   2
21  2   1
22  2   2
23  2   3
3   3   1

If you want the rownames to be 1,2,3,4,5,6 the try this (using setNames):

data.frame(ID=substr(names(x),1,1), Obs=setNames(x, NULL))
  ID Obs
1  1   1
2  1   2
3  2   1
4  2   2
5  2   3
6  3   1

This solution is valid only if all names have the same length, otherwise it'll fail, and it'd be better using Gavin's solution. See for example:

List2 <- list('week1'=c(1,2), 'week2'= c(1,2,3), 'week3'=1)
x <- unlist(List2)  
data.frame(ID=substr(names(x),1,nchar(names(x)[1])-1), Obs=setNames(x, NULL))

    ID   Obs
1 week1   1
2 week1   2
3 week2   1
4 week2   2
5 week2   3
6 week3   1
Jilber Urbina
  • 58,147
  • 10
  • 114
  • 138
  • I think you can just do `x <- unlist(List)` can't you? – Gavin Simpson Sep 28 '12 at 12:08
  • @Gavin Simpson you're right, now I've just edited my answer including your comment. – Jilber Urbina Sep 28 '12 at 12:11
  • It seems like when I unlist it adds a number to the element name causing the rest of the script to fail for my purpose :S – ego_ Sep 28 '12 at 12:16
  • @Jilber sorry, but now it changes all the names to either "2" or "5". The elementnames are factors consisting of ID.Seasons.Week, e.g. 2225.Winter.1, if that is of any help. I like the angle of using basic functions when possible so it would be nice if this could work as well.. – ego_ Sep 28 '12 at 12:26