Pandas 数据处理进阶:缺失值、合并、分组聚合与透视表
在完成 pandas 的基础操作(索引、筛选、赋值、函数应用)之后,下一步便是处理真实数据中常见的问题:缺失值、多表合并、分组统计以及数据透视。本文带你系统掌握这些核心技能,并提供可直接运行的代码示例。
目录
- 数据清洗:缺失值处理
- 1.1 检测缺失值
- 1.2 删除缺失值
- 1.3 填充缺失值
- 1.4 自定义替换为 NaN
- [数据合并:concat 与 merge](#数据合并:concat 与 merge)
- 2.1 concat:轴向拼接
- 2.2 merge:键值关联
- 2.3 join:基于索引的合并
- [分组与聚合:groupby 的威力](#分组与聚合:groupby 的威力)
- 3.1 基础分组
- 3.2 多列分组
- 3.3 聚合函数 agg
- 3.4 过滤与变换
- 透视表与交叉表
- 4.1 交叉表 crosstab:频数统计
- 4.2 透视表 pivot_table:灵活聚合
- 4.3 多级透视
- 综合实战:电商订单数据分析
- 小结与进阶建议
1. 数据清洗:缺失值处理
真实数据往往不完整,pandas 提供了丰富的缺失值处理工具。
1.1 检测缺失值
isna()/isnull():返回布尔型 DataFrame,True 表示缺失。isna().any(axis=?):检查每列/行是否有缺失。isna().sum():统计每列缺失数量。
python
import pandas as pd
import numpy as np
df = pd.DataFrame({
'A': [1, 2, np.nan, 4],
'B': [5, np.nan, np.nan, 8],
'C': [9, 10, 11, 12]
})
print(df.isna())
print(df.isna().any()) # 每列是否有缺失
print(df.isna().sum()) # 每列缺失个数
1.2 删除缺失值:dropna
python
# 删除任何含有缺失值的行
df_clean = df.dropna()
# 删除任何含有缺失值的列
df_clean = df.dropna(axis=1)
# 仅当某列全部缺失时才删除
df_clean = df.dropna(how='all')
# 指定 subset:仅检查特定列
df_clean = df.dropna(subset=['A', 'B'])
1.3 填充缺失值:fillna
python
# 用常数填充
df_filled = df.fillna(0)
# 用前一个值填充(向前填充)
df_filled = df.fillna(method='ffill')
# 用后一个值填充
df_filled = df.fillna(method='bfill')
# 针对不同列用不同值
df_filled = df.fillna({'A': 0, 'B': df['B'].mean()})
1.4 自定义替换为 NaN
某些数据中,特殊值(如 "N/A"、-1、0)应视为缺失。
python
df = df.replace({'N/A': np.nan, -1: np.nan, 0: np.nan})
# 或原地修改
df.replace({'N/A': np.nan, 0: np.nan}, inplace=True)
2. 数据合并:concat 与 merge
2.1 concat:轴向拼接
axis=0:纵向拼接(上下堆叠),要求列名一致。axis=1:横向拼接(左右并排),要求行索引对齐。ignore_index=True:重置索引。
python
df1 = pd.DataFrame({'A': [1, 2], 'B': [3, 4]})
df2 = pd.DataFrame({'A': [5, 6], 'B': [7, 8]})
# 纵向拼接
df_vertical = pd.concat([df1, df2], axis=0, ignore_index=True)
# 横向拼接
df_horizontal = pd.concat([df1, df2], axis=1)
2.2 merge:键值关联(类似 SQL JOIN)
python
left = pd.DataFrame({'key': ['X', 'Y', 'Z'], 'value_left': [1, 2, 3]})
right = pd.DataFrame({'key': ['X', 'Y', 'W'], 'value_right': [4, 5, 6]})
# 内连接(默认)
inner = pd.merge(left, right, on='key', how='inner')
# 左连接
left_join = pd.merge(left, right, on='key', how='left')
# 右连接
right_join = pd.merge(left, right, on='key', how='right')
# 外连接(全连接)
outer = pd.merge(left, right, on='key', how='outer')
# 多列关联:on=['col1', 'col2']
# 处理重复列名:suffixes=('_x', '_y')
2.3 join:基于索引的合并
python
left = pd.DataFrame({'A': [1, 2]}, index=['a', 'b'])
right = pd.DataFrame({'B': [3, 4]}, index=['a', 'c'])
result = left.join(right, how='left') # 默认左连接,基于索引
3. 分组与聚合:groupby 的威力
分组聚合是数据分析的核心范式:split-apply-combine。
3.1 单字段分组
python
df = pd.DataFrame({
'类别': ['A', 'A', 'B', 'B', 'C'],
'销售额': [100, 150, 200, 250, 300],
'成本': [60, 80, 120, 140, 180]
})
grouped = df.groupby('类别')
print(grouped['销售额'].mean()) # 每类平均销售额
print(grouped['销售额'].sum())
3.2 多字段分组
python
df['城市'] = ['北京', '上海', '北京', '上海', '北京']
grouped_multi = df.groupby(['类别', '城市'])
print(grouped_multi['销售额'].sum())
3.3 聚合函数 agg
- 单列单函数:
.agg({'列名': 'mean'}) - 单列多函数:
.agg({'销售额': ['sum', 'mean', 'count']}) - 多列不同函数:
.agg({'销售额': 'sum', '成本': 'mean'}) - 自定义函数:
.agg(lambda x: x.max() - x.min())
python
result = df.groupby('类别').agg({
'销售额': ['sum', 'mean'],
'成本': 'min'
})
print(result)
3.4 过滤与变换
filter:根据分组条件过滤组(保留满足条件的整个组)。transform:将聚合结果广播回原始行,常用于添加组内统计列。
python
# 保留销售额总和大于 400 的组
filtered = df.groupby('类别').filter(lambda x: x['销售额'].sum() > 400)
# 添加组内平均值列
df['销售额_组内均指'] = df.groupby('类别')['销售额'].transform('mean')
4. 透视表与交叉表
4.1 交叉表 crosstab:频数统计
pd.crosstab 用于计算两个(或多个)分类变量的频数分布。
python
df = pd.DataFrame({
'性别': ['男', '女', '男', '女', '男'],
'城市': ['北京', '上海', '北京', '北京', '上海'],
'购买次数': [1, 3, 2, 1, 4]
})
# 性别 vs 城市的频数表
ct = pd.crosstab(df['性别'], df['城市'])
print(ct)
# 添加行/列总计
ct_margins = pd.crosstab(df['性别'], df['城市'], margins=True, margins_name='总计')
4.2 透视表 pivot_table:灵活聚合
类似 Excel 数据透视表,可指定行、列、值、聚合函数。
python
# 按性别和城市,统计购买次数的平均值
pt = pd.pivot_table(df,
values='购买次数',
index='性别',
columns='城市',
aggfunc='mean')
print(pt)
# 多个聚合函数
pt_multi = pd.pivot_table(df,
values='购买次数',
index='性别',
columns='城市',
aggfunc=['sum', 'count'])
4.3 多级 index / 多 values
python
# 多级行索引
pt_levels = pd.pivot_table(df,
values='购买次数',
index=['性别', '城市'],
aggfunc='sum')
# 多 values 列
pt_multi_values = pd.pivot_table(df,
values=['购买次数', '性别'],
index='城市',
aggfunc='count')
5. 综合实战:电商订单数据分析
模拟一个电商订单数据集,完成从清洗到聚合的全流程。
python
import pandas as pd
import numpy as np
# 创建订单数据
orders = pd.DataFrame({
'order_id': range(1, 11),
'user_id': [101, 102, 101, 103, 104, 101, 102, 105, 103, 106],
'category': ['电子产品', '服装', '电子产品', '食品', '服装', '电子产品', '服装', '食品', '食品', '电子产品'],
'amount': [299, 89, 450, 35, 120, 310, 150, 45, 60, 520],
'discount': [0.1, 0.0, 0.15, 0.0, 0.05, 0.1, 0.0, 0.0, 0.05, 0.2]
})
# 故意引入一个缺失值
orders.loc[5, 'amount'] = np.nan
print("原始数据:")
print(orders)
# 1. 缺失值处理:用该品类平均金额填充
category_avg = orders.groupby('category')['amount'].transform('mean')
orders['amount'].fillna(category_avg, inplace=True)
# 2. 计算实际支付金额(金额 * (1 - 折扣))
orders['paid'] = orders['amount'] * (1 - orders['discount'])
# 3. 分组聚合:每用户的总支付金额和订单数
user_stats = orders.groupby('user_id').agg(
总支付=('paid', 'sum'),
订单数=('order_id', 'count')
).reset_index()
print("\n用户统计:")
print(user_stats)
# 4. 透视表:各类别在不同折扣区间的平均支付金额
orders['discount_bin'] = pd.cut(orders['discount'], bins=[-0.01, 0.05, 0.15, 0.21], labels=['低折扣', '中折扣', '高折扣'])
pt = pd.pivot_table(orders, values='paid', index='category', columns='discount_bin', aggfunc='mean')
print("\n透视表(品类×折扣区间 平均支付):")
print(pt)
# 5. 交叉表:用户 vs 品类购买频数
ct = pd.crosstab(orders['user_id'], orders['category'])
print("\n用户购买品类频次:")
print(ct)
6. 小结与进阶建议
本文系统介绍了 pandas 中四个核心进阶功能:
| 功能 | 核心方法 | 典型场景 |
|---|---|---|
| 缺失值处理 | isna(), dropna(), fillna() |
数据清洗 |
| 数据合并 | concat(), merge(), join() |
多表关联 |
| 分组聚合 | groupby(), agg(), transform(), filter() |
统计分析 |
| 透视表/交叉表 | pivot_table(), crosstab() |
多维汇总 |
经验法则:
- 处理缺失时,优先分析缺失原因,合理填充或删除。
- 合并数据前确保关联键的数据类型一致。
- 分组聚合时使用
agg一次完成多个统计,代码更清晰。 - 透视表是快速生成报表的利器,尤其是多维度交叉分析。
进阶方向:
- 时间序列数据的重采样(
resample) - 窗口函数(
rolling,expanding) - 性能优化:使用
eval()和query()处理大数据 - 结合
matplotlib/seaborn做可视化分析
建议读者将本文代码逐一运行,并尝试修改参数观察结果。掌握这些技能,你就能应对绝大多数数据处理需求。