将列表转换为数据框

641

我有一个嵌套的数据列表,长度为132,每个项目都是一个长度为20的列表。有没有一种快速的方法将这个结构转换成一个数据框,该数据框有132行和20列数据?

以下是一些示例数据可供使用:

l <- replicate(
  132,
  as.list(sample(letters, 20)),
  simplify = FALSE
)

2
所以你想要将每个列表元素作为数据框中的一行数据? - Joshua Ulrich
2
@RichieCotton 这个例子不正确。原文中说“每个项目都是长度为20的列表”,但你把每个项目都变成了一个元素为长度为20的向量的列表。 - Marek
1
虽然有点晚了,但我没有看到任何人提到这个链接,我认为它非常方便(适用于我想要做的事情)。 - mflo-ByeSE
1
请转到以下链接查看有关将列表转换为数据框的内容:https://www.r-bloggers.com/converting-a-list-to-a-data-frame/ - 千木郷
bind_rows(l)是“do.call(rbind, dfs)常见模式的高效实现”(最简单的答案,包装了Marek的答案) - Arthur Yip
26个回答

612

使用rbind

do.call(rbind.data.frame, your_list)

编辑:先前版本返回listdata.frame而不是向量(正如@IanSudbery在评论中指出的)。


7
为什么这个代码能够运行,但是 rbind(your_list) 却返回一个 1x32 的列表矩阵? - eykanal
35
do.callyour_list 中的元素作为参数传递给 rbind 函数。相当于执行 rbind(your_list[[1]], your_list[[2]], your_list[[3]], ....., your_list[[your_list长度]]) - Marek
2
该方法存在空值情况。 - Frank Wang
4
但这种方法并不适用于空情况。它要求your_list包含相同大小的向量。NULL长度为0,因此应该会失败。 - Marek
16
这种方法似乎会返回正确的对象,但是在检查该对象时,你会发现列是列表而不是向量,如果你没有预料到这一点,那么这可能会导致问题。 - Ian Sudbery
显示剩余7条评论

479

2020年7月更新:

stringsAsFactors参数的默认值现在为default.stringsAsFactors(),它的默认值是FALSE


假设您的列表名为 l:

df <- data.frame(matrix(unlist(l), nrow=length(l), byrow=TRUE))

上述代码将所有字符列转换为因子,为了避免这种情况,你可以在data.frame()调用中添加一个参数:

df <- data.frame(matrix(unlist(l), nrow=132, byrow=TRUE),stringsAsFactors=FALSE)

131
如果您的数据类型不完全相同,请注意这一点。通过矩阵时,所有数据将被强制转换为共同的类型。例如,如果您有一个包含字符数据和数字数据的列,则矩阵()将把数字数据强制转换为字符串,然后由data.frame()将两者都转换为因子。 - Ian Sudbery
2
@Dave:对我来说没问题...请看这里http://www.r-fiddle.org/#/fiddle?id=y8DW7lqL&version=3 - nico
4
如果您使用字符数据类型,请注意,data.frame会将其转换为因子。 - Alex Brown
4
有没有办法在数据框中将列表元素的名称保留为列名或行名? - N.Varela
4
如果你想保留列表元素的名称,可以在使用上述命令后尝试names(df) <- names(unlist(l[1])) - Chules
显示剩余9条评论

162
你可以使用 plyr 包。例如,一个形式为嵌套列表的数据:
l <- list(a = list(var.1 = 1, var.2 = 2, var.3 = 3)
      , b = list(var.1 = 4, var.2 = 5, var.3 = 6)
      , c = list(var.1 = 7, var.2 = 8, var.3 = 9)
      , d = list(var.1 = 10, var.2 = 11, var.3 = 12)
      )

现在l中每个列表都包含长度为3的另一个列表,因此l现在的长度为4。

现在你可以运行。

  library (plyr)
  df <- ldply (l, data.frame)

并且应该获得与@Marek和@nico答案中相同的结果。


8
好的回答。您可以稍微解释一下它是如何工作的吗?它只是为每个列表条目返回一个数据框架吗? - Michael Barton
13
我认为这是最佳答案。它返回一个诚实的数据框。所有数据类型(字符、数字等)都被正确转换了。如果列表包含不同的数据类型,它们将会采用“矩阵”方法全部转换为字符。 - Roah
1
以下是有关编程的内容翻译,仅返回翻译后的文本:这里提供的示例不是问题提供的示例。在原始数据集上执行此答案的结果是不正确的。 - MySchizoBuddy
对我来说非常有效!而且生成的数据框中的列名已经设置好了!Tx - bAN
plyr是多核的吗?还是有一个可与mclapply一起使用的lapply版本? - Garglesoap
4
为了支持dplyr,plyr已被弃用。 - csgillespie

135

修复示例数据,使其与原始描述“每个项目都是长度为20的列表”相匹配。

mylistlist <- replicate(
  132,
  as.list(sample(letters, 20)),
  simplify = FALSE
)

我们可以像这样将它转换为数据框:
data.frame(t(sapply(mylistlist,c)))

sapply将其转换为矩阵。 data.frame将矩阵转换为数据框。

结果如下:

enter image description here


22
到目前为止,这是最好的答案!其他解决方案均未正确列出类型/列名称。谢谢! - d_a_c321
2
你打算让 c 在这里扮演什么角色,是列表数据的一个实例吗?哦,等等,你是指连接函数中的 c 对吧?我对 @mnel 使用 c 的方式感到有些困惑。我也同意 @dchandler 的观点,在我的用例中,正确获取列名是非常重要的需求。这是一个绝妙的解决方案。 - jxramos
1
无法使用问题中提供的示例数据运行。 - MySchizoBuddy
有人(不是原作者)更改了问题。应该改回来。 - Alex Brown
4
这不会生成一个列表的数据框吗? - Carl
显示剩余4条评论

89
假设您的列表名称为L
data.frame(Reduce(rbind, L))

2
不错!与@Alex Brown的解决方案相比,您的方法有一个区别,按照您的方式会出现以下警告消息:`Warning message: In data.row.names(row.names, rowsi, i) : some row.names duplicated: 3,4 --> row.names NOT used' - jxramos
非常好!!在这里对我有用:http://stackoverflow.com/questions/32996321/rearranging-list-into-data-frame?noredirect=1#comment53817556_32996321 - Anastasia Pupynina
3
除非列表中只有一个元素,否则运行良好:data.frame(Reduce(rbind, list(c('col1','col2')))) 会产生一个2行1列的数据框(我预期是1行2列)。 - Nate Anderson
1
不必使用基本函数“Reduce”,您可以使用purr函数“reduce”,例如:reduce(L, rbind)。这将输出一个单独的数据框,并假定列表中的每个数据框(L)都以相同的方式组织(即按相同顺序包含相同数量的列)。 - ESELIA

70

data.table中有函数rbindlist,它是do.call(rbind, list(...))的超快实现。

它可以将listsdata.framesdata.tables的列表作为输入。

library(data.table)
ll <- list(a = list(var.1 = 1, var.2 = 2, var.3 = 3)
  , b = list(var.1 = 4, var.2 = 5, var.3 = 6)
  , c = list(var.1 = 7, var.2 = 8, var.3 = 9)
  , d = list(var.1 = 10, var.2 = 11, var.3 = 12)
  )

DT <- rbindlist(ll)

这返回一个 data.table 继承自 data.frame

如果你真的想转换回 data.frame,使用as.data.frame(DT)


1
关于最后一行,setDF现在允许通过引用返回到data.frame。 - Frank
1
对于我的包含30k个项目的列表,rbindlist比ldply快得多。 - tallharish
这确实是超级快的! - kf007
这正是我需要的 - 感谢你让我注意到它! - C. Murtaugh

55

tibble包有一个enframe()函数,它通过将嵌套的list对象强制转换为嵌套的tibble(“整洁”的数据框)对象来解决此问题。以下是来自R for Data Science的简要示例:

x <- list(
    a = 1:5,
    b = 3:4, 
    c = 5:6
) 

df <- enframe(x)
df
#> # A tibble: 3 × 2
#>    name     value
#>   <chr>    <list>
#>    1     a <int [5]>
#>    2     b <int [2]>
#>    3     c <int [2]>

由于您的列表中有几个嵌套,l,因此您可以使用unlist(recursive = FALSE)来删除不必要的嵌套,以获得单个分层列表,然后传递给enframe()。我使用tidyr::unnest()将输出展开为单级“整洁”数据框,其中包含您的两列(一列用于组name,另一列用于带有组value的观察值)。如果您想要宽列,可以使用add_column()添加一列,只需重复132次的值的顺序即可。然后只需使用spread()展开这些值。
library(tidyverse)

l <- replicate(
    132,
    list(sample(letters, 20)),
    simplify = FALSE
)

l_tib <- l %>% 
    unlist(recursive = FALSE) %>% 
    enframe() %>% 
    unnest()
l_tib
#> # A tibble: 2,640 x 2
#>     name value
#>    <int> <chr>
#> 1      1     d
#> 2      1     z
#> 3      1     l
#> 4      1     b
#> 5      1     i
#> 6      1     j
#> 7      1     g
#> 8      1     w
#> 9      1     r
#> 10     1     p
#> # ... with 2,630 more rows

l_tib_spread <- l_tib %>%
    add_column(index = rep(1:20, 132)) %>%
    spread(key = index, value = value)
l_tib_spread
#> # A tibble: 132 x 21
#>     name   `1`   `2`   `3`   `4`   `5`   `6`   `7`   `8`   `9`  `10`  `11`
#> *  <int> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr>
#> 1      1     d     z     l     b     i     j     g     w     r     p     y
#> 2      2     w     s     h     r     i     k     d     u     a     f     j
#> 3      3     r     v     q     s     m     u     j     p     f     a     i
#> 4      4     o     y     x     n     p     i     f     m     h     l     t
#> 5      5     p     w     v     d     k     a     l     r     j     q     n
#> 6      6     i     k     w     o     c     n     m     b     v     e     q
#> 7      7     c     d     m     i     u     o     e     z     v     g     p
#> 8      8     f     s     e     o     p     n     k     x     c     z     h
#> 9      9     d     g     o     h     x     i     c     y     t     f     j
#> 10    10     y     r     f     k     d     o     b     u     i     x     s
#> # ... with 122 more rows, and 9 more variables: `12` <chr>, `13` <chr>,
#> #   `14` <chr>, `15` <chr>, `16` <chr>, `17` <chr>, `18` <chr>,
#> #   `19` <chr>, `20` <chr>

引用原帖:“有没有快速的方法将此结构转换为具有132行和20列数据的数据框?”因此,您可能需要进行扩展步骤或其他操作。 - Frank
1
啊,是的,只需要一个可以展开的索引列。我会很快更新。 - Matt Dancho

36

根据您的列表结构,有一些tidyverse选项可以很好地处理长度不相等的列表:

l <- list(a = list(var.1 = 1, var.2 = 2, var.3 = 3)
        , b = list(var.1 = 4, var.2 = 5)
        , c = list(var.1 = 7, var.3 = 9)
        , d = list(var.1 = 10, var.2 = 11, var.3 = NA))

df <- dplyr::bind_rows(l)
df <- purrr::map_df(l, dplyr::bind_rows)
df <- purrr::map_df(l, ~.x)

# all create the same data frame:
# A tibble: 4 x 3
  var.1 var.2 var.3
  <dbl> <dbl> <dbl>
1     1     2     3
2     4     5    NA
3     7    NA     9
4    10    11    NA

您还可以混合使用向量和数据框:

library(dplyr)
bind_rows(
  list(a = 1, b = 2),
  data_frame(a = 3:4, b = 5:6),
  c(a = 7)
)

# A tibble: 4 x 2
      a     b
  <dbl> <dbl>
1     1     2
2     3     5
3     4     6
4     7    NA

这个dplyr :: bind_rows函数非常好用,即使是来自JSON的难以处理的列表也能很好地工作。从JSON到一个令人惊讶的干净数据框。不错。 - GGAnderson
@sbha 我尝试使用 df <- purrr::map_df(l, ~.x),但似乎它不起作用,我收到的错误信息是“错误:无法将列X2从整数转换为字符”。 - Jolin

30

这种方法使用tidyverse包(purrr)。

列表:

x <- as.list(mtcars)

将其转换为数据框(更具体地说,是 tibble):

library(purrr)
map_df(x, ~.x)

编辑:2021年5月30日

可以使用dplyr中的bind_rows()函数实现此目标。

x <- as.list(mtcars)
dplyr::bind_rows(x)

 A tibble: 32 x 11
     mpg   cyl  disp    hp  drat    wt  qsec    vs    am  gear  carb
   <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
 1  21       6  160    110  3.9   2.62  16.5     0     1     4     4
 2  21       6  160    110  3.9   2.88  17.0     0     1     4     4
 3  22.8     4  108     93  3.85  2.32  18.6     1     1     4     1
 4  21.4     6  258    110  3.08  3.22  19.4     1     0     3     1
 5  18.7     8  360    175  3.15  3.44  17.0     0     0     3     2
 6  18.1     6  225    105  2.76  3.46  20.2     1     0     3     1
 7  14.3     8  360    245  3.21  3.57  15.8     0     0     3     4
 8  24.4     4  147.    62  3.69  3.19  20       1     0     4     2
 9  22.8     4  141.    95  3.92  3.15  22.9     1     0     4     2
10  19.2     6  168.   123  3.92  3.44  18.3     1     0     4     4
# ... with 22 more rows

17

Reshape2的输出与上面的plyr示例相同:

library(reshape2)
l <- list(a = list(var.1 = 1, var.2 = 2, var.3 = 3)
          , b = list(var.1 = 4, var.2 = 5, var.3 = 6)
          , c = list(var.1 = 7, var.2 = 8, var.3 = 9)
          , d = list(var.1 = 10, var.2 = 11, var.3 = 12)
)
l <- melt(l)
dcast(l, L1 ~ L2)
产生:
  L1 var.1 var.2 var.3
1  a     1     2     3
2  b     4     5     6
3  c     7     8     9
4  d    10    11    12

如果你的像素快用完了,你可以使用recast()将所有内容都放在一行中。


我认为reshape2正在被dplyr、tidyr等替代。 - csgillespie

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接