什么是 DataFrame 中的“嵌套”?
在 Pandas 中,“嵌套”通常指以下几种情况:

- 列中包含嵌套的数据结构:最常见的情况是 DataFrame 的某一列,其单元格本身不是一个简单的标量值(如数字、字符串),而是一个列表、字典、元组或其他嵌套结构。
- 多级索引:这是一种结构上的“嵌套”,行或列索引本身具有层级关系。
- JSON 数据:从 API 或数据库读取的 JSON 数据,常常是嵌套的,直接导入 DataFrame 后会形成上述第一种情况。
下面我们重点讨论最常见也最麻烦的第一种情况:列中包含嵌套结构。
列中包含嵌套结构
这是数据处理中最需要解决的问题,我们来看一个典型的例子。
示例:一个包含嵌套字典的 DataFrame
假设我们有以下数据,每个用户除了基本信息外,还有一些“元数据”存储在字典里。
import pandas as pd
import numpy as np
data = {
'id': [1, 2, 3],
'name': ['Alice', 'Bob', 'Charlie'],
'metadata': [
{'signup_date': '2025-01-15', 'status': 'active', 'login_count': 25},
{'signup_date': '2025-03-22', 'status': 'inactive', 'login_count': 5},
{'signup_date': '2025-05-30', 'status': 'active', 'login_count': 120}
]
}
df = pd.DataFrame(data)
print(df)
输出结果:
id name metadata
0 1 Alice {'signup_date': '2025-01-15', 'status':...
1 2 Bob {'signup_date': '2025-03-22', 'status':...
2 3 Charlie {'signup_date': '2025-05-30', 'status':...
你看,metadata 这一列的每个值都是一个字典,这会带来很多问题,
# 尝试对 metadata 列进行数值计算会失败 # df['metadata'].mean() # TypeError: Could not convert ['active', 'inactive', 'active'] to numeric # 尝试筛选也很麻烦 # 比如想找出 status 是 'active' 的用户 # df[df['metadata'] == 'active'] # 这行代码是错误的,不会得到预期结果
解决方案:将嵌套列“展开”(Explode/Normalize)
目标是将 metadata 字典中的键值对,变成 DataFrame 的独立列,主要有两种方法。
使用 pd.json_normalize() (推荐,专为 JSON/字典设计)
这是最直接、最优雅的方法,专门用于将半结构化的 JSON 数据“扁平化”为表格。
# 使用 json_normalize 展开嵌套的字典列
# record_path 指定要展开的嵌套数据所在的列
df_normalized = pd.json_normalize(data, record_path=['metadata'])
print("展开后的 metadata:")
print(df_normalized)
输出:
展开后的 metadata:
signup_date status login_count
0 2025-01-15 active 25
1 2025-03-22 inactive 5
2 2025-05-30 active 120
我们需要将这个新 DataFrame 和原来的 id、name 列合并起来。
# 使用 pd.concat 水平合并
df_final = pd.concat([df[['id', 'name']], df_normalized], axis=1)
print("\n最终合并后的 DataFrame:")
print(df_final)
最终结果:
最终合并后的 DataFrame:
id name signup_date status login_count
0 1 Alice 2025-01-15 active 25
1 2 Bob 2025-03-22 inactive 5
2 3 Charlie 2025-05-30 active 120
完美!嵌套问题解决了。
使用 apply 和 pd.Series (更通用,适用于列表等)
如果你的嵌套结构不是字典,而是列表,或者你想要更灵活的控制,可以使用 apply 方法。
# 回到最初的嵌套 DataFrame df_nested = pd.DataFrame(data) # 对 metadata 列应用 pd.Series 函数 # 这会把每个字典转换成一个 Series,然后这些 Series 会自动对齐并合并成新的列 df_expanded = df_nested['metadata'].apply(pd.Series) print(df_expanded)
输出:
signup_date status login_count
0 2025-01-15 active 25
1 2025-03-22 inactive 5
2 2025-05-30 active 120
然后同样地,将它与原始列合并:
df_final_v2 = pd.concat([df_nested[['id', 'name']], df_expanded], axis=1)
print("\n使用 apply 方法得到的最终结果:")
print(df_final_v2)
结果与方法一完全相同。
列中包含列表
嵌套的是列表而不是字典。
示例:
data_list = {
'id': [1, 2, 3],
'name': ['Alice', 'Bob', 'Charlie'],
'hobbies': [
['reading', 'hiking', 'painting'],
['gaming', 'cooking'],
['coding', 'reading', 'traveling']
]
}
df_list = pd.DataFrame(data_list)
print(df_list)
输出:
id name hobbies
0 1 Alice [reading, hiking, painting]
1 2 Bob [gaming, cooking]
2 3 Charlie [coding, reading, traveling]
解决方案:展开列表
使用 explode() (如果要将列表中的每个元素变成一行)
如果你的目标是让每个爱好都成为独立的一行,explode() 是最佳选择。
df_exploded = df_list.explode('hobbies')
print(df_exploded)
输出:
id name hobbies
0 1 Alice reading
0 1 Alice hiking
0 1 Alice painting
1 2 Bob gaming
1 2 Bob cooking
2 3 Charlie coding
2 3 Charlie reading
2 3 Charlie traveling
创建多个列 (如果要将列表中的元素分配到固定列)
如果列表长度固定,并且想把它们分别放到不同列里。
# 使用 apply 和 pd.Series
hobbies_df = df_list['hobbies'].apply(pd.Series)
# 重命名列
hobbies_df = hobbies_df.rename(columns=lambda x: f'hobby_{x+1}')
# 合并
df_with_hobbies = pd.concat([df_list, hobbies_df], axis=1)
print(df_with_hobbies)
输出:
id name hobbies hobby_1 hobby_2 hobby_3
0 1 Alice [reading, hiking, painting] reading hiking painting
1 2 Bob [gaming, cooking] gaming cooking None
2 3 Charlie [coding, reading, traveling] coding reading traveling
注意:如果列表长度不一致,较短的列表会用 NaN 填充。
多级索引
多级索引是一种结构上的嵌套,本身不是“问题”,而是一种强大的数据组织方式。
示例:
# 创建一个多级索引的 DataFrame
index = pd.MultiIndex.from_tuples([('A', 'X'), ('A', 'Y'), ('B', 'X'), ('B', 'Y')],
names=['letter', 'number'])
columns = pd.MultiIndex.from_tuples([('Metrics', 'Score'), ('Metrics', 'Value'), ('Status', 'Flag')],
names=['category', 'attribute'])
data_multi = [
[10, 100, 'OK'],
[12, 150, 'OK'],
[8, 80, 'Fail'],
[9, 95, 'OK']
]
df_multi = pd.DataFrame(data_multi, index=index, columns=columns)
print(df_multi)
输出:
category Metrics Status
attribute Score Value Flag
letter number
A X 10 100 OK
Y 12 150 OK
B X 8 80 Fail
Y 9 95 OK
如何操作多级索引?
-
访问数据:
- 使用
df_multi.loc进行标签索引。 - 使用
df_multi.xs进行跨层索引。
# 访问 'A' 行的所有数据 print(df_multi.loc['A']) # 访问 'Score' 列的所有数据 print(df_multi[('Metrics', 'Score')]) # 使用 xs 获取 'X' 列的所有数据 (在 'number' 这一层) print(df_multi.xs('X', level='number')) - 使用
-
取消分层: 如果觉得多级索引太复杂,可以随时取消它。
# 将列索引堆叠成新的行索引 df_stacked = df_multi.stack() print(df_stacked) # 将行索引堆叠成新的列索引 df_unstacked = df_multi.unstack() print(df_unstacked) # 完全重置索引,变成普通列 df_reset = df_multi.reset_index() print(df_reset)
总结与最佳实践
| 问题类型 | 示例 | 推荐解决方案 | 备注 |
|---|---|---|---|
| 列中嵌套字典 | df['col'] = [{'a': 1}, {'b': 2}] |
pd.json_normalize() |
最简单、最直接。 |
df['col'].apply(pd.Series) |
更通用,也适用于列表。 | ||
| 列中嵌套列表 | df['col'] = [[1, 2], [3]] |
df.explode('col') |
当需要将列表元素转为行时。 |
df['col'].apply(pd.Series) |
当需要将列表元素转为列时。 | ||
| 多级索引 | df.index = pd.MultiIndex(...) |
df.loc[] / df.xs() |
学会使用它们来访问数据。 |
df.stack() / df.unstack() |
用于重塑数据结构。 | ||
df.reset_index() |
当需要将索引变回普通列时。 |
核心思想:Pandas 的核心是处理“整洁”的表格数据,即每个单元格都是标量值,当遇到嵌套结构时,你的首要任务就是通过上述方法将其“解嵌套”或“扁平化”,转换成整洁的表格格式,之后所有的分析、筛选、聚合操作都会变得异常简单。
