数据重塑是将数据在"宽格式"和"长格式"之间转换。宽格式适合呈现,长格式适合分析。
pivot()— 旋转(无聚合)
将长表不经过聚合直接变成宽表。要求索引+列的组合唯一。
import pandas as pd
import numpy as np
df = pd.dataframe({
'date': ['2024-01', '2024-01', '2024-02', '2024-02'],
'city': ['北京', '上海', '北京', '上海'],
'temp': [5, 8, 10, 12]
})
print(df)
# date city temp
# 2024-01 北京 5
# 2024-01 上海 8
# 2024-02 北京 10
# 2024-02 上海 12
# pivot: 长 -> 宽
wide = df.pivot(index='date', columns='city', values='temp')
print(wide)
# city 北京 上海
# date
# 2024-01 5 8
# 2024-02 10 12
# 不指定 values: 对剩余所有列做 pivot
result = df.pivot(index='date', columns='city')
# ⚠️ 如果 index + columns 组合不唯一会报错!
# 如果有多余列,pivot 会用多层级列来容纳
pivot_table()— 数据透视表(带聚合)
当 index + columns 组合不唯一(即需要聚合)时使用。
df = pd.dataframe({
'date': ['2024-01', '2024-01', '2024-01', '2024-02', '2024-02', '2024-02'],
'city': ['北京', '北京', '上海', '北京', '北京', '上海'],
'product': ['a', 'b', 'a', 'a', 'b', 'b'],
'sales': [100, 150, 200, 110, 160, 220]
})
# 基本用法
result = df.pivot_table(
values='sales', # 要聚合的值
index='city', # 行索引
columns='product', # 列索引
aggfunc='sum', # 聚合函数(默认 'mean')
fill_value=0 # 用 0 填充 nan
)
print(result)
# product a b
# city
# 北京 210 310
# 上海 200 220
# 多个聚合函数
result = df.pivot_table(
values='sales',
index='city',
columns='product',
aggfunc=['sum', 'mean', 'count']
)
# 对多个值列使用不同聚合
result = df.pivot_table(
index='city',
aggfunc={'sales': 'sum', 'quantity': 'mean'}
)
# 多层索引
result = df.pivot_table(
values='sales',
index=['city', 'date'], # 多级行索引
columns='product',
aggfunc='sum'
)
pivot_table 完整参数
pd.pivot_table(
data,
values=none, # 要聚合的列
index=none, # 行分组键
columns=none, # 列分组键
aggfunc='mean', # 聚合函数: str / list / dict
fill_value=none, # 填充缺失值
margins=false, # 是否添加 'all' 汇总行/列
margins_name='all', # 汇总标签名
dropna=true, # 是否丢弃全为 nan 的列
observed=false, # 分类数据:是否只显示数据中出现的组
sort=true # 是否对结果排序
)
margins— 汇总行列
result = df.pivot_table(
values='sales',
index='city',
columns='product',
aggfunc='sum',
margins=true,
margins_name='合计'
)
# 添加了"合计"行和列
melt()— 宽 -> 长(逆透视)
将列名"融化"成行值,是 pivot 的逆操作。
df_wide = pd.dataframe({
'name': ['张三', '李四', '王五'],
'math': [85, 90, 78],
'english': [88, 82, 95],
'chinese': [92, 88, 85]
})
# 基本用法: 指定 id_vars(保持不变的列)
long = pd.melt(df_wide, id_vars=['name'])
# name variable value
# 0 张三 math 85
# 1 李四 math 90
# ...
# 7 李四 chinese 88
# 8 王五 chinese 85
# 自定义列名
long = pd.melt(
df_wide,
id_vars=['name'],
value_vars=['math', 'english', 'chinese'], # 指定要融化的列
var_name='科目', # 原列名变成的新列名
value_name='分数' # 原值变成的新列名
)
print(long)
# name 科目 分数
# 0 张三 math 85
# ...
melt 常见场景
# 只融化部分列 pd.melt(df, id_vars=['id', 'date'], value_vars=['sale', 'cost']) # 不指定 value_vars: 自动使用所有非 id_vars 列 pd.melt(df, id_vars=['id']) # 配合 groupby 做聚合 long = pd.melt(df, id_vars=['region'], value_vars=['q1', 'q2', 'q3', 'q4']) result = long.groupby(['region', 'variable'])['value'].sum()
stack()/unstack()— 层级旋转
操作的是 multiindex 的行/列层级。
stack()— 列层级 -> 行层级(变长)
# 创建有多层列的 dataframe
cols = pd.multiindex.from_product([['温度', '湿度'], ['北京', '上海']],
names=['指标', '城市'])
df = pd.dataframe(np.arange(12).reshape(3, 4),
index=['1月', '2月', '3月'],
columns=cols)
print(df)
# 指标 温度 湿度
# 城市 北京 上海 北京 上海
# 1月 0 1 2 3
# 2月 4 5 6 7
# 3月 8 9 10 11
# stack: 列层级 -> 行层级
stacked = df.stack() # 压缩最内层列到行
print(stacked)
# 指标 温度 湿度
# 1月 北京 0 2
# 上海 1 3
# 2月 北京 4 6
# ...
stacked = df.stack(level=0) # 压缩第 0 层列到行
stacked = df.stack(level='指标') # 按名称压缩
unstack()— 行层级 -> 列层级(变宽)
# 创建有 multiindex 的 series
idx = pd.multiindex.from_product([['1月', '2月'], ['北京', '上海']],
names=['月份', '城市'])
s = pd.series([5, 8, 10, 12], index=idx, name='温度')
print(s)
# 月份 城市
# 1月 北京 5
# 上海 8
# 2月 北京 10
# 上海 12
# unstack: 行层级 -> 列层级
wide = s.unstack() # 压缩最内层行到列
print(wide)
# 城市 北京 上海
# 月份
# 1月 5 8
# 2月 10 12
wide = s.unstack(level=0) # 压缩第 0 层
wide = s.unstack(level='月份') # 按名称压缩
wide = s.unstack(fill_value=0) # nan 填充为 0
使用场景对比
| 操作 | 方向 | 效果 |
|---|---|---|
| stack() | 列 → 行 | 数据变长、变窄 |
| unstack() | 行 → 列 | 数据变宽 |
| melt() | 列 → 行 | 将列名变为一列的值 |
| pivot() | 行 → 列 | 将一列的值变成列名 |
crosstab()— 交叉表/列联表
计算分组频率,类似 pivot_table(aggfunc='count'),但更简洁。
df = pd.dataframe({
'性别': ['男', '女', '女', '男', '女', '男', '男'],
'学历': ['本科', '硕士', '本科', '博士', '硕士', '本科', '硕士'],
'部门': ['技术', '销售', '技术', '技术', '销售', '销售', '技术']
})
# 基本交叉表
result = pd.crosstab(df['性别'], df['学历'])
print(result)
# 学历 博士 本科 硕士
# 性别
# 女 0 1 2
# 男 1 2 1
# 多列分组
result = pd.crosstab(df['性别'], [df['学历'], df['部门']])
# 归一化:百分比
pd.crosstab(df['性别'], df['学历'], normalize='all') # 整体占比
pd.crosstab(df['性别'], df['学历'], normalize='index') # 行占比
pd.crosstab(df['性别'], df['学历'], normalize='columns') # 列占比
# 带汇总
pd.crosstab(df['性别'], df['学历'], margins=true, margins_name='合计')
# 带值(而非计数)
df2 = pd.dataframe({
'性别': ['男', '女', '女', '男'],
'产品': ['a', 'a', 'b', 'b'],
'金额': [100, 200, 150, 180]
})
pd.crosstab(df2['性别'], df2['产品'],
values=df2['金额'], aggfunc='sum')
crosstab 完整参数
pd.crosstab(
index, # 行分组
columns, # 列分组
values=none, # 要聚合的值
aggfunc=none, # 聚合函数
rownames=none, # 行名称
colnames=none, # 列名称
margins=false, # 汇总
margins_name='all',
dropna=true, # 丢弃 nan 列
normalize=false # 归一化: 'all' / 'index' / 'columns'
)
格式转换速查
| 需求 | 方法 |
|---|---|
| 长→宽(无聚合,唯一键) | df.pivot(index, columns, values) |
| 长→宽(需要聚合) | df.pivot_table(values, index, columns, aggfunc) |
| 宽→长 | pd.melt(df, id_vars, var_name, value_name) |
| 列层级→行层级 | df.stack() |
| 行层级→列层级 | df.unstack() |
| 分组计数 | pd.crosstab(index, columns) |
| 行/列互换 | df.t 或 df.transpose() |
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论