R语言 将命名不一致的列表转换为深度可变的数据框

wn9m85ua  于 2022-12-20  发布在  其他
关注(0)|答案(3)|浏览(121)

请考虑以下列表:

x <- list("a" = list("b", "c"),
          "d" = list("e", "f" = list("g", "h")),
          "i" = list("j", "k" = list("l" = list("m", "n" = list("o", "p")))))

值得注意的是:

  • 并非所有名称和元素都是一个字符
  • 有一个不确定的嵌套级别 * 先验 *。

给定x,我的目标是输出 Dataframe :

y <- data.frame(
  main_level = c(rep("a", 2), rep("d", 3), rep("i", 4)),
  level1 = c("b", "c", "e", rep("f", 2), "j", rep("k", 3)),
  level2 = c(NA, NA, NA, "g", "h", NA, "l", "l", "l"),
  level3 = c(NA, NA, NA,  NA,  NA, NA, "m", "n", "n"), 
  level4 = c(NA, NA, NA,  NA,  NA, NA, NA, "o", "p")
)
> y
  main_level level1 level2 level3 level4
1          a      b   <NA>   <NA>   <NA>
2          a      c   <NA>   <NA>   <NA>
3          d      e   <NA>   <NA>   <NA>
4          d      f      g   <NA>   <NA>
5          d      f      h   <NA>   <NA>
6          i      j   <NA>   <NA>   <NA>
7          i      k      l      m   <NA>
8          i      k      l      n      o
9          i      k      l      n      p

注意更正了上述y中的一个排印错误。

上面的内容暗示了列的数量也是可变的,这取决于嵌套的深度。
我在网上找到的解决方案,当涉及到嵌套列表时,假设列表命名结构或多或少是一致的,当然这里不是这样;例如,How to convert a nested lists to dataframe in R?Converting nested list to dataframe处的解决方案不适用,因为它们在命名上要一致得多。

3pmvbmvn

3pmvbmvn1#

这里有一个主要依靠rrapply的方法:

rrapply::rrapply(x, how = "melt") |>
  apply(1, function(row){
    newrow <- row[grep("[A-Za-z]", row)]
    length(newrow) <- purrr::vec_depth(x) - 1
    newrow
  }) |> 
  t() |> as.data.frame() |>
  `colnames<-`(c("main_level", paste0("level", 1:4)))

输出

main_level level1 level2 level3 level4
1          a      b   <NA>   <NA>   <NA>
2          a      c   <NA>   <NA>   <NA>
3          d      e   <NA>   <NA>   <NA>
4          d      f      g   <NA>   <NA>
5          d      f      h   <NA>   <NA>
6          i      j   <NA>   <NA>   <NA>
7          i      k      l      m   <NA>
8          i      k      l      n      o
9          i      k      l      n      p

注意到目前为止它还很粗糙,可能有更好的方法来改变rrapply的输出,例如,row[grep("[A-Za-z]", row)]可能不是每次都有效,我也没有测试length(newrow) <- purrr::vec_depth(x) - 1是否是猜测长度的好方法,但它在这里有效。

ma8fv8wu

ma8fv8wu2#

下面是一个递归函数,除了你描述的结构外,它没有其他假设:

list_to_df <- function(l) {
  
  leaves <- list()
  
  go_deeper <- function(l, index=1, path=NULL) {

    # we can still go deeper    
    if (is.list(l[[index]])) {
      
      path <- c(path, names(l)[index])
      l <- l[[index]]
      
      lapply(seq_along(l), function(i) go_deeper(l, i, path))

    # this is the final node (leaf)      
    } else {
      
      leaves <<- c(leaves, list(c(path, l[[index]])))
    }
  }
  
  # this saves the paths to each last node (leaf) in 'leaves' as a side effect
  go_deeper(list(l))
  
  # now just make a data frame from the 'leaves' list
  len.max <- max(lengths(leaves))
  leaves <- sapply(leaves, function(x) c(x, rep(NA, len.max-length(x))))
  leaves <- as.data.frame(t(leaves))
  names(leaves) <- c('main_level', paste0('level', seq_len(ncol(leaves)-1)))
  
  leaves 
}
list_to_df(x)
#   main_level level1 level2 level3 level4
# 1          a      b   <NA>   <NA>   <NA>
# 2          a      c   <NA>   <NA>   <NA>
# 3          d      e   <NA>   <NA>   <NA>
# 4          d      f      g   <NA>   <NA>
# 5          d      f      h   <NA>   <NA>
# 6          i      j   <NA>   <NA>   <NA>
# 7          i      k      l      m   <NA>
# 8          i      k      l      n      o
# 9          i      k      l      n      p
hiz5n14c

hiz5n14c3#

受Maël答案的启发,下面是一个使用rrapply()的稍微更健壮的方法:

  • 将嵌套列表中所有缺失/空的名称设置为NA
  • 将节点路径作为data.frame行,融化为一个长data.frame
  • 将每个节点路径上的第一个NA替换为其叶值(value列)
library(rrapply)

x1 <- rrapply(x, f = \(x, .xname) ifelse(grepl("^\\d*$", .xname), NA, .xname), how = "names")
x2 <- rrapply(x1, how = "melt")  
x3 <- apply(x2, 1, \(x){ x[is.na(x)][1] <- x[["value"]]; x })

as.data.frame(t(x3[-nrow(x3), ]))

#>   L1 L2   L3   L4   L5
#> 1  a  b <NA> <NA> <NA>
#> 2  a  c <NA> <NA> <NA>
#> 3  d  e <NA> <NA> <NA>
#> 4  d  f    g <NA> <NA>
#> 5  d  f    h <NA> <NA>
#> 6  i  j <NA> <NA> <NA>
#> 7  i  k    l    m <NA>
#> 8  i  k    l    n    o
#> 9  i  k    l    n    p

相关问题