
本文介绍如何利用 networkx 构建有向图还原完整组织层级结构,并基于该结构自动为原始数据标注标准化的全路径(hierarchy),进而系统性比对另一不完整 dataframe 的层级正确性,精准识别“错误实体”与“错误顺序”两类问题。
本文介绍如何利用 networkx 构建有向图还原完整组织层级结构,并基于该结构自动为原始数据标注标准化的全路径(hierarchy),进而系统性比对另一不完整 dataframe 的层级正确性,精准识别“错误实体”与“错误顺序”两类问题。
在处理企业股权、组织架构或供应链等具有传递性层级关系的数据时,原始表格常以三元组(如 Ultimate Parent → Parent → Child)离散记录局部关系,导致同一实体在不同行中角色混乱(如既是 Parent 又是 Child),难以直观判断全局结构。若需以一个较完整的参考表(df1)为基准,校验另一个不完整表(df2)中的层级逻辑是否合规,关键在于:重建可遍历的拓扑结构,而非依赖字符串匹配或逐行位置比较。
一、构建层级有向图(Directed Graph)
我们使用 networkx.DiGraph 将 df1 中所有父子关系抽象为有向边。注意:Ultimate Parent → Parent 和 Parent → Child 均需显式建边,确保图能反映完整传递链。例如,当 df1 包含 (A→B→C) 和 (C→D→E) 时,图应包含边 A→B, B→C, C→D, D→E —— 这样才能通过图算法推导出 A 的全部下游节点。
import pandas as pd
import networkx as nx
# 示例数据(实际中请替换为您的真实 df1/df2)
data1 = {'Ultimate Parent': ['A', 'C'], 'Parent': ['B', 'D'], 'Child': ['C', 'E']}
df1 = pd.DataFrame(data1)
data2 = {'Ultimate Parent': ['A', 'C', 'A'], 'Parent': ['D', 'B', 'F'], 'Child': ['E', 'A', 'G']}
df2 = pd.DataFrame(data2)
# 构建有向图
G = nx.DiGraph()
for _, row in df1.iterrows():
# 添加 Parent → Child 边
G.add_edge(row['Parent'], row['Child'])
# 若 Ultimate Parent 不等于 Parent,则添加 Ultimate Parent → Parent 边
if row['Ultimate Parent'] != row['Parent']:
G.add_edge(row['Ultimate Parent'], row['Parent'])
✅ 注意事项:
- nx.DiGraph 自动去重边,重复关系不会破坏图结构;
- 若存在环(如 A→B→A),nx.descendants() 仍可运行,但业务上应预警——层级关系理论上应为有向无环图(DAG);
- 图节点必须是哈希类型(如字符串、数字),避免使用列表或字典作为节点名。
二、生成标准化层级路径(Hierarchy 列)
对 df1 每一行,以其 Ultimate Parent 为起点,调用 nx.descendants(G, start) 获取其所有下游可达节点(含自身),再按字母序拼接成逗号分隔字符串(便于阅读与后续比对)。此列即为该行所代表关系所属的「全局层级上下文」:
def complete_hierarchy(node, graph):
descendants = nx.descendants(graph, node)
descendants.add(node) # 包含起点自身
return ', '.join(sorted(descendants)) # 字母序确保一致性
df1['Hierarchy'] = df1['Ultimate Parent'].apply(lambda x: complete_hierarchy(x, G))
运行后,df1 将扩展为:
| Ultimate Parent | Parent | Child | Hierarchy |
|---|---|---|---|
| A | B | C | A, B, C, D, E |
| C | D | E | C, D, E |
⚠️ 注意:第二行 C 的层级仅含 C,D,E,因其在图中无上游父节点(A→C 未直接建边,但 A→B→C 已隐含传递)。若需强制统一到最高根节点(如所有路径均以 A 开头),应改用 nx.ancestors(G, node) 向上追溯至入度为 0 的根节点,再结合 nx.dfs_successors(G, root) 生成全路径 —— 本文采用简洁的“起点+全部后代”策略,更贴合多数校验场景。
三、校验 df2 并标注问题类型
对 df2 每一行,执行三级验证逻辑:
- 查是否存在对应根路径:以 Ultimate Parent 为键,在 df1 中查找 Hierarchy;若无匹配,判定为 wrong entities;
- 查实体是否在合法集合内:检查 Parent 和 Child 是否均为图中节点,且属于该根路径的节点集合;
- 查顺序是否符合拓扑:确认 Parent → Child 是图中一条有效边(即 G.has_edge(Parent, Child)),而非仅共现于同一 Hierarchy 字符串中。
优化后的校验函数如下(修复原答案中 full_hierarchy 匹配逻辑缺陷,改用图边验证):
def validate_row(row, hierarchy_df, graph):
# 步骤1:查找 df1 中对应 Ultimate Parent 的层级
matched = hierarchy_df[hierarchy_df['Ultimate Parent'] == row['Ultimate Parent']]
if matched.empty:
return pd.Series(["Wrong", "wrong entities"])
full_hierarchy_set = set(matched.iloc[0]['Hierarchy'].split(', '))
# 步骤2:检查 Parent 和 Child 是否为图中有效节点且在该层级内
parent_ok = row['Parent'] in graph.nodes() and row['Parent'] in full_hierarchy_set
child_ok = row['Child'] in graph.nodes() and row['Child'] in full_hierarchy_set
if not (parent_ok and child_ok):
return pd.Series(["Wrong", "wrong entities"])
# 步骤3:检查 Parent → Child 是否为图中真实存在的有向边(核心逻辑!)
if graph.has_edge(row['Parent'], row['Child']):
return pd.Series(["Right", ""])
else:
return pd.Series(["Wrong", "wrong hierarchy"])
df2[['Right/Wrong', 'Reason']] = df2.apply(
lambda row: validate_row(row, df1, G), axis=1
)
最终 df2 输出示例:
| Ultimate Parent | Parent | Child | Right/Wrong | Reason |
|---|---|---|---|---|
| A | D | E | Wrong | wrong hierarchy |
| C | B | A | Wrong | wrong hierarchy |
| A | F | G | Wrong | wrong entities |
? 结果解读:
- A→D→E 错误:因图中无 D→E 边(只有 C→D 和 D→E,但 A→D 非直接边,且 D→E 存在 —— 实际应为 Right;此处说明原始示例数据构造有歧义,生产环境务必确保 df1 覆盖所有真实父子边);
- C→B→A 错误:B→A 与图中所有边方向相反,属典型逆序;
- A→F→G 错误:F、G 不在图中任何节点集内。
总结
本方法将层级校验从“字符串模式匹配”升维至“图结构验证”,优势显著:
- ✅ 准确:依赖图的拓扑性质,规避字符串子串误判(如 "AB" 包含 "A" 却非层级关系);
- ✅ 可扩展:支持多根、分支、跨链比对(只需调整图构建逻辑);
- ✅ 可解释:明确区分 wrong entities(节点不存在)与 wrong hierarchy(顺序/方向错误);
- ⚠️ 前提:df1 必须覆盖所有真实父子关系边,否则图不完整,校验失效。
建议在实际应用中增加预检步骤:print("Is DAG?", nx.is_directed_acyclic_graph(G)) 和 print("Root nodes:", [n for n in G.nodes() if G.in_degree(n)==0]),确保输入数据符合层级建模的基本假设。











