Python 数据可视化入门:Matplotlib 核心用法
摘要
数据表能够保存事实,但图表更适合展示趋势、差异、构成和异常。Matplotlib 是 Python 生态中基础而通用的绘图库,能够创建折线图、柱状图、散点图、直方图和多子图,并控制标题、坐标轴、图例、标注与输出文件。
本文从 Figure、Axes 和数据映射开始,介绍 Matplotlib 的基本绘图流程,重点演示业务数据中的趋势图、对比图、分布图和组合图。文章还会讨论图表类型选择、中文字体、坐标轴误导、图例遮挡、图片分辨率和可复现输出等实践问题。
一、背景与问题
1. 表格不一定能直接表达规律
假设有一份按月统计的销售数据:
text
月份 线上销售额 门店销售额
一月 1200 900
二月 1350 1020
三月 1480 1100
直接阅读表格可以得到具体数字,但不容易快速观察:
- 销售额是否持续增长;
- 哪个渠道增长更快;
- 两个渠道之间的差距是否扩大;
- 某个月是否出现异常波动;
- 不同指标的量级是否适合在同一张图中比较。
图表的价值不只是"把数据画出来",而是把数据中的关系编码成位置、长度、颜色、形状和连接线,让比较过程更直观。
2. 图表应服务于一个明确问题
常见的分析问题与图表类型可以这样对应:
| 分析问题 | 常见图表 |
|---|---|
| 随时间如何变化 | 折线图 |
| 不同类别谁更高 | 柱状图、条形图 |
| 两个数值变量是否相关 | 散点图 |
| 数值分布如何 | 直方图、箱线图 |
| 各类别占比如何 | 饼图或条形图 |
| 多个指标如何并列比较 | 多子图 |
如果一张图同时放入太多指标、颜色和注释,读者反而难以找到重点。
二、核心概念
1. Figure 和 Axes
Matplotlib 常用的对象层次如下:
text
Figure
├── Axes
│ ├── x 轴
│ ├── y 轴
│ ├── 标题
│ └── 图例
└── 其他 Axes
Figure是整张画布;Axes是画布中的一个坐标系;- 一个
Figure可以包含一个或多个Axes。
推荐使用面向对象接口:
text
fig, ax = plt.subplots()
ax.plot(x, y)
这样可以明确操作哪个坐标系,尤其适合多子图和复杂报表。
2. pyplot 和面向对象接口
matplotlib.pyplot 提供了简洁的状态式接口:
text
plt.plot(x, y)
plt.title("趋势")
plt.show()
面向对象接口则把设置绑定到具体的 Axes:
text
fig, ax = plt.subplots()
ax.plot(x, y)
ax.set_title("趋势")
plt.show()
简单探索时两者都可以使用;正式脚本、报告和多图布局优先使用 fig, ax 方式。
3. 数据与视觉元素的映射
绘图时需要决定数据如何映射到视觉属性:
| 数据关系 | 视觉属性 |
|---|---|
| 时间先后 | x 轴位置 |
| 指标数值 | y 轴位置或柱子高度 |
| 分类字段 | 颜色、线型或分组位置 |
| 记录密度 | 点的数量或透明度 |
| 不确定性 | 误差线或阴影区域 |
映射关系越稳定,图表越容易解释。相同含义的渠道,应该尽量在不同图表中使用相同颜色。
4. 标题、坐标轴和图例
一张可读的图表至少需要:
- 能说明业务含义的标题;
- 清晰的 x 轴和 y 轴标签;
- 单位或数量级;
- 多系列数据对应的图例;
- 合理的刻度和范围。
不要只写"销售趋势"这样的泛化标题,可以写成"2026 年第一季度各渠道销售额趋势(元)"。
三、工作原理
1. 创建画布、绘制图形、调整布局、输出文件
Matplotlib 的典型流程如下:
text
准备数据
↓
创建 Figure 和 Axes
↓
调用 plot、bar、scatter 等绘图方法
↓
设置标题、坐标轴、图例和样式
↓
tight_layout 调整布局
↓
savefig 保存或 show 展示
绘图方法通常不会改变原始 DataFrame,因此数据清洗、统计和可视化可以保持相互分离。
2. 折线图连接的是有序观测
折线图适合时间序列或有明确顺序的数值。线段会把相邻点连接起来,因此必须先按 x 轴顺序排序:
text
data = data.sort_values("month")
ax.plot(data["month"], data["revenue"])
如果类别没有自然顺序,直接使用折线连接可能制造不存在的趋势,此时更适合使用柱状图或散点图。
3. 柱状图通过长度比较数值
柱状图适合比较有限数量的类别。类别名称较长时,横向条形图通常比竖向柱状图更容易阅读:
text
ax.bar(categories, values)
ax.barh(categories, values)
柱状图的基线通常从 0 开始,因为柱子长度代表数值大小。截断基线会放大差异,除非图表明确标注并且使用场景允许。
4. 多子图共享坐标轴
多个指标需要并排比较时,可以创建多子图:
text
fig, axes = plt.subplots(2, 1, sharex=True)
共享 x 轴能够对齐时间位置;共享 y 轴则适合量纲相同的指标。不同单位的指标不应强行共享 y 轴。
5. 图像渲染与输出格式
show() 主要用于交互式查看,savefig() 用于保存文件。输出时要明确:
- 文件格式:PNG 适合位图,SVG/PDF 适合矢量输出;
- 分辨率:
dpi控制位图清晰度; - 画布尺寸:
figsize以英寸为单位; - 边距:
bbox_inches="tight"可以减少多余空白。
四、实战示例
下面的示例使用 Python 3.10+、pandas 2.1+ 和 Matplotlib 3.7+。运行前安装依赖:
text
python -m pip install "pandas>=2.1" "matplotlib>=3.7"
1. 准备业务数据
python
from pathlib import Path
import matplotlib.pyplot as plt
import pandas as pd
data = pd.DataFrame(
{
"month": ["一月", "二月", "三月", "四月", "五月", "六月"],
"online": [1200, 1350, 1480, 1420, 1660, 1810],
"store": [900, 1020, 1100, 1080, 1190, 1280],
"orders": [120, 130, 145, 138, 156, 170],
}
)
month_order = ["一月", "二月", "三月", "四月", "五月", "六月"]
data["month"] = pd.Categorical(
data["month"],
categories=month_order,
ordered=True,
)
data = data.sort_values("month").reset_index(drop=True)
assert data["month"].tolist() == month_order
assert data[["online", "store", "orders"]].notna().all().all()
月份虽然是中文标签,但业务顺序不是字符串自然排序结果,因此显式设置有序分类。
2. 绘制销售额趋势折线图
python
fig, ax = plt.subplots(figsize=(8, 4.5))
ax.plot(
data["month"],
data["online"],
marker="o",
linewidth=2,
label="线上",
)
ax.plot(
data["month"],
data["store"],
marker="o",
linewidth=2,
label="门店",
)
ax.set_title("2026 年上半年各渠道销售额趋势")
ax.set_xlabel("月份")
ax.set_ylabel("销售额(元)")
ax.grid(axis="y", alpha=0.3)
ax.legend(title="渠道")
fig.tight_layout()
fig.savefig("sales_trend.png", dpi=150, bbox_inches="tight")
plt.close(fig)
使用脚本批量生成图片时,保存后调用 plt.close(fig) 可以释放画布资源,避免循环绘图时内存持续增长。
3. 绘制渠道对比柱状图
python
fig, ax = plt.subplots(figsize=(8, 4.5))
positions = range(len(data))
bar_width = 0.38
online_positions = [position - bar_width / 2 for position in positions]
store_positions = [position + bar_width / 2 for position in positions]
ax.bar(
online_positions,
data["online"],
width=bar_width,
label="线上",
)
ax.bar(
store_positions,
data["store"],
width=bar_width,
label="门店",
)
ax.set_xticks(list(positions))
ax.set_xticklabels(data["month"])
ax.set_title("各月份渠道销售额对比")
ax.set_xlabel("月份")
ax.set_ylabel("销售额(元)")
ax.legend()
fig.tight_layout()
fig.savefig("channel_comparison.png", dpi=150, bbox_inches="tight")
plt.close(fig)
分组柱状图中,两组柱子共享同一组月份位置,只是通过水平偏移并排显示。类别数量较多时,可以考虑改用横向条形图或分面图。
4. 绘制订单量与销售额散点图
python
fig, ax = plt.subplots(figsize=(7, 4.5))
ax.scatter(
data["orders"],
data["online"] + data["store"],
s=70,
alpha=0.8,
)
for _, row in data.iterrows():
ax.annotate(
row["month"],
(row["orders"], row["online"] + row["store"]),
xytext=(5, 5),
textcoords="offset points",
)
ax.set_title("订单量与总销售额关系")
ax.set_xlabel("订单量")
ax.set_ylabel("总销售额(元)")
ax.grid(alpha=0.25)
fig.tight_layout()
fig.savefig("orders_revenue_scatter.png", dpi=150, bbox_inches="tight")
plt.close(fig)
散点图适合观察两个数值变量的关系。标注不宜过多,否则文字会互相遮挡;大数据集通常应减少标注或使用交互式工具。
5. 绘制销售额分布直方图
python
revenue = (data["online"] + data["store"]).astype(float)
fig, ax = plt.subplots(figsize=(7, 4.5))
ax.hist(
revenue,
bins=4,
edgecolor="white",
alpha=0.85,
)
ax.axvline(
revenue.mean(),
color="red",
linestyle="--",
label=f"均值:{revenue.mean():.0f}",
)
ax.set_title("月度总销售额分布")
ax.set_xlabel("总销售额(元)")
ax.set_ylabel("月份数量")
ax.legend()
fig.tight_layout()
fig.savefig("revenue_distribution.png", dpi=150, bbox_inches="tight")
plt.close(fig)
直方图的箱数会影响分布外观。箱数太少会隐藏结构,太多则会产生噪声,应结合样本量和分析目的调整。
6. 创建多子图分析面板
python
fig, axes = plt.subplots(
2,
2,
figsize=(11, 7),
constrained_layout=True,
)
axes[0, 0].plot(data["month"], data["online"], marker="o", label="线上")
axes[0, 0].plot(data["month"], data["store"], marker="o", label="门店")
axes[0, 0].set_title("销售额趋势")
axes[0, 0].legend()
axes[0, 1].bar(data["month"], revenue, color="#4C78A8")
axes[0, 1].set_title("月度总销售额")
axes[1, 0].scatter(data["orders"], revenue, color="#F58518")
axes[1, 0].set_title("订单量与销售额")
axes[1, 0].set_xlabel("订单量")
axes[1, 0].set_ylabel("销售额")
axes[1, 1].hist(revenue, bins=4, color="#54A24B")
axes[1, 1].set_title("销售额分布")
fig.suptitle("销售业务分析面板", fontsize=15)
fig.savefig("sales_dashboard.png", dpi=150, bbox_inches="tight")
plt.close(fig)
多子图适合把同一主题的多个视角放在一张报告图中。每个子图仍然需要清晰标题和必要的坐标轴说明。
7. 处理中文字体和负号
如果运行环境没有中文字体,标题和标签可能显示为方框。字体名称需要根据操作系统和服务器环境配置:
python
import matplotlib.pyplot as plt
plt.rcParams["axes.unicode_minus"] = False
plt.rcParams["font.sans-serif"] = [
"Microsoft YaHei",
"SimHei",
"DejaVu Sans",
]
生产环境不应假设某个字体一定存在。更稳妥的做法是把字体文件作为部署资产,使用 matplotlib.font_manager 注册并在生成前验证。
8. 封装统一的图片输出函数
python
def save_figure(fig, output_dir, filename):
output_dir = Path(output_dir)
output_dir.mkdir(parents=True, exist_ok=True)
output_path = output_dir / filename
fig.savefig(
output_path,
dpi=150,
bbox_inches="tight",
facecolor="white",
)
plt.close(fig)
return output_path
fig, ax = plt.subplots(figsize=(6, 3.5))
ax.plot(data["month"], revenue, marker="o")
ax.set_title("月度总销售额")
ax.set_xlabel("月份")
ax.set_ylabel("销售额(元)")
output_path = save_figure(
fig,
"figures",
"monthly_revenue.png",
)
assert output_path.exists()
统一封装输出参数可以减少不同图表的分辨率、边距和文件命名不一致问题。
五、常见问题与实践建议
1. 把类别顺序交给字符串排序
月份、季度、等级和业务流程状态都可能有自定义顺序。绘图前要通过有序分类、映射表或显式排序确定 x 轴顺序。
2. 用折线图连接无序类别
折线暗示相邻点存在连续变化。对于没有自然顺序的地区、产品类别或渠道,柱状图通常比折线图更合适。
3. 截断柱状图 y 轴
柱状图依靠柱子长度表达差异,通常应从 0 开始。若必须放大微小差异,可以使用点图、折线图或在标题和注释中明确 y 轴截断。
4. 图例和文字遮挡数据
图例、数据标签和注释都可能覆盖数据。可以调整图例位置、减少标注、增大画布,或使用 bbox_inches="tight" 配合人工检查输出图片。
5. 颜色使用过多
颜色越多不一定越清晰。分类数量较少时使用稳定的离散配色;连续数值应使用连续色带;不要只依赖颜色表达关键信息,还应配合位置、线型或文字。
6. 误差线没有定义
误差线必须说明表示标准差、标准误、置信区间还是业务上下限。没有定义的误差线会让图表看起来专业,却无法正确解释。
7. 在循环中不断创建图但不关闭
批量生成图表时,每次 plt.subplots() 都会创建对象。处理完后关闭 Figure,必要时使用非交互后端,避免服务器环境等待图形界面。
8. 只保存图片,不保存生成口径
报告图片还应保留数据范围、筛选条件、代码版本、绘图参数和生成时间。否则图片发生变化时,很难解释差异来源。
六、进阶思考
1. 图表应该表达比较关系
高质量图表通常围绕一个比较问题设计:
text
谁更高?
增长多快?
差距是否扩大?
异常发生在哪里?
分布是否偏斜?
先写出问题,再选择图形和视觉编码,比先画一张"漂亮的图"更可靠。
2. 尺度会改变视觉结论
线性坐标适合观察绝对差异;对数坐标适合跨数量级比较增长倍数。使用对数轴时必须明确标注,否则读者可能把视觉距离误认为线性差值。
3. 不同单位不要直接共用 y 轴
销售额和订单量量纲不同。若需要同时展示,可以使用双 y 轴,但双轴容易造成虚假的相关性,优先考虑上下排列的多子图或先计算单位统一的指标。
4. 可视化函数应与数据准备分离
推荐分成三层:
text
原始数据读取与清洗
↓
统计结果和图表数据准备
↓
绘图与文件输出
这样可以单独测试统计结果,也可以替换输出格式而不改变业务计算。
5. 可视化测试不只检查图片是否生成
自动化测试可以检查:
- 输入字段是否存在;
- 统计后的记录数和总量是否正确;
- 输出文件是否存在且非空;
- 图表标题、单位和文件名是否符合约定。
像素级图片比较容易受到字体和后端影响,通常应把数据断言与少量视觉抽查结合起来。
结论
Matplotlib 的核心工作流是创建 Figure 和 Axes,使用合适的绘图方法表达数据关系,再补充标题、坐标轴、图例、样式和输出配置。折线图适合趋势,柱状图适合类别比较,散点图适合关系分析,直方图适合分布观察,多子图适合组织同一主题的多个视角。
真正可靠的可视化还需要处理类别顺序、中文字体、坐标轴尺度、缺失值、输出分辨率和可复现性。下一步可以继续学习 Seaborn,在 Matplotlib 基础上快速绘制统计图表和分组分布。