pivot_table() 函数提供了类似 Excel 透视表的功能,并且更加灵活强大,本质上是 groupby 的多维拓展。
我们将使用一个销售订单数据集来演示透视表的各种用法:
import pandas as pd
import numpy as np
# 创建销售数据
sales = pd.DataFrame({
'日期': pd.date_range('2024-01-01', periods=20, freq='D'),
'产品': np.random.choice(['A', 'B', 'C'], 20),
'区域': np.random.choice(['华北', '华东', '华南'], 20),
'销售额': np.random.randint(100, 500, 20),
'利润': np.random.randint(20, 100, 20),
'销售员': np.random.choice(['张三', '李四', '王五'], 20)
})
print(sales.head(10))
pivot_table() 的核心参数包括:
| 参数 | 说明 | 示例 |
|---|---|---|
data | DataFrame 数据源 | data=sales |
values | 需要聚合的列名 | values='销售额' |
index | 行索引的分组列(可多个) | index='产品' 或 index=['产品','区域'] |
columns | 列索引的分组列 | columns='区域' |
aggfunc | 聚合函数,默认为 'mean' | aggfunc='sum' 或 aggfunc={'销售额':'sum','利润':'mean'} |
fill_value | 替换缺失值的填充值 | fill_value=0 |
margins | 是否添加总计行/列,默认 False | margins=True |
margins_name | 总计行列的名称,默认 'All' | margins_name='总计' |
dropna | 是否删除全为 NaN 的列,默认 True | dropna=False |
observed | 分类类型时是否只显示实际出现的组合,默认 False | observed=True |
最简单的透视表:指定行索引和聚合值。
# 按产品分组,计算平均销售额
pivot1 = pd.pivot_table(sales, values='销售额', index='产品')
print(pivot1)
# 按产品分组,计算销售额总和
pivot2 = pd.pivot_table(sales, values='销售额', index='产品', aggfunc='sum')
print(pivot2)
通过 columns 参数添加列分组,形成二维透视表。
# 产品为行,区域为列,聚合值为销售额总和
pivot3 = pd.pivot_table(sales,
values='销售额',
index='产品',
columns='区域',
aggfunc='sum')
print(pivot3)
可以同时对多个值字段进行聚合,也可以对不同字段使用不同聚合函数。
# 同时对销售额和利润求和
pivot4 = pd.pivot_table(sales,
values=['销售额', '利润'],
index='产品',
columns='区域',
aggfunc='sum')
print(pivot4)
# 对销售额求和,对利润求平均
pivot5 = pd.pivot_table(sales,
values=['销售额', '利润'],
index='产品',
columns='区域',
aggfunc={'销售额': 'sum', '利润': 'mean'})
print(pivot5)
使用 fill_value 填充透视表中的 NaN。
pivot6 = pd.pivot_table(sales,
values='销售额',
index='产品',
columns='区域',
aggfunc='sum',
fill_value=0)
print(pivot6)
margins=True 可以添加行总计和列总计,margins_name 自定义名称。
pivot7 = pd.pivot_table(sales,
values='销售额',
index='产品',
columns='区域',
aggfunc='sum',
margins=True,
margins_name='总计')
print(pivot7)
index 和 columns 都可以接受列表,创建多层透视表。
# 行:产品 + 销售员,列:区域,值:销售额总和
pivot8 = pd.pivot_table(sales,
values='销售额',
index=['产品', '销售员'],
columns='区域',
aggfunc='sum',
fill_value=0)
print(pivot8)
# 也可以对列使用多级
pivot9 = pd.pivot_table(sales,
values='销售额',
index='产品',
columns=['区域', '销售员'],
aggfunc='sum',
fill_value=0)
print(pivot9)
aggfunc 也可以传入自定义函数,或函数名的字符串。
# 自定义函数:计算范围
def price_range(x):
return x.max() - x.min()
pivot10 = pd.pivot_table(sales,
values='销售额',
index='产品',
columns='区域',
aggfunc=price_range,
fill_value=0)
print(pivot10)
# 多个聚合:既求和又计数
pivot11 = pd.pivot_table(sales,
values='销售额',
index='产品',
columns='区域',
aggfunc=['sum', 'count'],
fill_value=0)
print(pivot11)
实际上,pivot_table 底层就是通过 groupby 实现的。对于单层分组,两者等价;对于多层分组,pivot_table 更直观地展示二维表。
# 以下两种方式等价:
# 方式1:pivot_table
pt = pd.pivot_table(sales, values='销售额', index='产品', columns='区域', aggfunc='sum')
# 方式2:groupby + unstack
gb = sales.groupby(['产品', '区域'])['销售额'].sum().unstack(fill_value=0)
# 验证是否相等
print(pt.equals(gb)) # 可能 False 由于索引顺序,但值应该一致
import pandas as pd
import numpy as np
# 模拟电商销售数据
orders = pd.DataFrame({
'订单日期': pd.date_range('2024-01-01', periods=30, freq='D'),
'商品类别': np.random.choice(['电子产品', '服装', '家居'], 30),
'区域': np.random.choice(['北京', '上海', '广州', '深圳'], 30),
'销售额': np.random.randint(200, 1000, 30),
'利润': np.random.randint(20, 300, 30),
'客户等级': np.random.choice(['普通', '黄金', '铂金'], 30)
})
print("原始数据预览:")
print(orders.head())
# 需求:制作一张透视表,展示不同商品类别在不同区域的销售额总和,
# 并添加总计,缺失值填0,同时显示利润的平均值
pivot_final = pd.pivot_table(orders,
values=['销售额', '利润'],
index='商品类别',
columns='区域',
aggfunc={'销售额': 'sum', '利润': 'mean'},
fill_value=0,
margins=True,
margins_name='总计')
print("\n多维销售透视表:")
print(pivot_final)
# 可以进一步提取特定子集,比如只看电子产品
electronics = pivot_final.loc['电子产品', :]
print("\n电子产品区域销售情况:")
print(electronics)
values 的列是数值类型,否则聚合函数可能出错。
index 和 columns 的组合非常多时,透视表可能会变得巨大,导致内存溢出。考虑先对数据过滤或使用 observed=True 限制组合。
aggfunc 传入多个函数时,结果列会是 MultiIndex,后续操作需要留意。
pd.crosstab() 专门用于计算频数交叉表,而 pivot_table 更通用。如果需要计数,两者均可。
sample() 测试透视表结构,再应用到全量数据。fill_value 和 margins 让结果更易读。