将具有不同名称的嵌套列表转换为 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_rowsdata.table::rbindlistfill = 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 这里只是一个简化的例子。实际上,我无法假定子列表元素的名称(示例中的 abz),也无法假定子列表的长度(示例中的 2、1、1、2 ).

以下是预期 data.frame 和输入 mylist 的假设:

  1. 预期的 data.frame 列数由子列表的最大长度决定,从 1 到几百不等。没有关于每个子列表长度的明确信息来源(哪些名称将出现或消失在哪些子列表中是未知的) max(sapply(mylist, length)) <= 1000 ## ==> TRUE
  2. 预期的data.frame 的行数由mylist的长度决定,可以从1到几千不等 dplyr::between(length(mylist), 0, 10000) ## ==> TRUE
  3. 没有关于子列表元素的名称及其顺序的明确信息,因此预期的 data.frame 的列名称和顺序只能从 mylist
  4. 内在地确定
  5. 每个子列表包含 numericcharacterlist 类型的元素。为了简化问题,只考虑numericcharacter.

您可以执行以下操作:

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()

{.}中是用来定义和计算由unlistnames形成的函数。否则 . %>% 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