如何将树形结构DataFrame转为指定格式并补充leaf列及优化代码?
树形DataFrame结构转换问题
原始数据
import pandas as pd df = pd.DataFrame({ 'level': [1,2,2,2,3,3,3], 'id': [1,11,12,13,111,121,122], 'parent_id': [pd.NA,1,1,1,11,12,12], 'type': ['node','node','node','leaf','leaf','leaf','leaf'], 'text': ['a','b','c','d','e','f','g'] })
期望输出
需要转换为带多级列的DataFrame,最后额外添加一组列收集每条路径对应的leaf节点信息:
1 2 3 leaf id type text id type text id type text id type text 0 1 node a 11 node b 111 leaf e 111 leaf e 1 1 node a 12 node c 121 leaf f 121 leaf f 2 1 node a 12 node c 122 leaf g 122 leaf g 3 1 node a 13 leaf d <NA> NaN NaN 13 leaf d
当前实现与输出
现有代码如下:
from functools import reduce def join_fn(x, y): i, df1 = x j, df2 = y return ( j, pd.merge(df1, df2, left_on=f"id_{i}", right_on=f"parent_id_{j}", how="outer"), ) dfs = list(df.groupby("level")) dfs = [ (i, df.rename(columns={col: col + f"_{i}" for col in df.columns})) for i, df in dfs ] _, dfr = reduce(join_fn, dfs) dfr = dfr.filter([col for col in dfr.columns if col.startswith(("id", "text", "type"))]) idx = dfr.columns.str.split("_", expand=True) dfr.columns = idx.swaplevel()
当前输出结果:
1 2 3 id type text id type text id type text 0 1 node a 11 node b 111.0 leaf e 1 1 node a 12 node c 121.0 leaf f 2 1 node a 12 node c 122.0 leaf g 3 1 node a 13 leaf d NaN NaN NaN
解决方案
一、添加Leaf节点列
要补充最后三列的leaf节点信息,可按以下步骤实现:
- 从原始数据中提取所有
type=leaf的记录,单独保存为DataFrame - 对现有结果
dfr,逐行定位对应路径的leaf节点ID(即每行最后一个非空的id值) - 将leaf节点ID与提取的leaf数据关联,补充
type和text字段 - 将这些字段添加为多级列的顶层
leaf
完整修改后的代码:
import pandas as pd from functools import reduce # 原始数据 df = pd.DataFrame({ 'level': [1,2,2,2,3,3,3], 'id': [1,11,12,13,111,121,122], 'parent_id': [pd.NA,1,1,1,11,12,12], 'type': ['node','node','node','leaf','leaf','leaf','leaf'], 'text': ['a','b','c','d','e','f','g'] }) # 提取所有leaf节点数据 leaf_df = df[df['type'] == 'leaf'][['id', 'type', 'text']].reset_index(drop=True) # 合并逻辑函数 def join_fn(x, y): i, df1 = x j, df2 = y return j, pd.merge(df1, df2, left_on=f"id_{i}", right_on=f"parent_id_{j}", how="outer") # 按level分组并给列添加后缀 dfs = [(i, df.add_suffix(f"_{i}")) for i, df in df.groupby("level")] # 逐级合并各level的数据 _, dfr = reduce(join_fn, dfs) # 过滤保留需要的列,设置多级列索引 dfr = dfr.filter(regex='^(id|type|text)_') idx = dfr.columns.str.split("_", expand=True) dfr.columns = idx.swaplevel() dfr = dfr.sort_index(axis=1) # 提取每行对应的leaf节点ID:从高level到低level取最后一个非空id leaf_ids = dfr.xs('id', axis=1, level=1).bfill(axis=1).iloc[:, -1].astype(int) # 关联leaf节点的type和text信息 leaf_data = leaf_df.set_index('id').loc[leaf_ids].reset_index() # 添加leaf组列到结果中 dfr[('leaf', 'id')] = leaf_data['id'] dfr[('leaf', 'type')] = leaf_data['type'] dfr[('leaf', 'text')] = leaf_data['text'] # 调整列顺序,将leaf组列放到最后 level_cols = [col for col in dfr.columns if col[0] != 'leaf'] leaf_cols = [('leaf', 'id'), ('leaf', 'type'), ('leaf', 'text')] dfr = dfr[level_cols + leaf_cols] # 修正id列的数据类型(支持空值的整数类型) for level in dfr.columns.get_level_values(0).unique(): if level != 'leaf': dfr[(level, 'id')] = dfr[(level, 'id')].fillna(pd.NA).astype('Int64') # 重置索引并打印结果 dfr = dfr.reset_index(drop=True) print(dfr.to_string())
运行后输出将完全匹配期望格式。
二、代码优化建议
- 列重命名简化:用
add_suffix替代手动字典映射,代码更简洁高效:df.add_suffix(f"_{i}") - 列过滤简化:用正则表达式
filter(regex='^(id|type|text)_')替代列表推导,可读性更强 - leaf ID查找优化:用
xs提取所有id列,结合bfill快速获取最后一个非空值,避免逐行循环 - 数据类型修正:将id列转为
Int64类型(支持空值的整数类型),避免显示为浮点数 - 列排序:合并后对列进行排序,保证level顺序逻辑正确
- 逻辑拆分:将leaf数据提取和关联逻辑单独拆分,代码结构更清晰
内容的提问来源于stack exchange,提问作者bayes2021
相关产品推荐
相关产品推荐

