rbind a dataframe column containing list of different lengths

Viewed 118

I have a data frame, with a column containing a list with different lengths:

IP <- structure(list(V1 = list(l1 = c("M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M"), `l2` = c("D", 
"D", "D", "D", "D", "D", "D", "D", "D", "D", "D", "D", "D", "D", 
"D", "D", "D", "D", "D", "D", "D", "D", "D", "D", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M"), `l3` = c("D", 
"D", "D", "D", "D", "D", "D", "D", "D", "D", "D", "D", "D", "D", 
"D", "D", "D", "D", "D", "D", "D", "D", "D", "D", "D", "D", "D", 
"D", "D", "D", "D", "D", "D", "D", "D", "D", "D", "D", "D", "D", 
"D", "D", "D", "D", "D", "D", "D", "D", "D", "D", "D", "D", "D", 
"D", "D", "D", "D", "D", "D", "D", "D", "D", "D", "D", "D", "D", 
"D", "D", "D", "D", "D", "D", "D", "D", "D", "D", "D", "D", "D", 
"D", "D", "D", "D", "D", "D", "D", "D", "D", "D", "D", "D", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", 
"M"))), class = "data.frame", row.names = c("1", "2", "3"))

I am using the following command to convert the list. This works fine for a smaller dataset.

output <- plyr::ldply(IP$V1, rbind)

But when I apply this to a large dataset (approx >1Million), it runs forever and crashes.

Is there a way to apply this efficiently to larger datasets?

Thanks!

3 Answers

None of the solutions are duplicating what plyr::ldply is giving you. Namely,

out1 <- plyr::ldply(IP$V1, rbind)
out1[,c(1:3, 378:380)]
#   .id 1 2  377  378  379
# 1  l1 M M    M    M    M
# 2  l2 D D <NA> <NA> <NA>
# 3  l3 D D    M    M    M

Complicating this is that the embedded lists are not the same length:

lengths(IP$V1)
#  l1  l2  l3 
# 379 370 379 

The first suggested solution will work much better (without warning) when that difference is remedied.

IP$V1 <- lapply(IP$V1, `length<-`, max(lengths(IP$V1)))
out2 <- data.frame(do.call(rbind, IP$V1))
out2$.id <- seq_along(IP$V1)
dim(out2)
# [1]   3 380
out2[,c(1:3, 378:380)]
#    V1 V2 V3 V378 V379 .id
# l1  M  M  M    M    M   1
# l2  D  D  D <NA> <NA>   2
# l3  D  D  D    M    M   3

If you really want the column names to be just numbers (as strings), you can use out2 <- data.frame(..., check.names = FALSE) or just overwrite them manually. I don't recommend this, but it depends on your needs.

Update after clarification: We could use unnest_wider from tidyr_package:

library(dplyr)
library(tidyr)

IP %>% 
  unnest_wider(V1, names_sep = "_")
  V1_1  V1_2  V1_3  V1_4  V1_5  V1_6  V1_7  V1_8  V1_9  V1_10 V1_11 V1_12
  <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr>
1 M     M     M     M     M     M     M     M     M     M     M     M    
2 D     D     D     D     D     D     D     D     D     D     D     D    
3 D     D     D     D     D     D     D     D     D     D     D     D    
# ... with 367 more variables: V1_13 <chr>, V1_14 <chr>, V1_15 <chr>,
#   V1_16 <chr>, V1_17 <chr>, V1_18 <chr>, V1_19 <chr>, V1_20 <chr>,
#   V1_21 <chr>, V1_22 <chr>, V1_23 <chr>, V1_24 <chr>, V1_25 <chr>,
#   V1_26 <chr>, V1_27 <chr>, V1_28 <chr>, V1_29 <chr>, V1_30 <chr>,
#   V1_31 <chr>, V1_32 <chr>, V1_33 <chr>, V1_34 <chr>, V1_35 <chr>,
#   V1_36 <chr>, V1_37 <chr>, V1_38 <chr>, V1_39 <chr>, V1_40 <chr>,
#   V1_41 <chr>, V1_42 <chr>, V1_43 <chr>, V1_44 <chr>, V1_45 <chr>, ...

First answer: We could first transform to data.table and then use data.table code:

library(data.table)
dt1 <- as.data.table(IP)
dt1[, .(V1 = unlist(V1)), by = setdiff(names(dt1), 'V1')]
 V1
   1:  M
   2:  M
   3:  M
   4:  M
   5:  M
  ---   
1124:  M
1125:  M
1126:  M
1127:  M
1128:  M

Update after clarification: Another option is use stri_list2matrix from stringi, which is very fast.

library(stringi)

op <- as.data.frame(stri_list2matrix(c(IP$V1), byrow = TRUE))
op$.id <- seq_along(IP$V1)

A base R solution is to use lapply, which is also quite fast (though with a lot of variability as seen in the benchmark image).

op3 <-
  as.data.frame(transpose(setDT(lapply(
    c(IP$V1), "length<-", max(lengths(c(ok$V1)))
  ))))
op3$.id <- seq_along(IP$V1)

Another base R solution is to use sapply, which is also fairly fast (though a little slower than lapply.

op2 <- as.data.frame(t(sapply(c(IP$V1), "length<-", max(lengths(c(IP$V1))))))
op2$.id <- seq_along(IP$V1)

Output

# A tibble: 3 × 380
  V1    V2    V3    V4    V5    V6    V7    V8    V9    V10   V11   V12   V13   V14   V15   V16   V17   V18   V19  
  <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr>
1 M     M     M     M     M     M     M     M     M     M     M     M     M     M     M     M     M     M     M    
2 D     D     D     D     D     D     D     D     D     D     D     D     D     D     D     D     D     D     D    
3 D     D     D     D     D     D     D     D     D     D     D     D     D     D     D     D     D     D     D    
# … with 361 more variables: V20 <chr>, V21 <chr>, V22 <chr>, V23 <chr>, V24 <chr>, V25 <chr>, V26 <chr>, V27 <chr>,
#   V28 <chr>, V29 <chr>, V30 <chr>, V31 <chr>, V32 <chr>, V33 <chr>, V34 <chr>, V35 <chr>, V36 <chr>, V37 <chr>,
#   V38 <chr>, V39 <chr>, V40 <chr>, V41 <chr>, V42 <chr>, V43 <chr>, V44 <chr>, V45 <chr>, V46 <chr>, V47 <chr>,
#   V48 <chr>, V49 <chr>, V50 <chr>, V51 <chr>, V52 <chr>, V53 <chr>, V54 <chr>, V55 <chr>, V56 <chr>, V57 <chr>,
#   V58 <chr>, V59 <chr>, V60 <chr>, V61 <chr>, V62 <chr>, V63 <chr>, V64 <chr>, V65 <chr>, V66 <chr>, V67 <chr>,
#   V68 <chr>, V69 <chr>, V70 <chr>, V71 <chr>, V72 <chr>, V73 <chr>, V74 <chr>, V75 <chr>, V76 <chr>, V77 <chr>,
#   V78 <chr>, V79 <chr>, V80 <chr>, V81 <chr>, V82 <chr>, V83 <chr>, V84 <chr>, V85 <chr>, V86 <chr>, V87 <chr>, …

Benchmark

library (tidyverse)

bm <- microbenchmark::microbenchmark(
  r2evans = {IP$V1 <- lapply(IP$V1, `length<-`, max(lengths(IP$V1)));
  out2 <- data.frame(do.call(rbind, IP$V1));
  out2$.id <- seq_along(IP$V1)},
  RduU = {plyr::ldply(IP$V1, rbind)},
  tidyr = {IP %>%
    unnest_wider(V1, names_sep = "_")},
  stringi = {op <- as.data.frame(stri_list2matrix(c(IP$V1), byrow=TRUE)); op$.id <- seq_along(IP$V1)},
  sapply = {as.data.frame(t(sapply(c(IP$V1), "length<-", max(lengths(c(IP$V1)))))); op2$.id <- seq_along(IP$V1)},
  lapply = {op3 <- as.data.frame(transpose(setDT(lapply(c(IP$V1), "length<-", max(lengths(c(ok$V1)))))));
  op3$.id <- seq_along(IP$V1)},
  times = 100
)

microbenchmark:::autoplot(bm)

enter image description here

Unit: microseconds
    expr       min         lq        mean     median         uq       max neval
 r2evans  1503.602  1640.0915  1799.95612  1747.6035  1872.3480  3092.314   100
    RduU  1764.108  2003.0560  2150.63791  2086.5735  2232.9945  4152.803   100
   tidyr 15108.671 15938.5185 17209.04116 16487.6840 17480.8740 33108.209   100
 stringi   747.871   819.4205   875.45533   853.2315   913.2410  1569.510   100
  sapply  1056.223  1173.0940  1294.82064  1255.7130  1337.3275  2450.791   100
  lapply   939.044  1078.7225  1335.96819  1139.3605  1236.4150 13476.396   100

First Answer: You can use data.table as it will be the faster than plyr or tidyr.

library(data.table)

setDT(IP)[, list(V1 = as.character(unlist(V1)))] %>% 
  as.data.frame()

Benchmark

library (dplyr)

microbenchmark::microbenchmark(
  data.table = setDT(IP)[, list(V1 = as.character(unlist(V1)))] %>%
    as.data.frame(),
  tidyr = tidyr::unnest(IP, cols = c(V1)),
  plyr = plyr::ldply(IP$V1, rbind)
)


Unit: microseconds
       expr      min        lq       mean   median        uq       max neval
 data.table  588.723  679.6965  768.05463  745.360  808.5615  1465.043   100
      tidyr 2631.968 2833.8095 3269.19794 3054.737 3393.4345 12726.122   100
       plyr 1173.735 1290.8645 1379.57338 1335.448 1412.0445  2027.333   100
Related