Pandas 数据透视表

数据透视表是数据汇总分析的有力工具,可以让你从多个维度对数据进行聚合。Pandas 的 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() 的核心参数包括:

参数说明示例
dataDataFrame 数据源data=sales
values需要聚合的列名values='销售额'
index行索引的分组列(可多个)index='产品'index=['产品','区域']
columns列索引的分组列columns='区域'
aggfunc聚合函数,默认为 'mean'aggfunc='sum'aggfunc={'销售额':'sum','利润':'mean'}
fill_value替换缺失值的填充值fill_value=0
margins是否添加总计行/列,默认 Falsemargins=True
margins_name总计行列的名称,默认 'All'margins_name='总计'
dropna是否删除全为 NaN 的列,默认 Truedropna=False
observed分类类型时是否只显示实际出现的组合,默认 Falseobserved=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)

🔗 多级索引和列

indexcolumns 都可以接受列表,创建多层透视表。

# 行:产品 + 销售员,列:区域,值:销售额总和
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)

🧩 透视表与 groupby 的关系

实际上,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 的列是数值类型,否则聚合函数可能出错。
内存使用:indexcolumns 的组合非常多时,透视表可能会变得巨大,导致内存溢出。考虑先对数据过滤或使用 observed=True 限制组合。
多重聚合列名:使用 aggfunc 传入多个函数时,结果列会是 MultiIndex,后续操作需要留意。
与 crosstab 的区别:pd.crosstab() 专门用于计算频数交叉表,而 pivot_table 更通用。如果需要计数,两者均可。
最佳实践:
  • 先明确分析维度:行、列、值分别是什么。
  • 对于大型数据集,可以先使用 sample() 测试透视表结构,再应用到全量数据。
  • 善用 fill_valuemargins 让结果更易读。
  • 透视表的结果往往是报表的最终形态,可以直接导出到 Excel 或用于绘图。