【问题标题】:Flatten a list with complex nested structure展平具有复杂嵌套结构的列表
【发布时间】:2015-03-25 08:15:20
【问题描述】:

我有一个具有以下示例结构的列表:

> dput(test)
structure(list(id = 1, var1 = 2, var3 = 4, section1 = structure(list(
    var1 = 1, var2 = 2, var3 = 3), .Names = c("var1", "var2", 
"var3")), section2 = structure(list(row = structure(list(var1 = 1, 
    var2 = 2, var3 = 3), .Names = c("var1", "var2", "var3")), 
    row = structure(list(var1 = 4, var2 = 5, var3 = 6), .Names = c("var1", 
    "var2", "var3")), row = structure(list(var1 = 7, var2 = 8, 
        var3 = 9), .Names = c("var1", "var2", "var3"))), .Names = c("row", 
"row", "row"))), .Names = c("id", "var1", "var3", "section1", 
"section2"))


> str(test)
List of 5
 $ id      : num 1
 $ var1    : num 2
 $ var3    : num 4
 $ section1:List of 3
  ..$ var1: num 1
  ..$ var2: num 2
  ..$ var3: num 3
 $ section2:List of 3
  ..$ row:List of 3
  .. ..$ var1: num 1
  .. ..$ var2: num 2
  .. ..$ var3: num 3
  ..$ row:List of 3
  .. ..$ var1: num 4
  .. ..$ var2: num 5
  .. ..$ var3: num 6
  ..$ row:List of 3
  .. ..$ var1: num 7
  .. ..$ var2: num 8
  .. ..$ var3: num 9

注意section2 列表包含名为rows 的元素。这些代表多个记录。我所拥有的是一个嵌套列表,其中一些元素位于根级别,而其他元素是同一观察的多个嵌套记录。我想要data.frame 格式的以下输出:

> desired
  id var1 var3 section1.var1 section1.var2 section1.var3 section2.var1 section2.var2 section2.var3
1  1    2    4             1             2               3             1             4             7
2 NA   NA   NA            NA            NA              NA             2             5             8
3 NA   NA   NA            NA            NA              NA             3             6             9

根级元素应该填充第一行,而row 元素应该有自己的行。作为一个额外的复杂因素,row 条目中的变量数量可能会有所不同。

【问题讨论】:

  • 你为什么想要这个想要的输出?这似乎是一种不方便使用的数据格式。
  • 我正在执行一个soap请求,它返回一个在嵌套列表中具有非常嵌套结构的html表。我不确定您为什么认为所需的输出不方便。它以 data.frame 格式重新创建 html 表,并在条目跨越多行时填充 NA 值。
  • 您是否可以再提供一两个测试用例,因为您已经为此添加了赏金。您提到您正在寻找“通用”解决方案,因此最好了解应该考虑哪些其他场景。

标签: r list


【解决方案1】:

此解决方案的中心思想是展平除名为“行”的子列表之外的所有子列表。这可以通过为每个列表元素创建一个唯一的 ID(存储在 z)然后请求单个“行”中的所有元素应该具有相同的 ID(存储在 z2;必须编写递归函数)来完成遍历嵌套列表)。然后,z2 可用于对属于同一行的元素进行分组。可以使用stringi 包中的stri_list2matrix 将结果列表转换为矩阵形式,然后转换为数据框。

utest <- unlist(test)
z <- relist(seq_along(utest),test)

recurse <- function(L) {
    if (class(L)!='list') return(L)
    b <- names(L)=='row'
    L.b <- lapply(L[b],function(k) relist(rep(k[[1]],length(k)),k))
    L.nb <- lapply(L[!b],recurse)
    c(L.b,L.nb)
}

z2 <- unlist(recurse(z))

library(stringi)
desired <- as.data.frame(stri_list2matrix(split(utest,z2)))
names(desired) <- names(z2)[unique(z2)]

desired
#     id var1 var3 section1.var1 section1.var2 section1.var3 section2.row.var1
# 1    1    2    4             1             2             3                 1
# 2 <NA> <NA> <NA>          <NA>          <NA>          <NA>                 2
# 3 <NA> <NA> <NA>          <NA>          <NA>          <NA>                 3
#   section2.row.var1 section2.row.var1
# 1                 4                 7
# 2                 5                 8
# 3                 6                 9

【讨论】:

    【解决方案2】:

    因为当行很复杂时,您的问题没有得到很好的定义 结构(即如果test 中的每一行都包含列表test`,行应该如何绑定在一起。如果同一个表中的行有不同的结构怎么办?),以下解决方案取决于行是一个值列表。

    也就是说,我猜在一般情况下,您的列表 test 将 包含值、值列表或行列表(其中行是 值列表)。此外,如果行并不总是称为“行”,则此解决方案仍然有效。

    temp <- lapply(test,
                    function(x){
                        if(!is.list(x))
                            # x is a value
                            return(x)
                        # x is a lis of rows or values
                        out <- do.call(cbind,x)
                        if(nrow(out)>1){
                            # x is a list of rows 
                            colnames(out)<-paste0(colnames(out),'.',rownames(out))
                            rownames(out)<-rep_len(NA,nrow(out))
                        }
                        return(out)
                    })
    
    # a function that extends a matrix to a fixt number of rows (n)
    # by appending rows of NA's 
    rowExtend  <-  function(x,N){
                     if((!is.matrix(x)) ){
                         out<-do.call(rbind,c(list(x),as.list(rep_len(NA,N - 1))))
                         colnames(out) <- ""
                         out
                     }else if(nrow(x) < N)
                         do.call(rbind,c(list(x),as.list(rep_len(NA,N - nrow(x)))))
                     else
                         x
                 }
    
    # calculate the maximum number of rows
    .nrows <- sapply(temp,nrow)
    .nrows <- max(unlist(.nrows[!sapply(.nrows,is.null)]))
    
    # extend the shorter rows
    (temp2<-lapply(temp, rowExtend,.nrows))
    
    # calculate new column namames
    newColNames <- mapply(function(x,y) {
                           if(nzchar(y)[1L])
                               paste0(x,'.',y)
                           else x
                            },
                           names(temp2),
                           lapply(temp2,colnames))
    
    
    do.call(cbind,mapply(`colnames<-`,temp2,newColNames))
    
    #> id var1 var3 section1.var1 section1.var2 section1.var3 section2.row.var1 section2.row.var2 section2.row.var3
    #> 1  2    4    1             2             3             1                 4                 7                
    #> NA NA   NA   NA            NA            NA            2                 5                 8                
    #> NA NA   NA   NA            NA            NA            3                 6                 9                
    

    【讨论】:

      【解决方案3】:

      这是一种通用方法。它不假设您只有三行;无论您拥有多少行,它都可以使用。如果嵌套结构中缺少某个值(例如,对于 section2 中的某些子列表不存在 var1),则代码会正确返回该单元格的 NA。

      例如如果我们使用以下数据:

      test <- structure(list(id = 1, var1 = 2, var3 = 4, section1 = structure(list(var1 = 1, var2 = 2, var3 = 3), .Names = c("var1", "var2", "var3")), section2 = structure(list(row = structure(list(var1 = 1, var2 = 2), .Names = c("var1", "var2")), row = structure(list(var1 = 4, var2 = 5), .Names = c("var1", "var2")), row = structure(list( var2 = 8, var3 = 9), .Names = c("var2", "var3"))), .Names = c("row", "row", "row"))), .Names = c("id", "var1", "var3", "section1", "section2"))
      

      一般的方法是使用 melt 创建一个包含嵌套结构信息的数据框,然后 dcast 将其塑造成您想要的格式。

      library("reshape2")
      
      flat <- unlist(test, recursive=FALSE)
      names(flat)[grep("row", names(flat))] <- gsub("row", "var", paste0(names(flat)[grep("row", names(flat))], seq_len(length(names(flat)[grep("row", names(flat))]))))  ## keeps track of rows by adding an ID
      ul <- melt(unlist(flat))
      split <- strsplit(rownames(ul), split=".", fixed=TRUE) ## splits the names into component parts
      max <- max(unlist(lapply(split, FUN=length)))
      pad <- function(a) {
        c(a, rep(NA, max-length(a)))
      }
      levels <- matrix(unlist(lapply(split, FUN=pad)), ncol=max, byrow=TRUE)
      
      ## Get the nesting structure
      nested <- data.frame(levels, ul)
      nested$X3[is.na(nested$X3)] <- levels(as.factor(nested$X3))[[1]]
      desired <- dcast(nested, X3~X1 + X2)
      names(desired) <- gsub("_", "\\.", gsub("_NA", "", names(desired)))
      desired <- desired[,names(flat)]
      
      > desired
        ## id var1 var3 section1.var1 section1.var2 section1.var3 section2.var1 section2.var2 section2.var3
      ## 1  1    2    4             1             2             3             1             4             7
      ## 2 NA   NA   NA            NA            NA            NA             2             5             8
      ## 3 NA   NA   NA            NA            NA            NA             3             6             9
      

      【讨论】:

        【解决方案4】:

        这开始与蒂芙尼的回答类似,但之后有所不同。

        library(data.table)
        
        # flatten the first level
        flat = unlist(test, recursive = FALSE)
        
        # compute max length
        N = max(sapply(flat, length))
        
        # pad NA's and convert to data.table (at this point it will *look* like the right answer)
        dt = as.data.table(lapply(flat, function(l) c(l, rep(NA, N - length(l)))))
        
        # but in reality some of the columns are lists - check by running sapply(dt, class)
        # so unlist them
        dt = dt[, lapply(.SD, unlist)]
        #   id var1 var3 section1.var1 section1.var2 section1.var3 section2.row section2.row section2.row
        #1:  1    2    4             1             2             3            1            4            7
        #2: NA   NA   NA            NA            NA            NA            2            5            8
        #3: NA   NA   NA            NA            NA            NA            3            6            9
        

        【讨论】:

          猜你喜欢
          • 2013-11-17
          • 2021-07-14
          • 2014-05-24
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多