【问题标题】:Simplifying a nested list without for loops简化没有 for 循环的嵌套列表
【发布时间】:2020-08-24 23:18:41
【问题描述】:

我有一个需要整理的具有复杂嵌套结构的列表(从 API JSON 导入)。我只设法用 for 循环来做到这一点,这似乎不是最优的:写起来很长,用许多元素计算很长,如果出现新的变化可能会出错。有没有办法使用 tidyverse 函数(例如 map()unnnest()flatten()squash() 或基本 R 中的等效函数)获得类似的结果?我在 StackOverflow 中没有找到任何具有类似问题组合的示例(在示例下方指出)。 这是可重现的示例:

metadata <- list(
  table1 = list(
    attribute1 = "tb1_att1",
    level_to_disard = list(
      attribute2 = "tb1_att2",
      columns = list(
        column1 = list(
          col_name = "tb1_col1_name",
          col_type = "tb1_col1_type"
        ),
        column2 = list(
          col_name = "tb1_col2_name",
          col_type = "tb1_col2_type"
        )
      ),
      tags = c("tag1", "tag2", "tag3"),
      irrelevant = list(irrelveant1 = "blabla", irrelevant2 = "blibli")
    )
  ),
  table2 = list(
    attribute1 = "tb2_att1",
    level_to_disard = list(
      columns = list(
        column1 = list(
          col_name = "tb2_col1_name",
          col_irrelevant = "bloblo"
        ),
        column2 = list(
          col_name = "tb2_col2_name",
          col_type = "tb2_col2_type"
        )
      ),
      tags = c("tag1", "tag3")
    )
  )
)

str(metadata)
# Output in console:
List of 2
 $ table1:List of 2
  ..$ attribute1     : chr "tb1_att1"
  ..$ level_to_disard:List of 4
  .. ..$ attribute2: chr "tb1_att2"
  .. ..$ columns   :List of 2
  .. .. ..$ column1:List of 2
  .. .. .. ..$ col_name: chr "tb1_col1_name"
  .. .. .. ..$ col_type: chr "tb1_col1_type"
  .. .. ..$ column2:List of 2
  .. .. .. ..$ col_name: chr "tb1_col2_name"
  .. .. .. ..$ col_type: chr "tb1_col1_type"
  .. ..$ tags      : chr [1:3] "tag1" "tag2" "tag3"
  .. ..$ irrelevant:List of 2
  .. .. ..$ irrelveant1: chr "blabla"
  .. .. ..$ irrelevant2: chr "blibli"
 $ table2:List of 2
  ..$ attribute1     : chr "tb2_att1"
  ..$ level_to_disard:List of 2
  .. ..$ columns:List of 2
  .. .. ..$ column1:List of 2
  .. .. .. ..$ col_name      : chr "tb2_col1_name"
  .. .. .. ..$ col_irrelevant: chr "bloblo"
  .. .. ..$ column2:List of 2
  .. .. .. ..$ col_name: chr "tb2_col2_name"
  .. .. .. ..$ col_type: chr "tb2_col1_type"
  .. ..$ tags   : chr [1:2] "tag1" "tag3"

请注意,属性位于不同的级别,必须丢弃某些元素(列表中名为“不相关”),表 2 中缺少“attribute2”,表 2 的第 1 列中缺少“类型”。 这是 for 循环的解决方案和预期的结果。

# Define a function to extract column information
extract_cols <- function(x){
  fields <- tibble()
  if (length(x) == 0) {
    return(fields)
  } else {
    for (i in 1:length(x)) {
      fields <- add_row(fields)
      # Extract name
      fields$name[i] = ""
      # Extract type if present of return empty string
      if (any(names(x[[i]]) == "type")) {
        fields$type[i] = x[[i]][["type"]]
      } else {
        fields$type[i] = ""
      }
      return(fields)
    }
  }
}

# Create an empty tibble for the tidy metadata. It could also be a list.
library(tibble)
meta <- tibble()

# for (i in 1:1) {
 for (i in 1:length(metadata)) {
  meta <- add_row(meta)
  meta$attribute1[[i]] <- metadata[[i]][["attribute1"]]
  meta$attribute2[[i]] <- ifelse(length(metadata[[i]][["level_to_disard"]][["attribute2"]]) > 0,
                          c(metadata[[i]][["level_to_disard"]][["attribute2"]]), "")
    metadata[[i]][["level_to_disard"]][["attribute2"]]
  meta$cols[[i]] <- extract_cols(metadata[[i]][["columns"]])
  meta$tags[[i]] <- metadata[[i]][["level_to_disard"]][["tags"]]

}
str(meta)
# Output in console:
tibble [2 × 4] (S3: tbl_df/tbl/data.frame)
 $ attribute1: chr [1:2] "tb1_att1" "tb2_att1"
 $ attribute2: chr [1:2] "tb1_att2" ""
 $ cols      :List of 2
  ..$ : tibble [0 × 0] (S3: tbl_df/tbl/data.frame)
 Named list()
  ..$ : tibble [0 × 0] (S3: tbl_df/tbl/data.frame)
 Named list()
 $ tags      :List of 2
  ..$ : chr [1:3] "tag1" "tag2" "tag3"
  ..$ : chr [1:2] "tag1" "tag3"

有没有更直接的方法来获得这个结果?输出可以是列表、tibble 或数据框,只要使用与上述 'meta'enter code here 类似的结构进行简化即可。

【问题讨论】:

    标签: r list


    【解决方案1】:

    这是使用tidyverse 的解决方案: 我添加了 cmets 以使其更具可读性。

    df<- unlist(metadata) %>% 
      data.frame() %>% # unlist and changing to data.frame
      rownames_to_column() %>% #adding row names to data
      magrittr::set_colnames(c("Var","value")) %>% #renaming columns
      tidyr::separate('Var', paste("Tag", 1:5, sep="_"), sep="[.]", extra="drop") %>% #spliting column to get all levels
      dplyr::mutate(Level1= ifelse(grepl("attribute",Tag_2)|grepl("attribute",Tag_3), "attribute",paste0(Tag_2,"_",Tag_3)),
                    Level1= gsub("\\d.*", "", Level1),
                    Level2=paste0(Level1,"_",Tag_5),
                    Level2= gsub("_NA*", "", Level2)
                    ) %>% #cleaning of data based on patterns to reach clean levels
      ungroup()%>%
      dplyr::group_by(Tag_1,Level2) %>% 
      dplyr::mutate(n= row_number()) %>%
      dplyr::ungroup() %>%
      dplyr::mutate(Level2=paste0(Level2,n),
                   Level3= ifelse(Level1=="attribute",Level1,Level2),
                   Level4= ifelse(is.na(Tag_5),Level1,Tag_5),
                   Level4= gsub("level_to_disard_","",Level4),
                   Level1= gsub("level_to_disard_","",Level1),
                   Level5= ifelse(Level1=="attribute",Level2,Level1)
                   ) %>% #cleaning again based on patterns to reach clean levels
      dplyr::select(Tag_1,Level1,Level2,Level3,Level4,Level5,value) %>%
      dplyr::group_by(Tag_1,Level5) %>% # at this step you can change Level number to get data at any other level
      dplyr::mutate(value_1=paste0(value,collapse = ",")) %>%
      dplyr::select(Tag_1,Level5,value_1) %>%
      dplyr::distinct() %>%
      tidyr::pivot_wider(names_from =c(Level5),values_from =  value_1) # changing to wide format
    
    

    output:

    df
    # A tibble: 2 x 6
    # Groups:   Tag_1 [2]
      Tag_1  attribute1 attribute2 columns                                                 tags           irrelevant   
      <chr>  <chr>      <chr>      <chr>                                                   <chr>          <chr>        
    1 table1 tb1_att1   tb1_att2   tb1_col1_name,tb1_col1_type,tb1_col2_name,tb1_col2_type tag1,tag2,tag3 blabla,blibli
    2 table2 tb2_att1   NA         tb2_col1_name,bloblo,tb2_col2_name,tb2_col2_type        tag1,tag3      NA 
    

    您可以使用select 删除不需要的列。

    【讨论】:

    • 哇!这种基于取消列出、获取和解析行名的方法与我想象的非常不同,而且效果很好。太棒了!剩下的一个问题是整理嵌套的列信息。但它基本上符合目标。非常感谢!
    • 很高兴为您提供帮助。如果我的建议适当地回答了您的问题,请单击勾选标记以接受它作为选择的答案并投票赞成。你能解释更多关于整洁的问题吗
    • 我立即接受并立即投了赞成票(我的赞成票只注册了,没有显示,因为我在 SO 上没有足够的代表)。关于“整洁问题”,我的意思是在 for 循环解决方案中,只检索特定的列属性(名称和类型),丢弃不相关的信息(“bloblo”)。但我发现您的解决方案也有效,如果不是在第 5 级设置 columns,而是将列的第 4 级属性(col_namecol_typecol_irrelevant)传递到第 5 级。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-26
    • 2012-06-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-01-14
    相关资源
    最近更新 更多