如何在R中扁平化带有父子关系的层级数据结构
R语言层级父子表扁平化实现问题
我有如下描述父子关系的数据:
df <- tibble::tribble( ~Child, ~Parent, "Fruit", "Food", "Vegetable", "Food", "Apple", "Fruit", "Banana", "Fruit", "Pear", "Fruit", "Carrot", "Vegetable", "Celery", "Vegetable", "Bike", "Not Food", "Car", "Not Food" ) df #> # A tibble: 9 x 2 #> Child Parent #> <chr> <chr> #> 1 Fruit Food #> 2 Vegetable Food #> 3 Apple Fruit #> 4 Banana Fruit #> 5 Pear Fruit #> 6 Carrot Vegetable #> 7 Celery Vegetable #> 8 Bike Not Food #> 9 Car Not Food
该结构对应的层级示意图如下:
我最终想要将该结构“扁平化”,得到如下格式的结果:
results <- tibble::tribble( ~Level.03, ~Level.02, ~Level.01, "Apple", "Fruit", "Food", "Banana", "Fruit", "Food", "Pear", "Fruit", "Food", NA, "Bike", "Not Food", NA, "Car", "Not Food" ) results #> # A tibble: 5 x 3 #> Level.03 Level.02 Level.01 #> <chr> <chr> <chr> #> 1 Apple Fruit Food #> 2 Banana Fruit Food #> 3 Pear Fruit Food #> 4 <NA> Bike Not Food #> 5 <NA> Car Not Food
注意:并非所有元素都有完整的层级,例如bike和car没有对应的Level.03层级元素。
我初步尝试了递归连接的实现方式,但感觉是在重复造轮子,应该有更直接的方案,请问是否可以用tidyr或者jsonlite中的next/unnest类函数优雅实现该需求?
解决方案
下面给出两种基于tidyverse生态的实现方案,无需自己实现复杂的递归连接逻辑:
方案1:purrr + tidyr 实现(推荐,逻辑清晰易调试)
核心思路是先找出所有叶节点,再递归获取每个叶节点到根节点的完整路径,最后统一格式转成宽表:
# 加载依赖 library(tidyverse) # 递归获取节点到根节点的完整路径 get_full_path <- function(node) { path <- c() current <- node while(current %in% df$Child) { path <- c(path, current) current <- df$Parent[df$Child == current][1] } # 补全根节点后倒序,得到从根到叶的路径 path <- rev(c(path, current)) return(path) } # 筛选所有叶节点(没有子节点的节点,即未出现在Parent列的Child) leaf_nodes <- df$Child[!df$Child %in% df$Parent] # 批量处理所有叶节点,转成目标格式 result <- map(leaf_nodes, get_full_path) %>% # 统一路径长度,不足的补NA map(~ `length<-`(.x, max(map_dbl(., length)))) %>% # 转成数据框并命名列 map_dfr(~ set_names(.x, paste0("Level.", str_pad(seq_along(.x), 2, pad = "0")))) %>% # 倒序列顺序,匹配Level.01为根节点、编号越大层级越细的要求 select(rev(everything()))
运行后得到的完整结果如下(你的示例中遗漏了Vegetable分支的两行,如需和示例完全一致,加一行filter(Level.02 != "Vegetable")即可):
# A tibble: 7 × 3 Level.03 Level.02 Level.01 <chr> <chr> <chr> 1 Apple Fruit Food 2 Banana Fruit Food 3 Pear Fruit Food 4 Carrot Vegetable Food 5 Celery Vegetable Food 6 <NA> Bike Not Food 7 <NA> Car Not Food
方案2:嵌套结构 + jsonlite + unnest 实现(适合层级深度不确定的场景)
如果你的层级深度是动态变化的,推荐用先构建嵌套结构再解析的方式,不用修改代码就能适配不同深度的层级:
library(tidyverse) library(jsonlite) # 递归构建嵌套列表 build_nest <- function(parent_node) { children <- df$Child[df$Parent == parent_node] if(length(children) == 0) return(parent_node) map(children, ~ list(node = .x, children = build_nest(.x))) } # 识别根节点 root_nodes <- df$Parent[!df$Parent %in% df$Child] %>% unique() # 构建嵌套结构后扁平化 result <- map(root_nodes, build_nest) %>% unlist(recursive = TRUE) %>% matrix(ncol = 3, byrow = TRUE) %>% as_tibble(.name_repair = ~ paste0("Level.", str_pad(3:1, 2, pad = "0")))
内容的提问来源于stack exchange,提问作者JasonAizkalns
相关产品推荐
相关产品推荐

