杰瑞科技汇

python dataframe嵌套

什么是 DataFrame 中的“嵌套”?

在 Pandas 中,“嵌套”通常指以下几种情况:

python dataframe嵌套-图1
(图片来源网络,侵删)
  1. 列中包含嵌套的数据结构:最常见的情况是 DataFrame 的某一列,其单元格本身不是一个简单的标量值(如数字、字符串),而是一个列表、字典、元组或其他嵌套结构。
  2. 多级索引:这是一种结构上的“嵌套”,行或列索引本身具有层级关系。
  3. 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 和原来的 idname 列合并起来。

# 使用 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

完美!嵌套问题解决了。

使用 applypd.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

如何操作多级索引?

  1. 访问数据

    • 使用 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'))
  2. 取消分层: 如果觉得多级索引太复杂,可以随时取消它。

    # 将列索引堆叠成新的行索引
    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 的核心是处理“整洁”的表格数据,即每个单元格都是标量值,当遇到嵌套结构时,你的首要任务就是通过上述方法将其“解嵌套”或“扁平化”,转换成整洁的表格格式,之后所有的分析、筛选、聚合操作都会变得异常简单。

分享:
扫描分享到社交APP
上一篇
下一篇