什么是最有效的方式来列表作为数据框?

我经常想要将每个索引具有相同元素types的列表转换为数据框。 例如,我可能有一个列表:

> my.list [[1]] [[1]]$global_stdev_ppb [1] 24267673 [[1]]$range [1] 0.03114799 [[1]]$tok [1] "hello" [[1]]$global_freq_ppb [1] 211592.6 [[2]] [[2]]$global_stdev_ppb [1] 11561448 [[2]]$range [1] 0.08870838 [[2]]$tok [1] "world" [[2]]$global_freq_ppb [1] 1002043 

我想将此列表转换为数据框,其中每个索引元素是一个列。 自然(对我)要去的是使用do.call

 > my.matrix<-do.call("rbind", my.list) > my.matrix global_stdev_ppb range tok global_freq_ppb [1,] 24267673 0.03114799 "hello" 211592.6 [2,] 11561448 0.08870838 "world" 1002043 

直截了当的,但是当我试图把这个matrix作为一个数据框架时,这些列仍然是列表元素,而不是vector:

 > my.df<-as.data.frame(my.matrix, stringsAsFactors=FALSE) > my.df[,1] [[1]] [1] 24267673 [[2]] [1] 11561448 

目前,为了正确地获得数据框架,我使用unlistas.vector遍历每一列,然后as.vector数据框:

 new.list<-lapply(1:ncol(my.matrix), function(x) as.vector(unlist(my.matrix[,x]))) my.df<-as.data.frame(do.call(cbind, new.list), stringsAsFactors=FALSE) 

但是,这看起来效率很低。 有没有更好的方法来做到这一点?

我想你想要:

 > do.call(rbind, lapply(my.list, data.frame, stringsAsFactors=FALSE)) global_stdev_ppb range tok global_freq_ppb 1 24267673 0.03114799 hello 211592.6 2 11561448 0.08870838 world 1002043.0 > str(do.call(rbind, lapply(my.list, data.frame, stringsAsFactors=FALSE))) 'data.frame': 2 obs. of 4 variables: $ global_stdev_ppb: num 24267673 11561448 $ range : num 0.0311 0.0887 $ tok : chr "hello" "world" $ global_freq_ppb : num 211593 1002043 

另一个select是:

 data.frame(t(sapply(mylist, `[`))) 

但是这个简单的操作会导致列表的数据框:

 > str(data.frame(t(sapply(mylist, `[`)))) 'data.frame': 2 obs. of 3 variables: $ a:List of 2 ..$ : num 1 ..$ : num 2 $ b:List of 2 ..$ : num 2 ..$ : num 3 $ c:List of 2 ..$ : chr "a" ..$ : chr "b" 

另一种方法是,沿着相同的路线,但现在的结果与其他解决scheme相同的是:

 data.frame(lapply(data.frame(t(sapply(mylist, `[`))), unlist)) 

[ 编辑:包括@Martin Morgan的两个解决scheme,这些解决scheme比其他解决scheme具有更好的向量dataframe的优势。]一些非常简单的问题上的代表性时间:

 mylist <- list(list(a = 1, b = 2, c = "a"), list(a = 2, b = 3, c = "b")) > ## @Joshua Ulrich's solution: > system.time(replicate(1000, do.call(rbind, lapply(mylist, data.frame, + stringsAsFactors=FALSE)))) user system elapsed 1.740 0.001 1.750 > ## @JD Long's solution: > system.time(replicate(1000, do.call(rbind, lapply(mylist, data.frame)))) user system elapsed 2.308 0.002 2.339 > ## my sapply solution No.1: > system.time(replicate(1000, data.frame(t(sapply(mylist, `[`))))) user system elapsed 0.296 0.000 0.301 > ## my sapply solution No.2: > system.time(replicate(1000, data.frame(lapply(data.frame(t(sapply(mylist, `[`))), + unlist)))) user system elapsed 1.067 0.001 1.091 > ## @Martin Morgan's Map() sapply() solution: > f = function(x) function(i) sapply(x, `[[`, i) > system.time(replicate(1000, as.data.frame(Map(f(mylist), names(mylist[[1]]))))) user system elapsed 0.775 0.000 0.778 > ## @Martin Morgan's Map() lapply() unlist() solution: > f = function(x) function(i) unlist(lapply(x, `[[`, i), use.names=FALSE) > system.time(replicate(1000, as.data.frame(Map(f(mylist), names(mylist[[1]]))))) user system elapsed 0.653 0.000 0.658 

我无法告诉你这在内存或速度方面是“最高效的”,但是在编码方面它是非常高效的:

 my.df <- do.call("rbind", lapply(my.list, data.frame)) 

用data.frame()的lapply()步骤将每个列表项转换成单行数据框,然后与rbind()

尽pipe这个问题早已得到解答,但值得指出的是data.table包中包含了rbindlist完成这个任务的rbindlist

 library(microbenchmark) library(data.table) l <- replicate(1E4, list(a=runif(1), b=runif(1), c=runif(1)), simplify=FALSE) microbenchmark( times=5, R=as.data.frame(Map(f(l), names(l[[1]]))), dt=data.frame(rbindlist(l)) ) 

给我

 Unit: milliseconds expr min lq median uq max neval R 31.060119 31.403943 32.278537 32.370004 33.932700 5 dt 2.271059 2.273157 2.600976 2.635001 2.729421 5 

这个

 f = function(x) function(i) sapply(x, `[[`, i) 

是一个返回提取x的第i个元素的函数的函数。 所以

 Map(f(mylist), names(mylist[[1]])) 

得到一个名为(感谢Map!)的向量列表,可以将其作为一个数据框

 as.data.frame(Map(f(mylist), names(mylist[[1]]))) 

对于速度来说,使用unlist(lapply(...), use.names=FALSE)通常更快

 f = function(x) function(i) unlist(lapply(x, `[[`, i), use.names=FALSE) 

更通用的变体是

 f = function(X, FUN) function(...) sapply(X, FUN, ...) 

列表结构什么时候出现? 也许有一个更早的步骤迭代可以被更多vector化的东西所取代?

dplyr包的bind_rows是有效的。

 one <- mtcars[1:4, ] two <- mtcars[11:14, ] system.time(dplyr::bind_rows(one, two)) user system elapsed 0.001 0.000 0.001