【问题标题】:How to flatten R data frame that contains lists?如何展平包含列表的 R 数据框?
【发布时间】:2016-03-16 07:57:03
【问题描述】:

我想找到最好的“R 方式”来展平如下所示的数据框:

  CAT    COUNT     TREAT
   A     1,2,3     Treat-a, Treat-b
   B     4,5       Treat-c,Treat-d,Treat-e

所以它的结构是这样的:

   CAT   COUNT1  COUNT2 COUNT3  TREAT1   TREAT2   TREAT3
    A    1       2      3       Treat-a  Treat-b  NA 
    B    4       5      NA      Treat-c  Treat-d  Treat-e 

生成源数据框的示例代码:

df<-data.frame(CAT=c("A","B"))
df$COUNT <-list(1:3,4:5) 
df$TREAT <-list(paste("Treat-", letters[1:2],sep=""),paste("Treat-", letters[3:5],sep=""))

我相信我需要 rbind 和 unlist 的组合?任何帮助将不胜感激。 - 蒂姆

【问题讨论】:

  • 您的“真实”数据有多大(性能是个问题吗?)
  • cSplit() 来自 splitstackshape 包将是一个不错的选择。
  • 以你的例子df[2:3]&lt;-lapply(df[,2:3], function(x) do.call(rbind,lapply(x,"[",1:3))) 似乎是一个不错的开始。
  • 非常小的数据集,所以性能不是问题 - 谢谢!
  • @jazzurro,你看错函数了……

标签: r


【解决方案1】:

这是一个使用基数 R 的解决方案,接受列表中任意长度的向量,无需指定要折叠的数据框的哪些列。部分解决方案是使用this 答案生成的。

df2 <- do.call(cbind,lapply(df,function(x){
  #check if it is a list, otherwise just return as is
  if(is.list(x)){
    return(data.frame(t(sapply(x,'[',seq(max(sapply(x,length)))))))
  } else{
  return(x)
  }
}))

从 R 3.2 开始,lengths 也可以替换 sapply(x, length)

df3 <- do.call(cbind.data.frame, lapply(df, function(x) {
  # check if it is a list, otherwise just return as is
  if (is.list(x)) {
    data.frame(t(sapply(x,'[', seq(max(lengths(x))))))
  } else {
   x
 }
}))

使用的数据:

df <- structure(list(CAT = structure(1:2, .Label = c("A", "B"), class = "factor"), 
    COUNT = list(1:3, 4:5), TREAT = list(c("Treat-a", "Treat-b"
    ), c("Treat-c", "Treat-d", "Treat-e"))), .Names = c("CAT", 
"COUNT", "TREAT"), row.names = c(NA, -2L), class = "data.frame")

【讨论】:

    【解决方案2】:

    这是base r中的另一种方式

    df<-data.frame(CAT=c("A","B"))
    df$COUNT <-list(1:3,4:5)
    df$TREAT <-list(paste("Treat-", letters[1:2],sep=""),paste("Treat-", letters[3:5],sep=""))
    

    创建一个辅助函数来完成这项工作

    f <- function(l) {
      if (!is.list(l)) return(l)
      do.call('rbind', lapply(l, function(x) `length<-`(x, max(lengths(l)))))
    }
    

    始终测试您的代码

    f(df$TREAT)
    
    #           [,1]      [,2]      [,3]     
    # [1,] "Treat-a" "Treat-b" NA       
    # [2,] "Treat-c" "Treat-d" "Treat-e"
    

    应用它

    df[] <- lapply(df, f)
    df
    
    #     CAT COUNT.1 COUNT.2 COUNT.3 TREAT.1 TREAT.2 TREAT.3
    #   1   A       1       2       3 Treat-a Treat-b    <NA>
    #   2   B       4       5      NA Treat-c Treat-d Treat-e
    

    【讨论】:

    • 然后再添加一个do.call(data.frame, ...)。他们的list 现在已“扁平化”为matrix,但列数仍为 3。
    【解决方案3】:

    这里有一个已删除的答案,表明“splitstackshape”可以用于此。可以,但是删除的答案使用了错误的功能。相反,它应该使用listCol_w 函数。不幸的是,在目前的形式中,这个函数没有跨列向量化,因此您需要为需要展平的每一列嵌套对 listCol_w 的调用。

    方法如下:

    library(splitstackshape)
    listCol_w(listCol_w(df, "COUNT", fill = NA), "TREAT", fill = NA)
    ##    CAT COUNT_fl_1 COUNT_fl_2 COUNT_fl_3 TREAT_fl_1 TREAT_fl_2 TREAT_fl_3
    ## 1:   A          1          2          3    Treat-a    Treat-b         NA
    ## 2:   B          4          5         NA    Treat-c    Treat-d    Treat-e
    

    请注意,已指定 fill = NA,因为它默认为 fill = NA_character_,否则会将所有值强制转换为字符。


    另一种选择是使用“data.table”中的transpose。这是一个可能的实现(看起来很吓人,但使用该功能很容易)。好处是(1)您可以指定要展平的列,(2)您可以决定是否要删除原始列,以及(3)它很快。

    flatten <- function(indt, cols, drop = FALSE) {
      require(data.table)
      if (!is.data.table(indt)) indt <- as.data.table(indt)
      x <- unlist(indt[, lapply(.SD, function(x) max(lengths(x))), .SDcols = cols])
      nams <- paste(rep(cols, x), sequence(x), sep = "_")
      indt[, (nams) := unlist(lapply(.SD, transpose), recursive = FALSE), .SDcols = cols]
      if (isTRUE(drop)) {
        indt[, (nams) := unlist(lapply(.SD, transpose), recursive = FALSE), 
             .SDcols = cols][, (cols) := NULL]
      }
      indt[]
    }
    

    用法是……

    保留原始列:

    flatten(df, c("COUNT", "TREAT"))
    #    CAT COUNT                   TREAT COUNT_1 COUNT_2 COUNT_3 TREAT_1 TREAT_2 TREAT_3
    # 1:   A 1,2,3         Treat-a,Treat-b       1       2       3 Treat-a Treat-b      NA
    # 2:   B   4,5 Treat-c,Treat-d,Treat-e       4       5      NA Treat-c Treat-d Treat-e
    

    删除原始列:

    flatten(df, c("COUNT", "TREAT"), TRUE)
    #    CAT COUNT_1 COUNT_2 COUNT_3 TREAT_1 TREAT_2 TREAT_3
    # 1:   A       1       2       3 Treat-a Treat-b      NA
    # 2:   B       4       5      NA Treat-c Treat-d Treat-e
    

    请参阅this gist 以了解与建议的其他解决方案的比较。

    【讨论】:

      猜你喜欢
      • 2019-03-08
      • 2020-09-21
      • 1970-01-01
      • 2018-11-09
      • 2020-05-31
      • 1970-01-01
      • 2016-11-29
      • 2023-02-20
      • 1970-01-01
      相关资源
      最近更新 更多