Pandas 数据透视表:像 Excel 一样汇总并重塑数据(指南) – Kanaries

Pandas 数据透视表:像 Excel 一样汇总并重塑数据(指南) – Kanaries

Pandas 数据透视表:像 Excel 一样汇总并重塑数据(指南)

NameSoren AtelierUpdated on 2026/2/12

每个用过 Excel 的分析师都熟悉数据透视表:把一个字段拖到“行”区域、另一个拖到“列”区域,选择一个汇总函数,原本密密麻麻的交易明细表就会变成清爽的汇总表——按类别求总计、按地区算平均值、按月份统计次数。现在你需要在 Python 里做同样的事,但用嵌套的 groupby 再配 unstack 写起来既笨拙又难读。

真正令人抓狂的是:当数据有多个分组层级、你需要小计/总计,或者你想对不同列同时应用多个聚合函数时,把 groupby、agg、unstack 一路链下去会产出脆弱的代码——数据形状一变就容易崩。

pandas 的 pivot_table 把 Excel 风格数据透视表的完整能力带到了 Python,并提供了干净、声明式的 API:一次函数调用就能处理分组、聚合、多级索引、小计/总计,以及缺失值处理。本指南会覆盖每个参数,带你走完实战示例,并把 pivot_table 与 groupby、pivot、crosstab 做对比,让你清楚知道该选哪一个工具。

📚

pd.pivot_table() 是做什么的

pd.pivot_table() 会从 DataFrame 生成一种“电子表格风格”的汇总表。你需要指定:哪些列作为行标签(index)、哪些列作为列标题(columns)、要汇总的数值列(values),以及要应用的聚合函数(aggfunc)。输出是一个新的 DataFrame,其中每个单元格都是一个汇总统计值。

下面是一个简单的前后对比:

原始数据:

regionproductrevenueNorthWidget1200NorthGadget800SouthWidget1500SouthGadget950NorthWidget1400SouthWidget1600

透视表(按 region 和 product 汇总 revenue 的 sum):

regionGadgetWidgetNorth8002600South9503100

每个单元格都包含该 region-product 组合的 revenue 总和。

pd.pivot_table() 语法与参数

pd.pivot_table(data, values=None, index=None, columns=None, aggfunc='mean',

fill_value=None, margins=False, dropna=True, margins_name='All',

observed=False, sort=True)

参数速查表

参数说明默认值data要汇总的 DataFrame必填values要聚合的列None(所有数值列)index用作行标签的列Nonecolumns用作列标题的列Noneaggfunc聚合函数:'mean'、'sum'、'count'、'min'、'max',或函数/dict/list'mean'fill_value用于替换结果中 NaN 的值Nonemargins添加行/列总计(小计/总计)Falsemargins_namemargins 行/列的名称'All'dropna排除所有条目均为 NaN 的列Trueobserved对分类(categorical)列只显示实际出现的类别Falsesort对结果排序True

所有示例使用的样例数据

下面每个示例都使用这份销售数据集:

import pandas as pd

import numpy as np

sales = pd.DataFrame({

'date': pd.to_datetime(['2025-01-15', '2025-01-20', '2025-02-10', '2025-02-18',

'2025-01-12', '2025-02-22', '2025-01-25', '2025-02-14',

'2025-01-30', '2025-02-05', '2025-01-18', '2025-02-28']),

'region': ['North', 'North', 'North', 'North', 'South', 'South',

'South', 'South', 'East', 'East', 'East', 'East'],

'product': ['Widget', 'Gadget', 'Widget', 'Gadget',

'Widget', 'Gadget', 'Widget', 'Gadget',

'Widget', 'Gadget', 'Widget', 'Gadget'],

'salesperson': ['Alice', 'Alice', 'Bob', 'Bob',

'Charlie', 'Charlie', 'Diana', 'Diana',

'Eve', 'Eve', 'Frank', 'Frank'],

'revenue': [1200, 800, 1400, 850, 1500, 950, 1100, 780, 1300, 900, 1250, 870],

'units': [10, 8, 12, 9, 15, 10, 11, 8, 13, 9, 12, 9]

})

print(sales)

基础透视表:按地区汇总收入总和

table = pd.pivot_table(sales, values='revenue', index='region', aggfunc='sum')

print(table)

输出:

revenue

region

East 4320

North 4250

South 4330

这等价于 sales.groupby('region')['revenue'].sum(),但会返回 DataFrame 而不是 Series。

添加列标题

添加 columns 参数,就能按第二个变量进一步拆分汇总结果:

table = pd.pivot_table(sales, values='revenue', index='region',

columns='product', aggfunc='sum')

print(table)

输出:

product Gadget Widget

region

East 1770 2550

North 1650 2600

South 1730 2600

现在每个单元格表示某个 region-product 组合的收入总和。

多个聚合函数

把函数列表传给 aggfunc,可以一次计算多个统计指标:

table = pd.pivot_table(sales, values='revenue', index='region',

columns='product', aggfunc=['sum', 'mean', 'count'])

print(table)

输出:

sum mean count

product Gadget Widget Gadget Widget Gadget Widget

region

East 1770 2550 885.0 1275.0 2 2

North 1650 2600 825.0 1300.0 2 2

South 1730 2600 865.0 1300.0 2 2

结果列会形成 MultiIndex:顶层是聚合函数,第二层是 product。

对不同列使用不同聚合函数

对 aggfunc 传入字典,就能对不同的 value 列应用不同的函数:

table = pd.pivot_table(sales, values=['revenue', 'units'], index='region',

columns='product',

aggfunc={'revenue': 'sum', 'units': 'mean'})

print(table)

输出:

revenue units

product Gadget Widget Gadget Widget

region

East 1770 2550 9.0 12.5

North 1650 2600 8.5 11.0

South 1730 2600 9.0 13.0

revenue 求和、units 求平均——这正是你在 Excel 透视表里会对不同字段选择不同“汇总方式”的做法。

使用 margins 添加小计/总计

margins 参数会添加一行和一列来显示总计:

table = pd.pivot_table(sales, values='revenue', index='region',

columns='product', aggfunc='sum', margins=True)

print(table)

输出:

product Gadget Widget All

region

East 1770 2550 4320

North 1650 2600 4250

South 1730 2600 4330

All 5150 7750 12900

All 行显示每个产品的总收入;All 列显示每个地区的总收入;右下角是总计中的总计(Grand Total)。

你也可以用 margins_name 自定义标签:

table = pd.pivot_table(sales, values='revenue', index='region',

columns='product', aggfunc='sum',

margins=True, margins_name='Total')

print(table)

用 fill_value 处理缺失值

当数据中不存在某些组合时,透视表会出现 NaN。用 fill_value 替换它们:

# Remove one row to create a missing combination

sales_missing = sales.drop(index=0)

table = pd.pivot_table(sales_missing, values='revenue', index='region',

columns='product', aggfunc='sum', fill_value=0)

print(table)

这会把 NaN 替换为 0,更利于展示,也能避免下游计算出错。

多级索引(行分组)

把列名列表传给 index,即可创建层级式的行标签:

table = pd.pivot_table(sales, values='revenue',

index=['region', 'salesperson'],

columns='product', aggfunc='sum')

print(table)

输出:

product Gadget Widget

region salesperson

East Eve 900.0 1300.0

Frank 870.0 1250.0

North Alice 800.0 1200.0

Bob 850.0 1400.0

South Charlie 950.0 1500.0

Diana 780.0 1100.0

每个 salesperson 会嵌套在其 region 下,提供一种“下钻”视图。

多级列

同样地,把列表传给 columns 可以创建层级式列标题:

sales['month'] = sales['date'].dt.month_name()

table = pd.pivot_table(sales, values='revenue', index='region',

columns=['product', 'month'], aggfunc='sum', fill_value=0)

print(table)

这样就会得到两级列头:顶层是 product,第二层是 month。

使用自定义聚合函数

你可以向 aggfunc 传入任何可调用对象,包括 lambda 和 NumPy 函数:

# Range (max - min) of revenue by region

table = pd.pivot_table(sales, values='revenue', index='region',

columns='product', aggfunc=lambda x: x.max() - x.min())

print(table)

输出:

product Gadget Widget

region

East 30 50

North 50 200

South 170 400

其他有用的自定义聚合示例:

# Coefficient of variation

table = pd.pivot_table(sales, values='revenue', index='region',

aggfunc=lambda x: x.std() / x.mean() * 100)

真实场景示例:学生成绩分析

students = pd.DataFrame({

'student': ['Alice', 'Alice', 'Alice', 'Bob', 'Bob', 'Bob',

'Charlie', 'Charlie', 'Charlie', 'Diana', 'Diana', 'Diana'],

'subject': ['Math', 'Science', 'English'] * 4,

'semester': ['Fall', 'Fall', 'Fall', 'Fall', 'Fall', 'Fall',

'Spring', 'Spring', 'Spring', 'Spring', 'Spring', 'Spring'],

'score': [92, 88, 95, 78, 85, 72, 90, 93, 88, 85, 79, 91]

})

# Average score by subject and semester

table = pd.pivot_table(students, values='score', index='subject',

columns='semester', aggfunc='mean', margins=True)

print(table.round(1))

输出:

semester Fall Spring All

subject

English 83.5 89.5 86.5

Math 85.0 87.5 86.2

Science 86.5 86.0 86.2

All 85.0 87.7 86.3

这能立刻看出 Spring 学期整体均值略高,其中 English 的提升最大。

真实场景示例:月度销售报表

# Create a monthly sales summary

sales['month'] = sales['date'].dt.strftime('%Y-%m')

report = pd.pivot_table(sales, values=['revenue', 'units'],

index='region', columns='month',

aggfunc={'revenue': 'sum', 'units': 'sum'},

margins=True, fill_value=0)

print(report)

这会生成和你在 Excel 里做的月度汇总报表类似的结果:按地区与月份汇总,并包含总计。

pivot_table vs pivot vs groupby vs crosstab

这四个函数的能力有重叠,但使用场景各不相同:

功能pivot_table()pivot()groupby()crosstab()聚合支持(任意函数)不支持(遇到重复会报错)支持(任意函数)支持(有限)处理重复键支持(通过 aggfunc)不支持支持支持小计/总计(margins)支持不支持不支持(需手动)支持填充缺失值支持(fill_value)不支持不支持支持(fill_value)输入DataFrameDataFrameDataFrameSeries/arrays输出DataFrameDataFrameDataFrame/SeriesDataFrame多级索引支持支持支持支持最适合带聚合的汇总分析唯一键值数据的重塑灵活的分组分析频数表默认函数meanN/AN/Acount

何时使用哪个

用 pivot_table():当你需要 Excel 风格的汇总(带聚合)、小计/总计、或多个聚合函数时。它通常是创建汇总表时最强且最易读的选择。

用 pivot():当 index 与 columns 的组合是唯一的,你只是想做重塑而不需要聚合时。它比 pivot_table() 更快,因为跳过了聚合步骤。

用 groupby():当你需要分组计算,但不需要宽表(wide-format)输出时。groupby 默认给你长表(long-format)结果。虽然 groupby().unstack() 能做出类似 pivot_table 的宽表,但 pivot_table 通常更直观。

用 crosstab():当你在做类别变量的频数表/交叉表时。crosstab() 可直接接收 Series 或数组(不局限于 DataFrame),且默认是计数。

等价示例

下面三段会产生相同结果:

# pivot_table approach

result1 = pd.pivot_table(sales, values='revenue', index='region',

columns='product', aggfunc='sum')

# groupby + unstack approach

result2 = sales.groupby(['region', 'product'])['revenue'].sum().unstack()

# Both produce the same table

print(result1.equals(result2)) # True

pivot_table 版本更易读,特别是当你要加 margins、填充缺失值或使用多个聚合函数时。

展平 MultiIndex 列

创建带多个聚合函数的透视表后,经常会得到不太好用的 MultiIndex 列:

table = pd.pivot_table(sales, values='revenue', index='region',

columns='product', aggfunc=['sum', 'mean'])

# Flatten the column MultiIndex

table.columns = ['_'.join(col).strip() for col in table.columns.values]

print(table)

输出:

sum_Gadget sum_Widget mean_Gadget mean_Widget

region

East 1770 2550 885.0 1275.0

North 1650 2600 825.0 1300.0

South 1730 2600 865.0 1300.0

现在列名变成了扁平字符串,更容易引用。

对透视表排序与过滤

透视表就是普通的 DataFrame,因此可以直接排序和过滤:

table = pd.pivot_table(sales, values='revenue', index='region',

columns='product', aggfunc='sum', margins=True)

# Sort by total revenue (All column), descending

sorted_table = table.sort_values('All', ascending=False)

print(sorted_table)

# Filter to show only regions with Widget revenue > 2500

filtered = table[table['Widget'] > 2500]

print(filtered)

导出透视表

把透视表保存到 Excel(干系人常常期望这个格式)或 CSV:

table = pd.pivot_table(sales, values='revenue', index='region',

columns='product', aggfunc='sum', margins=True)

# Export to Excel

table.to_excel('sales_pivot.xlsx', sheet_name='Revenue Summary')

# Export to CSV

table.to_csv('sales_pivot.csv')

性能建议

pivot_table() 内部会调用 groupby,所以性能表现类似。对于大数据集:

数据集规模预期耗时100K 行,2 个分组列~5 ms1M 行,2 个分组列~50 ms10M 行,3 个分组列~500 ms

优化策略:

在透视前先缩小数据 —— 在调用 pivot_table() 前先过滤行、只保留必要列。

使用 categorical dtype —— 把字符串列转成 category,分组会更快。

避免 lambda 聚合 —— 内置字符串函数名(如 'sum'、'mean')使用优化过的 C 代码;lambda 会回退到较慢的 Python 循环。

# Faster: use categorical dtypes

sales['region'] = sales['region'].astype('category')

sales['product'] = sales['product'].astype('category')

# Faster: use string name instead of lambda

table = pd.pivot_table(sales, values='revenue', index='region',

columns='product', aggfunc='sum') # 'sum' is optimized

用 PyGWalker 可视化透视表

虽然 pd.pivot_table() 很适合做数值汇总,但有时你需要交互式地可视化数据模式。PyGWalker (opens in a new tab) 是一个开源 Python 库,让你通过可视化的拖拽界面创建透视表、柱状图、热力图等——初始配置之后几乎不需要再写代码。

import pandas as pd

import pygwalker as pyg

sales = pd.DataFrame({

'region': ['North', 'North', 'South', 'South', 'East', 'East'] * 2,

'product': ['Widget', 'Gadget'] * 6,

'revenue': [1200, 800, 1500, 950, 1300, 900, 1400, 850, 1100, 780, 1250, 870],

'units': [10, 8, 15, 10, 13, 9, 12, 9, 11, 8, 12, 9]

})

# Launch interactive pivot table and visualization

walker = pyg.walk(sales)

PyGWalker 提供类似 Tableau 的界面:你可以把 region 拖到 rows,把 product 拖到 columns,把 revenue 拖到 values,直观地生成透视表。你还能在表格视图和图表视图间即时切换,尝试不同聚合函数,并导出结果——无需额外编写代码。

在 Google Colab (opens in a new tab)、Kaggle (opens in a new tab) 中试用 PyGWalker,或使用 pip install pygwalker 安装。

FAQ

pandas 里 pivot 和 pivot_table 有什么区别?

pivot() 只做数据重塑,不做聚合——它要求 index 与 columns 的组合必须唯一;如果存在重复组合会报错。pivot_table() 能通过 sum、mean 等函数对重复项进行聚合。简单重塑用 pivot();需要聚合或小计/总计时用 pivot_table()。

如何在 pandas 透视表中添加总计(margins)?

在 pivot_table() 调用中设置 margins=True:pd.pivot_table(df, values='revenue', index='region', columns='product', aggfunc='sum', margins=True)。这会添加一个名为 All 的行和列来显示小计/总计。也可以用 margins_name='Total' 自定义标签。

透视表可以使用多个聚合函数吗?

可以。向 aggfunc 传入列表:aggfunc=['sum', 'mean', 'count']。这会生成 MultiIndex 列头:一层是函数、一层是 value 列。你也可以传字典,对不同列应用不同函数:aggfunc={'revenue': 'sum', 'units': 'mean'}。

pivot_table 和 pandas 的 groupby 有什么不同?

两者都能做聚合,但 pivot_table() 会生成宽表(wide-format)结果(列标题来自某个分组变量),而 groupby() 默认产出长表(long-format)结果。pivot_table() 还原生支持 margins(小计/总计)与 fill_value(填充缺失值)。内部实现上,pivot_table() 会使用 groupby()。

如何处理透视表中的 NaN?

用 fill_value 参数把 NaN 替换为指定值:pd.pivot_table(df, ..., fill_value=0)。dropna=True(默认)会排除那些所有条目都为 NaN 的列。

可以生成百分比形式的透视表吗?

可以。先用计数或求和生成透视表,再除以总量即可。你也可以在 pd.crosstab() 里用 normalize 直接得到百分比交叉表。对透视表而言,通常手动计算百分比:table = table.div(table.sum(axis=1), axis=0) * 100。

总结

pandas 的 pivot_table() 是 Python 中创建汇总表最通用的工具。要点如下:

用 values、index、columns 定义汇总什么、如何分组行、如何分组列。

用 aggfunc 指定聚合函数;传列表实现多函数,传字典实现按列分别聚合。

用 margins=True 添加小计/总计——相当于 Excel 的 “Grand Total”。

用 fill_value 为缺失组合提供默认值(通常是 0)。

当你需要 margins 或 fill values 时,优先 pivot_table() 而不是 groupby().unstack(),可读性更好。

当数据的键组合唯一且不需要聚合时,优先 pivot() 而不是 pivot_table()。

做类别变量的简单频数表时,优先 crosstab()。

如果你想交互式探索透视表结果,PyGWalker (opens in a new tab) 提供了可视化拖拽界面,让你在 Jupyter Notebook 中获得接近 Excel 数据透视表的体验。

📚

相关推荐

破荒!秘鲁36年后斩世界杯首球 40年来首次领先
beat365在线下载

破荒!秘鲁36年后斩世界杯首球 40年来首次领先

📅 02-04 👁️ 6919
斑马智行使用教程,斑马系统怎么下载应用
mobile bt365体育投注

斑马智行使用教程,斑马系统怎么下载应用

📅 10-31 👁️ 1785
2019世界杯乒乓球女团决赛(2019世界杯乒乓球女团决赛完整视频回放)