简化没有for循环的嵌套列表。

问题描述 投票:0回答:1

我有一个复杂的嵌套结构的列表(从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"

请注意,属性位于不同的层次,一些元素(在列表中被命名为 "不相关")必须被丢弃,"attribute2 "在表2中缺失,"type "在表2的第1列中缺失。

# 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个回答
0
投票

下面是一个解决方案,使用 tidyverse:我为步骤添加了注释,使其更易读。

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.

© www.soinside.com 2019 - 2024. All rights reserved.