2017-04-24 58 views
3

我有一个嵌套列表如下:转换嵌套列表元素融入到数据帧,并绑定结果存入一个数据帧

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 

有谁有一个想法,generelized这对任何级别的嵌套? 感谢您的帮助

+2

对输入列表的结构是否有任何保证?它会一直有叶子包含相同数量的元素?我们可以指望它始终在输出中是2列吗?或者这可能会有所不同? – Dason

回答

6

借款和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) 
    } 
} 

我们可以任意深度列表现在绑定到一个单一的数据。框架:

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,并需要改变at_depthmodify_depth当他们的新版本推出来CRAN(谢谢@ManuelS)。

+1

你应该提到'purrr :: depth()'是'purrr'开发版本的一部分,并且'modify_depth()'最终将取代'at_depth()'。否则:很好的答案 –

+0

@ManuelS,实际上,我并不知道这一点。谢谢!我正在使用我的代码开始处定义的'depth'函数(从链接的QA中借用)。 – Axeman

+0

哦,我明白了。我忽略了这部分,并假设你正在使用'purrr :: depth()'函数。使用该版本可能有意义,尽管您的功能可能也是如此 –

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 
+0

您的基本答案只适用于第二个例子。 – Axeman

2

UPDATE

这里做到这一点的扁平化更加的方式与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 aftrerwards。但是,它仅适用于问题中的示例。从Spacedman

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 
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 
2

您可以拼合和强迫到data.frame同时用purrr::flatten_dfthe 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 

,或者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