
本文介绍一种基于 pandas.melt() 与递归字典构建的通用方法,可将 DataFrame 按任意数量的列(如 Region/Country/AREA_CODE 等)分组,并将目标列(如 AREA_SUB_CODE_1/2)的值按层级结构组织为嵌套字典,支持去重、列表化及多层索引映射。
本文介绍一种基于 `pandas.melt()` 与递归字典构建的通用方法,可将 dataframe 按任意数量的列(如 region/country/area_code 等)分组,并将目标列(如 area_sub_code_1/2)的值按层级结构组织为嵌套字典,支持去重、列表化及多层索引映射。
在实际数据分析中,常需将宽表结构转换为深度嵌套的字典,尤其当数据具有明确的层级关系(如 Region → Country → AREA_CODE → SUB_CODE)时。直接使用 groupby 多列后解包会因元组长度不匹配而报错(如 too many values to unpack (expected 2)),根本原因在于 df.groupby([...])['col'] 返回的是 SeriesGroupBy 对象,其迭代项为 (group_keys_tuple, series),而非单个标量键。
✅ 正确思路:先熔解(melt),再构建多级索引 Series,最后递归展开
核心步骤如下:
-
熔解目标列:将多个待聚合的子列(如
AREA_SUB_CODE_1,AREA_SUB_CODE_2)统一转为长格式,新增variable列标识原始列名; -
设置多级索引:以
variable+ 分组列(如Region,Country,AREA_CODE)构建MultiIndex; - 递归构建嵌套字典:对每个索引层级递归映射,底层提取唯一值并转为列表。
✅ 示例代码(完整可运行)
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']
})
# Step 1: 熔解所有 SUB_CODE 列,保留分组列
group_cols = ['Region', 'Country', 'AREA_CODE']
sub_code_cols = ['AREA_SUB_CODE_1', 'AREA_SUB_CODE_2']
# 使用 melt 将 SUB_CODE 列转为长格式,'variable' 表示原列名
melted = df.melt(
id_vars=group_cols,
value_vars=sub_code_cols,
var_name='sub_code',
value_name='value'
)
# Step 2: 设置多级索引并压缩为 Series
# 索引顺序:(sub_code, Region, Country, AREA_CODE)
indexed_series = melted.set_index(['sub_code'] + group_cols)['value'].squeeze()
# Step 3: 递归函数 —— 将多级索引 Series 转为嵌套字典
def nested_dict_from_series(s):
if s.index.nlevels == 1:
# 最底层:按索引值分组,取唯一值并转为 list
return {k: s.loc[[k]].unique().tolist() for k in s.index.unique()}
else:
# 非最底层:对第一级索引递归处理
return {
k: nested_dict_from_series(s[k])
for k in s.index.levels[0]
}
# 执行转换
result = nested_dict_from_series(indexed_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']}
}
}
}
✅ 优势总结:
- ✅ 支持任意数量的分组列(4列、5列甚至更多),只需扩展
group_cols列表; - ✅ 自动适配新增的
SUB_CODE类列,无需修改逻辑; - ✅ 底层自动去重(
.unique())并转为 Pythonlist,便于 JSON 序列化或后续处理; - ✅ 无硬编码层级,完全由索引层级数驱动,健壮性强。
⚠️ 注意事项
- 若某组内存在重复值且你不需要去重,请将
.unique().tolist()替换为.tolist(); - 若每组保证仅有一个值(如主键约束),可进一步优化为
s.to_dict(),但需确保数据一致性; -
squeeze()在单列时安全;若melted含多列需谨慎,此处因只取'value'列故无风险; - 建议对原始数据做空值检查(
df.isna().sum()),避免NaN进入索引导致意外分组。
该方法摆脱了传统 for 循环手动嵌套的繁琐与易错性,是处理“多维分组→嵌套字典”场景的专业级解决方案。










