【问题标题】:How to parse a data.frame into a tree?如何将 data.frame 解析成树?
【发布时间】:2015-11-30 22:55:58
【问题描述】:

这是一个简单的分类法(标签和 ID):

test_data <- data.frame(
  cat_id = c(661, 197, 228, 650, 126, 912, 949, 428),
  cat_h1 = c(rep("Animals", 5), rep("Plants", 3)),
  cat_h2 = c(rep("Mammals", 3), rep("Birds", 2), c("Wheat", "Grass", "Other")),
  cat_h3 = c("Dogs", "Dogs", "Other", "Hawks", "Other", rep(NA, 3)),
  cat_h4 = c("Big", "Little", rep(NA, 6)))

解析后的结构应符合以下内容:

list(
  Animals = list(Mammals = list(Dogs  = list(Big = 661, Little = 197), Other = 228),
                 Birds   = list(Hawks = 650, Other = 126)),
  Plants  = list(Wheat = 912, Grass = 949, Other = 428))

【问题讨论】:

    标签: r parsing tree


    【解决方案1】:

    如果您对订单的轻微变化感到满意,这是一个按列处理的递归解决方案:

    f <- function(x, d=cbind(x,NA)) {
        c( 
           # call f by branch
           if(ncol(d) > 3) local({
             x <- d[!is.na(d[[3]]),] 
             by( x[-2], droplevels(x[2]), f, x=NA, simplify=FALSE) 
           }), 
           # leaf nodes
           setNames(as.list(d[[1]]), d[[2]])[is.na(d[[3]])] 
        )
    }
    

    这将给出:

    > str(f(test_data))
    List of 2
     $ Animals:List of 2
      ..$ Birds  :List of 2
      .. ..$ Hawks: num 650
      .. ..$ Other: num 126
      ..$ Mammals:List of 2
      .. ..$ Dogs :List of 2
      .. .. ..$ Big   : num 661
      .. .. ..$ Little: num 197
      .. ..$ Other: num 228
     $ Plants :List of 3
      ..$ Wheat: num 912
      ..$ Grass: num 949
      ..$ Other: num 428
    

    【讨论】:

    • 不错!在一切崩溃之前,我使用类似的by/split 逻辑最接近的是with(test_data, Map(split, split(cat_id, cat_h1), split(cat_h2, cat_h1)))
    • 顺序不重要!并且递归是可以的。非常感谢!
    【解决方案2】:

    也许不是最有效的,但也不是太难:

    创建数据:

    test_data <- data.frame(
      cat_id = c(661, 197, 228, 650, 126, 912, 949, 428),
      cat_h1 = c(rep("Animals", 5), rep("Plants", 3)),
      cat_h2 = c(rep("Mammals", 3), rep("Birds", 2), c("Wheat", "Grass", "Other")),
      cat_h3 = c("Dogs", "Dogs", "Other", "Hawks", "Other", rep(NA, 3)),
      cat_h4 = c("Big", "Little", rep(NA, 6)))
    

    遍历数据框并构建列表/树:

    tax <- list()  ## initialize
    for (i in 1:nrow(test_data)) {
        ## convert data.frame row to character vector
        taxdat <- sapply(test_data[i,-1],as.character)
        taxstr <- character(0)  ## initialize taxon string
        ntax <- length(na.omit(taxdat))
        for (j in 1:ntax) {
            taxstr <- c(taxstr,taxdat[j])  ## build string
            if (is.null(tax[[taxstr]])) {
                tax[[taxstr]] <- list()  ## initialize if necessary
            }
        }
        tax[[taxstr]] <- test_data$cat_id[i]  ## assign value to tip
    }
    

    将结果与期望进行比较:

    res <- list(
      Animals = list(Mammals = list(Dogs  = list(Big = 661, Little = 197),
                     Other = 228),
                     Birds   = list(Hawks = 650, Other = 126)),
      Plants  = list(Wheat = 912, Grass = 949, Other = 428))
    
    all.equal(res,tax)  ## TRUE
    

    【讨论】:

    • 我觉得 Reduce()split() 必须有一个解决方案,但它就是不来找我。
    • @TheTime +1 指向“data.tree”包的指针。谢谢!
    【解决方案3】:

    我会避免使用列表结构而不是整洁的数据。这是一种减少数据冗余的方法。

    library(dplyr)
    
    h1_h2 = 
      test_data %>%
      select(cat_h1, cat_h2) %>%
      distinct %>%
      filter(cat_h2 %>% is.na %>% `!`)
    
    h2_h3 =
      test_data %>%
      select(cat_h2, cat_h3) %>%
      distinct %>%
      filter(cat_h3 %>% is.na %>% `!`)
    
    h3_h4 = 
      test_data %>%
      select(cat_h3, cat_h4) %>%
      distinct %>%
      filter(cat_h4 %>% is.na %>% `!`)
    

    原件可以很容易地重构:

    h1_h2 %>%
      left_join(h2_h3) %>%
      left_join(h3_h4)
    

    编辑:这是一种自动化整个过程的方法。

    library(dplyr)
    library(lazyeval)
    
    adjacency = function(data) {
      adjacency_table = function(data, larger_name, smaller_name)
        lazy(data %>%
               select(larger_name, smaller_name) %>%
               distinct %>%
               filter(smaller_name %>% is.na %>% `!`) ) %>%
        interp(larger_name = larger_name %>% as.name, 
               smaller_name = smaller_name %>% as.name) %>%
        lazy_eval %>%
        setNames(c("larger", "smaller"))
    
      data_frame(smaller_name = data %>% names) %>%
        mutate(larger_name = smaller_name %>% lag) %>%
        slice(-1) %>%
        group_by(larger_name, smaller_name) %>%
        do(adjacency_table(data, .$larger_name, .$smaller_name) )
    }
    
    result = 
      test_data %>%
      select(-cat_id) %>%
      adjacency
    

    【讨论】:

    • 但这并不是 OP 所要求的全部。我可以理解“这不是一个好方法,这更好”,但这似乎非常偏离主题......
    • @BenBolker 从技术上讲,这不是主题,但实际上(偶然?)预料到我的迫切需要,即以邻接列表形式重新表示树(与原始的“列沿袭”形式相反) )!
    • 我可以看到这是被概括的,包裹在“lapply”中,然后通过管道传输到“bind_rows”。也许距离“减少”仅一步之遥。但是 --- 这在 OP 中没有体现 --- 如果有两个或多个节点具有相同的标签(但从根开始的不同路径),则可能会出现歧义/冲突的问题。
    • 我使用新的自动化版本进行了编辑。是的,存在歧义的可能性。但是,如果确实如此,两个或多个节点可以具有相同的标签但路径不同,则原始表中实际上没有冗余,可以保持原样。
    猜你喜欢
    • 1970-01-01
    • 2011-05-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-20
    • 2016-08-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多