我有一个这样的嵌套列表:
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
有没有人有想法将其归结为任何嵌套级别? 谢谢你的帮助
答案 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的答案之上,并使用来自here和here的想法来提取列表中的最终名称。
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