
本文介绍如何基于 3 个及以上列对 dataframe 进行分组,并递归构建嵌套字典,将指定值列(如 area_sub_code_1/2)按 region→country→area_code 多层键路径聚合为列表或单值映射。
本文介绍如何基于 3 个及以上列对 dataframe 进行分组,并递归构建嵌套字典,将指定值列(如 area_sub_code_1/2)按 region→country→area_code 多层键路径聚合为列表或单值映射。
在 Pandas 中,直接对 4 列或更多列调用 groupby 并解包元组(如 for (k1,k2,k3), v in ...)容易因解包维度不匹配报错(如 too many values to unpack)。根本原因在于:当 groupby 的 key 数量超过 2 时,迭代器返回的是长度为 n_keys 的元组,而 for (k1, k2), v in ... 仅声明了两个变量,导致解包失败。
更稳健、可扩展的方案是避免手动解包,转而利用 Pandas 的层级索引(MultiIndex)与递归字典构造。核心思路分三步:
-
重塑数据结构:使用
melt()将待聚合的多个子码列(如AREA_SUB_CODE_1,AREA_SUB_CODE_2)统一为长格式,并以variable(列名)作为第一级索引; -
构建多级索引 Series:以
['variable', 'Region', 'Country', 'AREA_CODE']为索引,调用.squeeze()得到带 MultiIndex 的 Series; -
递归生成嵌套字典:定义一个 lambda 函数,根据索引层级数(
.index.nlevels)逐层展开,底层提取唯一值并转为列表。
以下是完整实现代码:
import pandas as pd
# 示例数据
df = pd.DataFrame({
'Region': ['AMER', 'AMER', 'AMER', 'AMER'],
'Country': ['US', 'CANADA', 'US', 'US'],
'AREA_CODE': ['A1', 'A1', 'B1', 'A1'],
'AREA_SUB_CODE_1': ['A1_US_1x', 'A1_CA_1', 'B1_US_1', 'A1_US_1y'],
'AREA_SUB_CODE_2': ['A1_US_2', 'A1_CA_2', 'B1_US_2', 'A1_US_2']
})
# 步骤1 & 2:熔化 + 设置多级索引
inner_cols = ['Region', 'Country', 'AREA_CODE']
values_series = (
df.melt(id_vars=inner_cols, var_name='sub_code')
.set_index(['sub_code'] + inner_cols)
.squeeze() # 转为 Series,索引为 MultiIndex
)
# 步骤3:递归构建嵌套字典(支持任意层级,自动处理重复值)
def build_nested_dict(s: pd.Series) -> dict:
if s.index.nlevels > 1:
return {k: build_nested_dict(s.xs(k)) for k in s.index.levels[0]}
else:
# 底层:按索引值分组,取唯一值并转为 list(保留去重后所有可能值)
return {k: s.loc[k].unique().tolist() for k in s.index.unique()}
result = build_nested_dict(values_series)
print(result)
输出示例(已格式化):
{
"AREA_SUB_CODE_1": {
"AMER": {
"CANADA": {"A1": ["A1_CA_1"]},
"US": {"A1": ["A1_US_1x", "A1_US_1y"], "B1": ["B1_US_1"]}
}
},
"AREA_SUB_CODE_2": {
"AMER": {
"CANADA": {"A1": ["A1_CA_2"]},
"US": {"A1": ["A1_US_2"], "B1": ["B1_US_2"]}
}
}
}
✅ 关键优势:
- ✅ 无需硬编码解包变量数:函数自动适配 3 层、4 层甚至 5 层分组;
- ✅ 天然支持多值聚合:
.unique().tolist()确保相同键路径下的重复值被去重并保留为列表; - ✅ 结构清晰可读:输出严格遵循
{"列名": {"一级键": {"二级键": {...}}}}的嵌套逻辑,便于后续 JSON 序列化或 API 返回。
⚠️ 注意事项:
- 若业务要求每个末级键只对应唯一值(即无重复),可将底层替换为
s.to_dict(),但需确保数据无歧义; -
melt()后务必使用.set_index(...).squeeze(),否则无法获得带 MultiIndex 的 Series; - 对于超大数据集,递归函数可能存在栈深度限制,此时建议改用迭代方式(如
defaultdict+ 循环遍历索引)。
该方法不仅解决了“4 列 groupby 解包失败”的表层问题,更提供了一种可复用、可扩展的多维聚合建模范式,适用于配置管理、权限树、地理编码映射等典型场景。










