使用Pandas Pivot Table生成多级子总计的教程

DDD
发布: 2025-12-02 08:55:00
原创
507人浏览过

使用pandas pivot table生成多级子总计的教程

本教程旨在详细阐述如何利用Pandas的pivot_table功能,结合CategoricalDtype和数据预处理技巧,生成包含所有列组合子总计的多级标题DataFrame。文章将逐步指导读者创建包含“all”类别(代表所有分组的总和)的复杂透视表,从而满足在数据分析中对多维度聚合和子总计的展示需求。

引言

在数据分析中,我们经常需要对数据进行多维度聚合,并同时查看各个维度的子总计。Pandas的pivot_table是一个强大的工具,但它默认只对现有数据进行聚合。当我们需要在透视表中为每个维度(例如,主题、动物、颜色)引入一个代表“所有类别”的总计列时,直接使用pivot_table会遇到挑战。本教程将介绍一种有效的方法,通过巧妙地结合分类数据类型(CategoricalDtype)和数据预处理,实现包含所有组合子总计的复杂多级透视表。

准备工作与问题阐述

假设我们有一个包含日期、主题、动物、颜色和数值的数据集,如下所示:

date subject animal colors value
Jan English cat blue 1
Feb Chemistry dog green 2

我们的目标是创建一个多级列标题的DataFrame,其中包含所有列(subject, animal, colors)的组合,并且每个维度都额外包含一个“all”类别,代表该维度下所有类别的总计。例如,如果subject有2个唯一值,animal有2个唯一值,colors有2个唯一值,那么最终的列组合将是 (2+1) * (2+1) * (2+1) = 27 种。

直接使用pivot_table难以实现这一点,因为它不会自动生成“all”类别并计算其总计。我们需要一种方法来在透视之前准备数据,使其包含这些“all”类别的聚合信息。

核心策略:分类数据类型与数据预处理

解决此问题的关键在于两步:

  1. 扩展分类类型: 将涉及聚合的列转换为Pandas的CategoricalDtype,并在其类别列表中手动添加一个特殊的“all”类别。
  2. 生成子总计数据: 通过对原始数据进行不同的分组聚合,创建包含“all”类别值的中间数据集,然后将这些数据集与原始数据合并。
  3. 最终透视: 使用pivot_table对预处理后的数据进行透视,并利用observed=False参数确保所有分类组合都被显示。

步骤一:转换列为分类类型并添加“all”类别

首先,我们需要导入必要的库并创建示例DataFrame。

import pandas as pd
from itertools import combinations

# 示例数据
data = {
    'date': ['Jan', 'Feb'],
    'subject': ['English', 'Chemistry'],
    'animal': ['cat', 'dog'],
    'colors': ['blue', 'green'],
    'value': [1, 2]
}
df = pd.DataFrame(data)

# 扩展DataFrame以模拟更多数据点,确保所有组合都有数据
# 否则,如果只有两个数据点,很多组合会是空的
df = pd.DataFrame({
    'date': ['Jan', 'Jan', 'Feb', 'Feb', 'Jan', 'Feb'],
    'subject': ['English', 'English', 'Chemistry', 'Chemistry', 'English', 'Chemistry'],
    'animal': ['cat', 'dog', 'cat', 'dog', 'cat', 'dog'],
    'colors': ['blue', 'green', 'blue', 'green', 'green', 'blue'],
    'value': [1, 2, 3, 4, 5, 6]
})

print("原始DataFrame:")
print(df)

# 定义需要转换为分类类型的列
cols_to_categorize = ['date', 'subject', 'animal', 'colors']

# 为每个列创建CategoricalDtype,并添加'all'类别
cats = {}
for col in cols_to_categorize:
    unique_values = list(df[col].unique())
    # 确保'all'类别排在最后,以便在透视表中显示时有序
    cats[col] = pd.CategoricalDtype(unique_values + ['all'], ordered=True)

df_categorized = df.astype(cats)

print("\n转换为分类类型并添加'all'类别后的DataFrame:")
print(df_categorized.dtypes)
登录后复制

解释:

LibLibAI
LibLibAI

国内领先的AI创意平台,以海量模型、低门槛操作与“创作-分享-商业化”生态,让小白与专业创作者都能高效实现图文乃至视频创意表达。

LibLibAI 159
查看详情 LibLibAI
  • 我们首先定义了需要进行分类处理的列。
  • 对于每个列,我们获取其所有唯一值,然后创建一个pd.CategoricalDtype,并将这些唯一值与字符串'all'一起作为其类别。ordered=True有助于在透视表中保持类别的顺序。
  • 将原始DataFrame的这些列转换为新定义的CategoricalDtype。

步骤二:生成包含“all”类别的子总计数据

这一步是实现子总计的关键。我们需要创建一系列新的DataFrame,每个DataFrame都代表一个特定分组下的“all”总计。例如,一个DataFrame可能包含所有subject和animal组合下,所有colors的总计。

# 存储所有子总计的列表
data_for_pivot = [df_categorized.copy()] # 包含原始数据

# 遍历所有可能的组合,生成子总计
# 例如,当r=len(cols_to_categorize)-1时,意味着一个维度是'all'
# 当r=len(cols_to_categorize)-2时,意味着两个维度是'all',以此类推
# 我们可以通过迭代r从1到len(cols_to_categorize)来生成所有级别的子总计
for r in range(len(cols_to_categorize)):
    # print(f"\n生成 {len(cols_to_categorize)-r} 个维度是'all'的子总计:")
    for grp_cols in combinations(cols_to_categorize, r=r):
        # 确定哪些列将作为分组键,哪些列将是'all'
        grouping_keys = list(grp_cols)
        all_cols = [col for col in cols_to_categorize if col not in grouping_keys]

        if not grouping_keys: # 如果没有分组键,表示计算所有数据的总计
            subtotal_df = df_categorized['value'].agg(['sum']).reset_index()
            subtotal_df = subtotal_df.rename(columns={'sum': 'value'})
            for col in cols_to_categorize:
                subtotal_df[col] = 'all'
        else:
            # 计算当前分组键下的值总和
            subtotal_df = df_categorized.groupby(grouping_keys, as_index=False, observed=True)['value'].sum()
            # 将非分组键的列填充为'all'
            for col in all_cols:
                subtotal_df[col] = 'all'

        # 确保subtotal_df的列顺序和类型与原始df_categorized一致
        for col in cols_to_categorize:
            if col not in subtotal_df.columns:
                subtotal_df[col] = 'all' # 如果某个维度完全是'all',则添加该列并赋值'all'
            subtotal_df[col] = subtotal_df[col].astype(cats[col])

        data_for_pivot.append(subtotal_df[cols_to_categorize + ['value']])

# 合并所有数据和子总计
out_df = pd.concat(data_for_pivot, ignore_index=True)

# 再次确保所有列都是正确的CategoricalDtype
for col in cols_to_categorize:
    out_df[col] = out_df[col].astype(cats[col])

print("\n包含所有子总计的预处理DataFrame:")
print(out_df)
print(out_df.dtypes)
登录后复制

解释:

  • 我们初始化一个列表data_for_pivot,将原始的df_categorized(已包含'all'分类但尚未填充'all'值)放入其中。
  • 我们使用itertools.combinations来遍历所有可能的列组合作为groupby的键。
    • r=0时,grouping_keys为空,这意味着计算整个数据集的总和,所有分类列都标记为'all'。
    • r=1时,grouping_keys包含一个列,例如['date'],其他列(subject, animal, colors)将被标记为'all'。
    • 依此类推,直到r=len(cols_to_categorize),此时grouping_keys包含所有列,这实际上是原始数据,但我们已经将其包含在data_for_pivot的初始副本中。
  • 对于每次分组聚合,我们计算value的sum。
  • 然后,将那些未被用于分组的列填充为'all'。
  • 最后,将所有这些子总计DataFrame与原始数据合并成一个大的out_df。这个out_df现在包含了所有粒度级别的数据,以及各种组合的子总计,其中子总计行在相应的维度列中标记为'all'。

步骤三:使用pivot_table生成最终报表

现在,我们有了包含所有粒度和子总计信息的out_df,可以使用pivot_table来构建最终的多级报表。

# 使用pivot_table进行最终透视
# index: 作为行索引的列
# columns: 作为列索引的列,将形成多级标题
# values: 聚合的数值列
# aggfunc: 聚合函数,例如'sum', 'median', 'mean'等
# fill_value: 填充没有数据点的单元格的值
# observed=False: 确保pivot_table显示所有CategoricalDtype的类别,包括'all',即使它们在数据中没有实际出现
final_pivot_table = out_df.pivot_table(
    index='date', 
    columns=['subject', 'animal', 'colors'], 
    values='value', 
    aggfunc='sum', # 聚合函数可以根据需求选择,例如'median'
    fill_value=0,  # 填充没有数据的组合,通常用0或NaN
    observed=False # 关键参数,确保显示所有分类,包括'all'
)

print("\n最终的多级子总计透视表:")
print(final_pivot_table)
登录后复制

解释:

  • index='date':date列将作为行索引。
  • columns=['subject', 'animal', 'colors']:这三列将形成多级列标题。
  • values='value':value列是我们要聚合的数值。
  • aggfunc='sum':这里我们使用sum作为聚合函数。如果需要,可以更改为median或其他函数。
  • fill_value=0:对于数据中不存在的组合,其单元格将被0填充。
  • observed=False:这是至关重要的参数。当列是CategoricalDtype时,observed=False会强制pivot_table显示所有可能的类别组合,即使某些组合在out_df中没有直接的行。结合我们之前在CategoricalDtype中添加的'all'类别,这将确保所有包含'all'的子总计列都会被创建并显示。

结果分析与注意事项

生成的final_pivot_table将是一个具有多级列标题的DataFrame,其中包含所有subject、animal、colors的组合,以及它们各自的“all”子总计。行索引也将包含date的类别及其“all”总计(如果date也被处理为分类并包含'all')。

注意事项:

  • 性能: 当原始数据量和分类列的唯一值非常多时,生成所有组合的子总计数据可能会消耗大量内存和计算资源。请根据实际数据规模评估此方法的适用性。
  • 聚合函数: 在本例中,我们使用了sum。如果需要计算median或mean等其他聚合函数,请确保在生成子总计数据(步骤二)和最终透视(步骤三)时都使用相应的函数。特别是对于median,直接对包含'all'的行计算中位数可能需要更复杂的逻辑,因为'all'本身不是一个数值。
  • 类别顺序: 通过在CategoricalDtype中设置ordered=True并控制'all'的位置,可以影响透视表中列的显示顺序。
  • fill_value: 在pivot_table中使用fill_value来处理数据中不存在的组合。
  • observed=False: 这是确保pivot_table显示所有分类组合(包括'all')的关键。如果设置为True(默认值),则只会显示out_df中实际出现的类别组合。

总结

通过以上步骤,我们成功地利用Pandas的CategoricalDtype、itertools.combinations进行数据预处理,并结合pivot_table的observed=False参数,创建了一个功能强大的多级子总计透视表。这种方法使得在复杂的业务分析中,能够清晰地展示不同维度下的聚合数据及其总计,极大地提升了数据分析的灵活性和洞察力。

以上就是使用Pandas Pivot Table生成多级子总计的教程的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号