将嵌套列表元素转换为数据框并将结果绑定到一个数据框中

时间:2017-04-24 14:38:33

标签: r

我有一个这样的嵌套列表:

x <- list(x = list(a = 1, 
                   b = 2), 
          y = list(a = 3, 
                   b = 4))

我想将嵌套列表转换为data.frames,然后将所有数据帧绑定为一个。

对于这种嵌套级别,我可以用这一行来完成:

do.call(rbind.data.frame, lapply(x, as.data.frame, stringsAsFactors = FALSE))

结果是:

  a b
x 1 2
y 3 4

我的问题是,无论嵌套程度如何,我都希望实现这一目标。这个列表的另一个例子是:

x <- list(X = list(x = list(a = 1, 
                       b = 2), 
              y = list(a = 3, 
                       b = 4)),
     Y = list(x = list(a = 1, 
                       b = 2), 
              y = list(a = 3, 
                       b = 4)))

do.call(rbind.data.frame, lapply(x, function(x) do.call(rbind.data.frame, lapply(x, as.data.frame, stringsAsFactors = FALSE))))

    a b
X.x 1 2
X.y 3 4
Y.x 1 2
Y.y 3 4

有没有人有想法将其归结为任何嵌套级别? 谢谢你的帮助

5 个答案:

答案 0 :(得分:8)

借用Spacedman和flodel here,我们可以定义以下一对递归函数:

library(tidyverse)  # I use dplyr and purrr here, plus tidyr further down below

depth <- function(this) ifelse(is.list(this), 1L + max(sapply(this, depth)), 0L)

bind_at_any_depth <- function(l) {
  if (depth(l) == 2) {
    return(bind_rows(l))
  } else {
    l <- at_depth(l, depth(l) - 2, bind_rows)
    bind_at_any_depth(l)
  }
}

我们现在可以将任意深度列表绑定到单个data.frame:

bind_at_any_depth(x)
# A tibble: 2 × 2
      a     b
  <dbl> <dbl>
1     1     2
2     3     4
bind_at_any_depth(x_ext) # From P Lapointe
# A tibble: 5 × 2
      a     b
  <dbl> <dbl>
1     1     2
2     5     6
3     7     8
4     1     2
5     3     4

如果要跟踪每行的来源,可以使用此版本:

bind_at_any_depth2 <- function(l) {
  if (depth(l) == 2) {
    l <- bind_rows(l, .id = 'source')
    l <- unite(l, 'source', contains('source'))
    return(l)
  } else {
    l <- at_depth(l, depth(l) - 2, bind_rows, .id = paste0('source', depth(l)))
    bind_at_any_depth(l)
  }
}

这将添加source列:

bind_at_any_depth2(x_ext)
# A tibble: 5 × 3
  source     a     b
*  <chr> <dbl> <dbl>
1  X_x_1     1     2
2  X_y_z     5     6
3 X_y_zz     7     8
4  Y_x_1     1     2
5  Y_y_1     3     4

注意:在某些时候,您可以使用purrr::depth,当新版本推出CRAN时,需要将at_depth更改为modify_depth(谢谢@ManuelS)。

答案 1 :(得分:2)

<强>更新

这是一种使用unlist简化更深层次嵌套列表的方法。由于结构现在不均匀,因此结果不会是data.frame

x_ext <- list(X = list(x = list(a = 1,
                       b = 2),
              y = list(z=list(a = 5,
                       b = 6),
                       zz=list(a = 7,
                       b = 8))),
     Y = list(x = list(a = 1,
                       b = 2),
              y = list(a = 3,
                       b = 4)))

unlist(x_ext)

   X.x.a    X.x.b  X.y.z.a  X.y.z.b X.y.zz.a X.y.zz.b    Y.x.a    Y.x.b    Y.y.a    Y.y.b 
       1        2        5        6        7        8        1        2        3        4 

我的初步答案是先unlist,然后rbind。但是,它仅适用于问题中的示例。

x_unlist <- unlist(x, recursive = FALSE)
do.call("rbind", x_unlist)
    a b
X.x 1 2
X.y 3 4
Y.x 1 2
Y.y 3 4

答案 2 :(得分:2)

You can flatten and coerce to a data.frame while collecting names with purrr::flatten_df from the development version:

library(purrr)    # or library(tidyverse)

x <- list(X = list(x = list(a = 1, 
                       b = 2), 
              y = list(a = 3, 
                       b = 4)),
     Y = list(x = list(a = 1, 
                       b = 2), 
              y = list(a = 3, 
                       b = 4)))

x %>% flatten_df(.id = 'var')
#> # A tibble: 4 × 3
#>     var     a     b
#>   <chr> <dbl> <dbl>
#> 1     x     1     2
#> 2     y     3     4
#> 3     x     1     2
#> 4     y     3     4

or if you want to save both sets of names, map_df:

library(tidyverse)

x %>% map_df(~bind_rows(.x, .id = 'var2'), .id = 'var1')
#> # A tibble: 4 × 4
#>    var1  var2     a     b
#>   <chr> <chr> <dbl> <dbl>
#> 1     X     x     1     2
#> 2     X     y     3     4
#> 3     Y     x     1     2
#> 4     Y     y     3     4

答案 3 :(得分:0)

我们可以使用tidyverse

执行此操作
library(tidyverse)
x %>% 
   map(bind_rows) %>%
   bind_rows(.id = 'grp')
# A tibble: 4 × 3
#     grp     a     b    
#   <chr> <dbl> <dbl>
#1     X     1     2
#2     X     3     4
#3     Y     1     2
#4     Y     3     4

或使用base R

do.call(rbind, do.call(c, x))
#    a b
#X.x 1 2
#X.y 3 4
#Y.x 1 2
#Y.y 3 4

答案 4 :(得分:0)

这建立在P.Lapointe的答案之上,并使用来自herehere的想法来提取列表中的最终名称。

 bind <- function(x) {
     s = stack(unlist(x))
     s$major = tools::file_path_sans_ext(s$ind)
     s$minor = tools::file_ext(s$ind)
     as.data.frame.matrix(xtabs(data=s, values ~  major + minor))
 }

 bind(x)
    a b
X.x 1 2
X.y 3 4
Y.x 1 2
Y.y 3 4

 bind(x_ext)
       a b
X.x    1 2
X.y.z  5 6
X.y.zz 7 8
Y.x    1 2
Y.y    3 4