
本文介绍使用pandas按id分组后,依据问题编码的层级结构(如q02-1-1是q02-1的子项,q02-1又是q02的子项)自动剔除被更细粒度子项覆盖的父级行,保留最深层的有效问题代码。
本文介绍使用pandas按id分组后,依据问题编码的层级结构(如q02-1-1是q02-1的子项,q02-1又是q02的子项)自动剔除被更细粒度子项覆盖的父级行,保留最深层的有效问题代码。
在问卷或层级化编码场景中,常出现类似 Q01 → Q01-1 → Q01-1-1 的父子关系。若某ID已包含子级代码(如 Q01-1),则其直接父级(Q01)往往冗余;同理,Q02-1-1 的存在意味着 Q02-1 和 Q02 均可被跳过——但注意:仅当父级被“严格更深层”的子项覆盖时才剔除,且需按ID独立判断。
核心思路是:对每个ID内的 Question Code,识别出所有“可被替代的父级”——即那些恰好等于某个其他代码截去末尾 -xxx 后的部分(例如 Q02-1-1 → 提取 Q02-1;Q02-1 → 提取 Q02)。若某行的代码出现在该提取结果集中,说明它已被更细粒度的子项隐含覆盖,应被过滤掉。
以下是完整实现:
import pandas as pd
# 构造示例数据
df = pd.DataFrame({
'ID': [1, 1, 1, 2, 2, 2, 2, 2],
'Question Code': ['Q01', 'Q01-1', 'Q02', 'Q01', 'Q02', 'Q02-1', 'Q02-1-1', 'Q02-2']
})
# 关键逻辑:每组内,找出所有“作为其他代码前缀”的行并剔除
mask = df.groupby('ID')['Question Code'].transform(
lambda x: ~x.isin(
x.str.extract(r'^(.*)-[^-]+$', expand=False).dropna()
)
)
out = df[mask].reset_index(drop=True)
print(out)输出:
ID Question Code 0 1 Q01-1 1 1 Q02 2 2 Q01 3 2 Q02-1-1 4 2 Q02-2
✅ 关键说明:
- 正则
r'^(.*)-[^-]+$'精确匹配末尾一个-及其后非-字符(即最后一级子后缀),(.*)捕获其前全部内容作为潜在父级; -
str.extract(...).dropna()得到所有“可能被替代的父级候选”; -
~x.isin(...)保留未被任何子项直接引用的行——这正是我们要的“最深层有效节点”; -
groupby('ID').transform确保比较仅发生在同一ID内,避免跨ID误删(如ID=1的Q01不影响ID=2的Q01)。
⚠️ 注意事项:
- 该方法假设层级由
-分隔,且子项严格以父项为前缀(如Q02-1-1必含Q02-1,而Q02-X不会误判为Q02的子项); - 若存在不规范编码(如
Q02-extra或Q02_1),需先统一清洗格式; - 如需保留“所有最深叶子节点”(而非仅一级子替代),本逻辑天然支持多层嵌套(
Q02-1-1-1会淘汰Q02-1-1和Q02-1),无需额外修改。
此方案简洁、向量化、可扩展,适用于千万级问卷数据的层级精简预处理。

















