这是系列的最后一篇。前半部分讲 pandas 的性能优化技巧(向量化、内存优化、大数据处理),后半部分用一个完整的实战项目把前面 15 章的知识串起来:从读数据、清洗、分析到可视化出报告。
目录
1. 性能优化总原则
2. 向量化优先
3. 内存优化
4. 使用 category 与高效数据类型
5. 并行与大数据方案
6. 常见慢代码排查清单
7. 实战项目:电商销售数据分析报告
8. 写在最后:进阶学习路线
1. 性能优化总原则
pandas 性能优化的核心是减少 Python 层面的循环,尽量让运算发生在底层(C/NumPy 向量化)。
速度:向量化运算 > map/内置方法 > apply > 显式 for 循环
| 手段 | 相对速度 | 说明 |
|------|----------|------|
| 向量化(+ - * / >、内置方法) | ⭐⭐⭐⭐⭐ | 首选,没有之一 |
| 字典 map / replace | ⭐⭐⭐⭐ | 映射首选 |
| apply(逐列) | ⭐⭐⭐ | 复杂逻辑兜底 |
| apply(逐行 axis=1) | ⭐⭐ | 尽量避免 |
| for 循环 + loc 逐行 | ⭐ | 千万行数据会慢到无法接受 |
2. 向量化优先
2.1 用向量化代替循环
import pandas as pd
import numpy as np
import time
n = 1_000_000
df = pd.DataFrame({"a": np.random.rand(n), "b": np.random.rand(n)})
# ❌ 慢:循环逐行
def slow():
result = []
for i in range(n):
if df.loc[i, "a"] > 0.5:
result.append(df.loc[i, "a"] * df.loc[i, "b"])
else:
result.append(0)
df["c_slow"] = result
# ✅ 快:向量化 + where/np.where
def fast():
df["c_fast"] = np.where(df["a"] > 0.5, df["a"] * df["b"], 0)
t0 = time.time(); fast()
print(f"向量化: {time.time() - t0:.3f}s")
2.2 条件运算的向量化写法
# np.where(三目运算)
df["c"] = np.where(df["a"] > 0.5, "高", "低")
# 多条件
df["grade"] = np.select(
[df["a"] > 0.8, df["a"] > 0.5, df["a"] > 0.2], # 条件列表
["A", "B", "C"], # 对应值
default="D",
)
# 直接布尔运算(无需循环)
df["高且b大"] = (df["a"] > 0.5) & (df["b"] > 0.5)
2.3 用内置方法代替自定义函数
# ❌ 慢:apply 自定义
# df["x"] = df["col"].apply(lambda v: v * 2)
# ✅ 快:向量化
df["x"] = df["col"] * 2
# ❌ 慢:apply 算排名
# df["rank"] = df["col"].apply(lambda v: (df["col"] > v).sum())
# ✅ 快:内置 rank
df["rank"] = df["col"].rank()
# 用 str 方法代替 apply + re
# ❌ df["email"] = df["text"].apply(lambda s: re.findall(...))
# ✅ df["email"] = df["text"].str.extract(r"([\w.]+@[\w.]+)")
3. 内存优化
3.1 降低数值类型宽度
df = pd.DataFrame({"x": np.random.randint(0, 100, 1_000_000)})
# 查看内存
print(df.memory_usage(deep=True))
# x 8000000
# int64 -> int32 -> int16 逐级压缩
df["x"] = df["x"].astype("int16")
print(df.memory_usage(deep=True))
# x 2000000
一个自动优化函数:
def optimize_numeric(df):
"""把数值列压到最小合适类型"""
for col in df.select_dtypes(include=["int64", "float64"]).columns:
c_min, c_max = df[col].min(), df[col].max()
if str(df[col].dtype) == "int64":
if c_min >= -2**7 and c_max <= 2**7 - 1:
df[col] = df[col].astype("int8")
elif c_min >= -2**15 and c_max <= 2**15 - 1:
df[col] = df[col].astype("int16")
elif c_min >= -2**31 and c_max <= 2**31 - 1:
df[col] = df[col].astype("int32")
else:
df[col] = df[col].astype("float32")
return df
3.2 只读需要的列
# 读取时只拿需要的列(省内存 + 省时间)
df = pd.read_csv("big.csv", usecols=["id", "金额"])
3.3 及时释放内存
import gc
del df # 删除引用
gc.collect() # 触发垃圾回收
# 或者用 copy-on-write 语义下重新赋值
df = df[df["a"] > 0] # 旧对象自然被回收
4. 使用 category 与高效数据类型
n = 1_000_000
df = pd.DataFrame({"city": np.random.choice(["北京", "上海", "广州", "深圳"], n)})
# object 内存
print(df.memory_usage(deep=True))
# city 8000064
# category 内存(少量类别时大幅压缩)
df["city"] = df["city"].astype("category")
print(df.memory_usage(deep=True))
# city 1000438 (约 1/8)
适合转 category 的场景:
- 分类数远小于行数(如性别、城市、状态码);
- 需要按分类做 groupby 或 value_counts。
注意事项:分类数接近行数(如 ID 列)时转 category 反而更占内存,不要盲目转。
# string 类型对文本更友好
df["code"] = df["code"].astype("string")
5. 并行与大数据方案
当单机 pandas 内存不够时,按数据量从小到大依次考虑:
| 方案 | 适合 | 工具 |
|------|------|------|
| 分块读取 + 聚合 | 单文件几 GB | pd.read_csv(chunksize=) |
| 列式存储 | 反复读取同一数据 | to_parquet |
| Polars | 想要更快 | pip install polars(API 兼容) |
| Dask | 超过内存 | dask.dataframe |
| 数据库 | 稳定查询 | SQLite/PostgreSQL + read_sql |
# 分块聚合示例
chunks = pd.read_csv("big.csv", chunksize=100_000)
total = 0
for chunk in chunks:
total += chunk["金额"].sum()
print(total)
# 分块 + groupby 聚合
from collections import defaultdict
agg = defaultdict(float)
for chunk in pd.read_csv("big.csv", chunksize=100_000):
for k, v in chunk.groupby("城市")["金额"].sum().items():
agg[k] += v
print(dict(agg))
6. 常见慢代码排查清单
| 症状 | 常见原因 | 修复 |
|------|----------|------|
| 数据加载慢 | 全列读取、未指定 dtype | usecols、dtype、parse_dates |
| 逐行处理慢 | for + loc | 向量化、np.where、apply |
| apply 极慢 | axis=1 大表逐行 | 尽量向量化或分组处理 |
| groupby 慢 | 未用内置 agg | agg("sum") 代替 apply(lambda) |
| 字符串处理慢 | 正则逐条 | .str 向量化方法 |
| 内存爆 | object 列多、int64 宽 | category + 降位 + 删列 |
| 拼接慢 | 循环里 concat 小表 | 先收集列表,最后一次性 concat |
经典错误示范与修复:
# ❌ 循环里拼接(极慢)
# for f in files:
# df = pd.concat([df, pd.read_csv(f)])
# ✅ 先收集再一次性拼接(快)
frames = [pd.read_csv(f) for f in files]
df = pd.concat(frames, ignore_index=True)
# ❌ groupby 用 apply lambda
# df.groupby("g")["v"].apply(lambda x: x.sum())
# ✅ 内置 agg
df.groupby("g")["v"].agg("sum")
7. 实战项目:电商销售数据分析报告
把前 15 章的知识全部用上,完成一个端到端的数据分析项目。
7.1 数据准备(模拟)
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams["font.sans-serif"] = ["SimHei", "Microsoft YaHei"]
plt.rcParams["axes.unicode_minus"] = False
np.random.seed(2025)
# 模拟 5000 条订单
n = 5000
orders = pd.DataFrame({
"订单号": [f"O{i:06d}" for i in range(1, n + 1)],
"日期": np.random.choice(pd.date_range("2025-01-01", "2025-06-30"), n),
"区域": np.random.choice(["华东", "华南", "华北", "西南", "东北"], n, p=[0.3, 0.25, 0.2, 0.15, 0.1]),
"品类": np.random.choice(["数码", "服装", "食品", "家居", "美妆"], n),
"数量": np.random.randint(1, 10, n),
"单价": np.random.choice([99, 199, 299, 499, 799, 1299], n),
"客户评分": np.random.randint(1, 6, n),
})
orders.loc[np.random.choice(n, 50), "客户评分"] = np.nan # 制造缺失
orders = orders.sort_values("日期").reset_index(drop=True)
7.2 数据清洗与探索
print("=== 基本信息 ===")
print(orders.shape)
print(orders.info())
print("\n=== 缺失值 ===")
print(orders.isna().sum())
# 处理缺失:客户评分用中位数填充
orders["客户评分"] = orders["客户评分"].fillna(orders["客户评分"].median())
# 新增金额列
orders["金额"] = orders["数量"] * orders["单价"]
# 提取月份、周几
orders["月份"] = orders["日期"].dt.month
orders["周几"] = orders["日期"].dt.day_name()
7.3 业务分析
# 1. 月度销售趋势
monthly = orders.groupby("月份")["金额"].sum()
print("=== 月度销售 ===")
print(monthly)
# 2. 区域销售占比
area = orders.groupby("区域")["金额"].sum().sort_values(ascending=False)
print("\n=== 区域销售 ===")
print(area)
# 3. 品类销量与销售额
cat = orders.groupby("品类").agg(
订单数=("订单号", "count"),
总销量=("数量", "sum"),
总金额=("金额", "sum"),
).sort_values("总金额", ascending=False)
print("\n=== 品类分析 ===")
print(cat)
# 4. 客单价(平均每单金额)
avg_order = orders["金额"].mean()
print(f"\n客单价: {avg_order:.2f}")
# 5. 客户满意度(平均评分)与金额关系
score_amount = orders.groupby("客户评分")["金额"].mean()
print("\n=== 评分与金额 ===")
print(score_amount)
7.4 可视化报告
fig, axes = plt.subplots(2, 2, figsize=(14, 9))
# 月度趋势
monthly.plot(kind="line", marker="o", ax=axes[0, 0], title="月度销售额趋势", color="#2E86AB")
# 区域占比
area.plot(kind="pie", autopct="%.1f%%", ax=axes[0, 1], title="区域销售占比")
axes[0, 1].set_ylabel("")
# 品类对比
cat["总金额"].plot(kind="bar", ax=axes[1, 0], title="品类销售额", color="steelblue")
axes[1, 0].set_xticklabels(axes[1, 0].get_xticklabels(), rotation=0)
# 周几表现
orders.groupby("周几")["金额"].sum().reindex(
["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
).plot(kind="bar", ax=axes[1, 1], title="每周各天销售", color="coral")
axes[1, 1].set_xticklabels(axes[1, 1].get_xticklabels(), rotation=45)
plt.tight_layout()
plt.savefig("销售分析报告.png", dpi=150)
plt.show()
7.5 输出结论要点(示例)
=== 结论示例 ===
1. 销售整体呈现上升趋势,6 月达到峰值;
2. 华东区域贡献约 30% 销售额,是核心市场;
3. 数码品类销售额最高,其次为家居;
4. 客单价约 X 元,评分 4-5 分的订单金额显著更高,说明好评客户消费力强;
5. 周末(周六、周日)销售额明显高于工作日。
(上述数字请以实际运行结果为准)
7.6 导出结果
# 汇总表导出
with pd.ExcelWriter("销售分析结果.xlsx") as writer:
monthly.to_frame("销售额").to_excel(writer, sheet_name="月度")
area.to_frame("销售额").to_excel(writer, sheet_name="区域")
cat.to_excel(writer, sheet_name="品类")
orders.head(1000).to_excel(writer, sheet_name="明细", index=False)
8. 写在最后:进阶学习路线
到这里,你已经掌握了 pandas 的核心知识全貌。想继续进阶,建议按此路线:
8.1 掌握程度自测
| 模块 | 自测问题 |
|------|----------|
| 数据结构 | 能说清 Series 与 DataFrame 区别吗? |
| 索引 | loc/iloc/布尔索引随手就用吗? |
| 清洗 | 缺失值、重复值、类型转换熟练吗? |
| 变换 | map/apply/transform 何时用哪个? |
| 合并 | inner/left/outer 结果想清楚了吗? |
| 重塑 | melt/pivot 互转顺手吗? |
| 分组 | agg/transform/filter 组合玩得转吗? |
| 时间序列 | resample/rolling/shift 会了吗? |
| 性能 | 能避免 apply 滥用吗? |
8.2 推荐继续学习的方向
1. Polars:新一代高性能 DataFrame 库,API 与 pandas 高度兼容;
2. Dask / Modin:pandas 的分布式/并行替代;
3. Seaborn / Plotly / ECharts:更专业的可视化;
4. scikit-learn:把 DataFrame 接入机器学习;
5. 数据库:学会 SQL,与 pandas 双剑合璧;
6. 官方文档:pandas.pydata.org 的 User Guide 是最终权威。
8.3 学习建议
- 每个例子都要亲手敲一遍,观察输出;
- 用自己手头的数据做项目,比刷教程有效 10 倍;
- 遇到报错先看最后一行错误信息,再查文档;
- 多用
df.info()、type(obj)确认对象类型; - 善用
help()和pd.__version__。
全系列目录
| 文章 | 主题 |
|------|------|
| 01 | pandas 入门与环境搭建 |
| 02 | Series 详解 |
| 03 | DataFrame 详解 |
| 04 | 数据读取与写入 |
| 05 | 数据查看与探索 |
| 06 | 数据选择与索引 |
| 07 | 缺失值与重复值处理 |
| 08 | 数据类型与转换 |
| 09 | 文本数据处理 |
| 10 | 数据变换:map / apply / applymap |
| 11 | 数据合并:concat / merge / join |
| 12 | 数据重塑与透视表 |
| 13 | 分组聚合:groupby |
| 14 | 时间序列分析 |
| 15 | 数据可视化 |
| 16 | 性能优化与实战案例 |
祝学习顺利!
文章回复
0 条公开回复