将具有不同名称的嵌套列表转换为 data.frame 填充 NA 并添加列
Convert nested list with different names to data.frame filling NA and adding column
我需要一个基本的 R 解决方案来将具有不同名称的嵌套列表转换为 data.frame
mylist <- list(list(a=1,b=2), list(a=3), list(b=5), list(a=9, z=list('k'))
convert(mylist)
## returns a data.frame:
##
## a b z
## 1 2 <NULL>
## 3 NA <NULL>
## NA 5 <NULL>
## 9 NA <chr [1]>
我知道这可以用 dplyr::bind_rows
或 data.table::rbindlist
和 fill = TRUE
轻松完成(虽然不理想,因为它用 NULL
填充字符列,而不是 NA
),但我确实需要一个基于 R 的解决方案。为了简化问题,使用没有第 3 级列表的 2 级嵌套列表也可以,例如
mylist <- list(list(a=1,b=2), list(a=3), list(b=5), list(a=9, z='k'))
convert(mylist)
## returns a data.frame:
##
## a b z
## 1 2 NA
## 3 NA NA
## NA 5 NA
## 9 NA k
我试过
convert <- function(L) as.data.frame(do.call(rbind, L))
这不填写 NA
并添加额外的列 z
更新
mylist
这里只是一个简化的例子。实际上,我无法假定子列表元素的名称(示例中的 a
、b
和 z
),也无法假定子列表的长度(示例中的 2、1、1、2 ).
以下是预期 data.frame
和输入 mylist
的假设:
- 预期的
data.frame
列数由子列表的最大长度决定,从 1 到几百不等。没有关于每个子列表长度的明确信息来源(哪些名称将出现或消失在哪些子列表中是未知的)
max(sapply(mylist, length)) <= 1000 ## ==> TRUE
- 预期的
data.frame
的行数由mylist
的长度决定,可以从1到几千不等
dplyr::between(length(mylist), 0, 10000) ## ==> TRUE
- 没有关于子列表元素的名称及其顺序的明确信息,因此预期的
data.frame
的列名称和顺序只能从 mylist
内在地确定
- 每个子列表包含
numeric
、character
或 list
类型的元素。为了简化问题,只考虑numeric
和character
.
您可以执行以下操作:
mylist <- list(list(a=1,b=2), list(a=3), list(b=5), list(a=9, z='k'))
convert <- function(mylist){
col_names <- NULL
# get all the unique names and create the df
for(i in 1:length(mylist)){
col_names <- c(col_names, names(mylist[[i]]))
}
col_names <- unique(col_names)
df <- data.frame(matrix(ncol=length(col_names),
nrow=length(mylist)))
colnames(df) <- col_names
# join data to row in df
for(i in 1:length(mylist)){
for(j in 1:length(mylist[[i]])){
df[i, names(mylist[[i]])[j]] <- mylist[[i]][names(mylist[[i]])[j]]
}
}
return(df)
}
df <- convert(mylist)
> df
a b z
1 1 2 <NA>
2 3 NA <NA>
3 NA 5 <NA>
4 9 NA k
我有办法了。注意这只使用管道,可以换成本地管道等
mylist %>%
#' first, ensure that the 2nd level is flat,
lapply(. %>% lapply(FUN = unlist, recursive = FALSE)) %>%
#' replace missing vars with `NA`
lapply(function(x, vars) {
x[vars[!vars %in% names(x)]]<-NA
x
}, vars = {.} %>% unlist() %>% names() %>% unique()) %>%
do.call(what = rbind) %>%
#' do nothing
identity()
在{.}
中是用来定义和计算由unlist
和names
形成的函数。否则 . %>% unlist() %>% names()
只是定义函数,而不是对输入 .
.
求值
基于 R 的较短解决方案是
make_df <- function(a = NA, b = NA, z = NA) {
data.frame(a = unlist(a), b = unlist(b), z = unlist(z))
}
do.call(rbind, lapply(mylist, function(x) do.call(make_df, x)))
#> a b z
#> 1 1 2 <NA>
#> 2 3 NA <NA>
#> 3 NA 5 <NA>
#> 4 9 NA k
更新
使用相同方法但不需要特定名称的更通用的解决方案是:
build_data_frame <- function(obj) {
nms <- unique(unlist(lapply(obj, names)))
frmls <- as.list(setNames(rep(NA, length(nms)), nms))
dflst <- setNames(lapply(nms, function(x) call("unlist", as.symbol(x))), nms)
make_df <- as.function(c(frmls, call("do.call", "data.frame", dflst)))
do.call(rbind, lapply(mylist, function(x) do.call(make_df, x)))
}
这允许
build_data_frame(mylist)
#> a b z
#> 1 1 2 <NA>
#> 2 3 NA <NA>
#> 3 NA 5 <NA>
#> 4 9 NA k
我们可以尝试下面的基本 R 代码
subset(
Reduce(
function(...) {
merge(..., all = TRUE)
},
Map(
function(k, x) cbind(id = k, list2DF(x)),
seq_along(mylist), mylist
)
),
select = -id
)
这给出了
a b z
1 1 2 NA
2 3 NA NA
3 NA 5 NA
4 9 NA k
我需要一个基本的 R 解决方案来将具有不同名称的嵌套列表转换为 data.frame
mylist <- list(list(a=1,b=2), list(a=3), list(b=5), list(a=9, z=list('k'))
convert(mylist)
## returns a data.frame:
##
## a b z
## 1 2 <NULL>
## 3 NA <NULL>
## NA 5 <NULL>
## 9 NA <chr [1]>
我知道这可以用 dplyr::bind_rows
或 data.table::rbindlist
和 fill = TRUE
轻松完成(虽然不理想,因为它用 NULL
填充字符列,而不是 NA
),但我确实需要一个基于 R 的解决方案。为了简化问题,使用没有第 3 级列表的 2 级嵌套列表也可以,例如
mylist <- list(list(a=1,b=2), list(a=3), list(b=5), list(a=9, z='k'))
convert(mylist)
## returns a data.frame:
##
## a b z
## 1 2 NA
## 3 NA NA
## NA 5 NA
## 9 NA k
我试过
convert <- function(L) as.data.frame(do.call(rbind, L))
这不填写 NA
并添加额外的列 z
更新
mylist
这里只是一个简化的例子。实际上,我无法假定子列表元素的名称(示例中的 a
、b
和 z
),也无法假定子列表的长度(示例中的 2、1、1、2 ).
以下是预期 data.frame
和输入 mylist
的假设:
- 预期的
data.frame
列数由子列表的最大长度决定,从 1 到几百不等。没有关于每个子列表长度的明确信息来源(哪些名称将出现或消失在哪些子列表中是未知的)max(sapply(mylist, length)) <= 1000 ## ==> TRUE
- 预期的
data.frame
的行数由mylist
的长度决定,可以从1到几千不等dplyr::between(length(mylist), 0, 10000) ## ==> TRUE
- 没有关于子列表元素的名称及其顺序的明确信息,因此预期的
data.frame
的列名称和顺序只能从mylist
内在地确定
- 每个子列表包含
numeric
、character
或list
类型的元素。为了简化问题,只考虑numeric
和character
.
您可以执行以下操作:
mylist <- list(list(a=1,b=2), list(a=3), list(b=5), list(a=9, z='k'))
convert <- function(mylist){
col_names <- NULL
# get all the unique names and create the df
for(i in 1:length(mylist)){
col_names <- c(col_names, names(mylist[[i]]))
}
col_names <- unique(col_names)
df <- data.frame(matrix(ncol=length(col_names),
nrow=length(mylist)))
colnames(df) <- col_names
# join data to row in df
for(i in 1:length(mylist)){
for(j in 1:length(mylist[[i]])){
df[i, names(mylist[[i]])[j]] <- mylist[[i]][names(mylist[[i]])[j]]
}
}
return(df)
}
df <- convert(mylist)
> df
a b z
1 1 2 <NA>
2 3 NA <NA>
3 NA 5 <NA>
4 9 NA k
我有办法了。注意这只使用管道,可以换成本地管道等
mylist %>%
#' first, ensure that the 2nd level is flat,
lapply(. %>% lapply(FUN = unlist, recursive = FALSE)) %>%
#' replace missing vars with `NA`
lapply(function(x, vars) {
x[vars[!vars %in% names(x)]]<-NA
x
}, vars = {.} %>% unlist() %>% names() %>% unique()) %>%
do.call(what = rbind) %>%
#' do nothing
identity()
在{.}
中是用来定义和计算由unlist
和names
形成的函数。否则 . %>% unlist() %>% names()
只是定义函数,而不是对输入 .
.
基于 R 的较短解决方案是
make_df <- function(a = NA, b = NA, z = NA) {
data.frame(a = unlist(a), b = unlist(b), z = unlist(z))
}
do.call(rbind, lapply(mylist, function(x) do.call(make_df, x)))
#> a b z
#> 1 1 2 <NA>
#> 2 3 NA <NA>
#> 3 NA 5 <NA>
#> 4 9 NA k
更新
使用相同方法但不需要特定名称的更通用的解决方案是:
build_data_frame <- function(obj) {
nms <- unique(unlist(lapply(obj, names)))
frmls <- as.list(setNames(rep(NA, length(nms)), nms))
dflst <- setNames(lapply(nms, function(x) call("unlist", as.symbol(x))), nms)
make_df <- as.function(c(frmls, call("do.call", "data.frame", dflst)))
do.call(rbind, lapply(mylist, function(x) do.call(make_df, x)))
}
这允许
build_data_frame(mylist)
#> a b z
#> 1 1 2 <NA>
#> 2 3 NA <NA>
#> 3 NA 5 <NA>
#> 4 9 NA k
我们可以尝试下面的基本 R 代码
subset(
Reduce(
function(...) {
merge(..., all = TRUE)
},
Map(
function(k, x) cbind(id = k, list2DF(x)),
seq_along(mylist), mylist
)
),
select = -id
)
这给出了
a b z
1 1 2 NA
2 3 NA NA
3 NA 5 NA
4 9 NA k