Python脚本修改需求:Excel物料数据生成正确层级树结构
问题描述
我开发了一段Python脚本,用于从Excel文件中提取Finished Good、Parent Part Code、Material Code三列数据,生成物料层级树结构。现有脚本运行后出现异常:代码V371同时作为Finished Good 10020115HU的直接子节点,以及C00211L0的子节点存在,不符合预期的树结构。
需求:修改脚本,使V371仅作为C00211L0的子节点,并包含其完整的子树,生成符合预期的层级树结构。
数据示例
Material Code Parent Part Code Finished Good M1 P1 F1 M2 P2 F2 M3 M2 F2 M4 P3 F2 .....
注:Material Code也可能出现在Parent Part Code列中。
预期树结构示例
F1 P1 M1 F2 P2 M2 M3 P3 M4
当前Python脚本
import pandas as pd from anytree import Node, RenderTree import json # Read excel df = pd.read_excel('excelFile.xlsx') root_dict = {} for index, row in df.iterrows(): finished_good = row['Finished Good'] parent_part_code = row['Parent Part Code'] material_code = row['Material Code'] if finished_good not in root_dict: root = Node(finished_good) root_dict[finished_good] = root else: root = root_dict[finished_good] if parent_part_code in [node.name for node in root.descendants]: parent_node = [node for node in root.descendants if node.name == parent_part_code][0] if material_code in [node.name for node in root.descendants]: material_node = [node for node in root.descendants if node.name == material_code][0] material_node.parent = parent_node else: material = Node(material_code, parent=parent_node) while material_code in df['Parent Part Code'].values: filtered = df[df['Parent Part Code'] == material_code] material_code = filtered.iloc[0]['Material Code'] parent_node = material material = Node(material_code, parent=parent_node) else: parent_node = Node(parent_part_code, parent=root) material = Node(material_code, parent=parent_node) while material_code in df['Parent Part Code'].values: filtered = df[df['Parent Part Code'] == material_code] material_code = filtered.iloc[0]['Material Code'] parent_node = material material = Node(material_code, parent=parent_node) # Print the trees for root in root_dict.values(): print(RenderTree(root)) # Save the trees def node_to_dict(node): return { 'name': node.name, 'children': [node_to_dict(child) for child in node.children] } with open('normTrees.json', 'w') as file: json.dump({key: node_to_dict(root) for key, root in root_dict.items()}, file) print("The trees were successfully saved")
当前输出结果
Node('/10020115HU') ├── Node('/10020115HU/V371') │ ├── Node('/10020115HU/V371/YG10-30300') │ ├── Node('/10020115HU/V371/VECTC002') │ │ ├── Node('/10020115HU/V371/VECTC002/YG10-30200') │ │ ├── Node('/10020115HU/V371/VECTC002/VNCTC002') │ │ │ ├── Node('/10020115HU/V371/VECTC002/VNCTC002/YG10-30300') │ │ │ ├── Node('/10020115HU/V371/VECTC002/VNCTC002/SZVIZ') │ │ │ └── Node('/10020115HU/V371/VECTC002/VNCTC002/RVSZALLPOR') │ │ └── Node('/10020115HU/V371/VECTC002/RVECTC002') │ └── Node('/10020115HU/V371/U100KOCS') │ └── Node('/10020115HU/V371/U100KOCS/YG10-30300') ├── Node('/10020115HU/C00211L0') │ ├── Node('/10020115HU/C00211L0/V371') │ │ └── Node('/10020115HU/C00211L0/V371/YG10-30300') │ ├── Node('/10020115HU/C00211L0/RWINNOVERS') │ └── Node('/10020115HU/C00211L0/RSZENNYEZETT') ├── Node('/10020115HU/10020115HU') │ └── Node('/10020115HU/10020115HU/TTK00001HU') ├── Node('/10020115HU/D67AMBR910') │ └── Node('/10020115HU/D67AMBR910/RWINNOVERS') └── Node('/10020115HU/D67LTRR910') ├── Node('/10020115HU/D67LTRR910/RWINNOVERS') └── Node('/10020115HU/D67LTRR910/RSECONDUST') The trees were successfully saved
预期输出结构
Node('/10020115HU') ├── Node('/10020115HU/C00211L0') │ ├── Node('/10020115HU/C00211L0/V371') │ │ └── Node('/10020115HU/C00211L0/V371/YG10-30300') │ │ └── Node('/10020115HU/C00211L0/V371/VECTC002') │ │ ├── Node('/10020115HU/C00211L0/V371/VECTC002/YG10-30200') │ │ ├── Node('/10020115HU/C00211L0/V371/VECTC002/VNCTC002') │ │ │ ├── Node('/10020115HU/C00211L0/V371/VECTC002/VNCTC002/YG10-30300') │ │ │ ├── Node('/10020115HU/C00211L0/V371/VECTC002/VNCTC002/SZVIZ') │ │ │ └── Node('/10020115HU/C00211L0/V371/VECTC002/VNCTC002/RVSZALLPOR') │ │ └── Node('/10020115HU/C00211L0/V371/VECTC002/RVECTC002') │ ├── Node('/10020115HU/C00211L0/V371/U100KOCS') │ │ └── Node('/10020115HU/C00211L0/V371/U100KOCS/YG10-30300') │ ├── Node('/10020115HU/C00211L0/RWINNOVERS') │ └── Node('/10020115HU/C00211L0/RSZENNYEZETT') ├── Node('/10020115HU/10020115HU') │ └── Node('/10020115HU/10020115HU/TTK00001HU') ├── Node('/10020115HU/D67AMBR910') │ └── Node('/10020115HU/D67AMBR910/RWINNOVERS') └── Node('/10020115HU/D67LTRR910') ├── Node('/10020115HU/D67LTRR910/RWINNOVERS') └── Node('/10020115HU/D67LTRR910/RSECONDUST')
修改后的脚本及说明
问题根源
原脚本核心问题是没有全局跟踪所有已创建的节点,仅在每个根节点的后代中查找。处理不同行时可能重复创建同一物料的节点,导致同一物料出现在树的多个位置;同时递归创建子节点的逻辑仅处理单条关联行,未覆盖所有子节点数据。
修改后的代码
import pandas as pd from anytree import Node, RenderTree import json # 读取Excel文件 df = pd.read_excel('excelFile.xlsx') # 全局字典:跟踪所有已创建的节点,key为物料/成品代码,value为对应的Node对象 all_nodes = {} # 根节点字典:存储每个Finished Good对应的根节点 root_dict = {} # 第一步:先创建所有节点(确保每个代码只创建一次) for _, row in df.iterrows(): finished_good = row['Finished Good'] parent_part = row['Parent Part Code'] material = row['Material Code'] # 创建根节点(如果不存在) if finished_good not in all_nodes: root_node = Node(finished_good) all_nodes[finished_good] = root_node root_dict[finished_good] = root_node # 创建父节点(如果不存在) if parent_part not in all_nodes: all_nodes[parent_part] = Node(parent_part) # 创建物料节点(如果不存在) if material not in all_nodes: all_nodes[material] = Node(material) # 第二步:建立父子关系 for _, row in df.iterrows(): finished_good = row['Finished Good'] parent_part = row['Parent Part Code'] material = row['Material Code'] parent_node = all_nodes[parent_part] material_node = all_nodes[material] # 如果父节点还没有父节点,且父节点不是根节点,则将父节点挂到对应的成品根节点下 if parent_node.parent is None and parent_node.name != finished_good: parent_node.parent = all_nodes[finished_good] # 设置物料节点的父节点 material_node.parent = parent_node # 打印树结构 for root in root_dict.values(): print(RenderTree(root)) # 将树结构保存为JSON def node_to_dict(node): return { 'name': node.name, 'children': [node_to_dict(child) for child in node.children] } with open('normTrees.json', 'w') as file: json.dump({key: node_to_dict(root) for key, root in root_dict.items()}, file, indent=2) print("树结构已成功保存到normTrees.json")
关键修改点
- 全局节点跟踪:新增
all_nodes字典,确保每个物料/成品代码只创建一个Node对象,从根本上避免重复节点。 - 分两步处理:先批量创建所有节点,再统一建立父子关系,确保所有关联都能正确映射,不会因为行处理顺序导致遗漏或错误。
- 修复父节点挂载逻辑:确保非根节点的父节点正确挂到对应的成品根节点下,避免出现游离节点。
修改后,V371只会存在一个节点,且会被正确挂载到C00211L0下,同时保留其完整的子树结构,符合预期输出。
内容的提问来源于stack exchange,提问作者David
相关产品推荐
相关产品推荐

