跳转到内容

3.4.2 Matplotlib 基础

Matplotlib Figure 与 Axes 结构图

  • 理解 Figure 和 Axes 的对象模型
  • 掌握 5 种基本图表的绑制
  • 学会定制图表元素(标题、图例、网格等)
  • 掌握子图布局

第一次学 Matplotlib,最稳的顺序不是“先记所有函数”,而是先看清一张图通常怎么长出来:

flowchart LR
A["先问想表达什么"] --> B["选图表类型"]
B --> C["创建 Figure / Axes"]
C --> D["把数据画上去"]
D --> E["加标题、标签、图例"]
E --> F["再做样式和保存"]

所以这节真正想解决的是:

  • 最基础的画图动作是什么
  • 为什么 Figure / Axes 模型会成为后面所有图的底座

“一图胜千言。”

同样的数据——

  • 用表格展示:平均薪资: 技术部 18667, 市场部 19000, 管理部 32500
  • 用图表展示:一眼就能看出管理部薪资远高于其他部门

数据可视化的目的是让数据说话,让人在几秒内理解背后的规律和故事。

你可以把 Matplotlib 理解成:

  • 一套最基础的画图工具箱

它不像 Seaborn 那样已经帮你配好很多默认样式, 但它的好处是:

  • 你真正能看见一张图是怎样一步步被搭起来的

所以学 Matplotlib 的价值,不只是会画图, 更是为了建立:

  • 图到底是怎么由数据、画布和坐标轴组成的

# 安装(通常已预装)
# python -m pip install --upgrade matplotlib
import matplotlib.pyplot as plt
import numpy as np
# 在 Jupyter Notebook 中显示图表
# %matplotlib inline

import matplotlib.pyplot as plt
x = [1, 2, 3, 4, 5]
y = [2, 4, 6, 8, 10]
plt.plot(x, y) # 绘制折线图
plt.title("我的第一张图") # 标题
plt.show() # 显示图表

就这么简单!你会看到一条从左下到右上的直线。

第一次画图时,最值得先记的顺序

Section titled “第一次画图时,最值得先记的顺序”

第一次画任何图时,更稳的顺序通常是:

  1. 先准备 xy
  2. 先画出最朴素版本
  3. 再补标题和标签
  4. 最后再补图例、颜色和美化

这样会比一开始就研究配色和样式更容易稳住。


Matplotlib 的图表由两个核心对象组成:

flowchart TD
F["Figure(画布)<br/>整张图的容器"] --> A1["Axes(子图 1)<br/>实际的绘图区域"]
F --> A2["Axes(子图 2)"]
F --> A3["Axes(子图 3)"]
A1 --> T1["标题 Title"]
A1 --> X1["X 轴 xlabel"]
A1 --> Y1["Y 轴 ylabel"]
A1 --> L1["图例 Legend"]
A1 --> D1["数据线/点/柱"]
style F fill:#e3f2fd,stroke:#1565c0,color:#333
style A1 fill:#fff3e0,stroke:#e65100,color:#333
style A2 fill:#fff3e0,stroke:#e65100,color:#333
style A3 fill:#fff3e0,stroke:#e65100,color:#333
  • Figure:整张画布,可以包含多个子图
  • Axes:一个具体的图表区域(注意不是”轴”,而是”子图”)
# 风格 1:plt 快速绘图(简单场景用这个)
plt.plot([1, 2, 3], [4, 5, 6])
plt.title("快速绘图")
plt.show()
# 风格 2:面向对象(推荐,更灵活)
fig, ax = plt.subplots() # 创建画布和子图
ax.plot([1, 2, 3], [4, 5, 6]) # 在子图上绑制
ax.set_title("面向对象绘图") # 设置标题
plt.show()

先别死记函数,先记“它们各自最适合说什么”

Section titled “先别死记函数,先记“它们各自最适合说什么””
图表最值得先记住的用途
折线图看趋势
柱状图看类别对比
散点图看关系
直方图看分布
饼图看占比(且类别要少)

这张表对新人特别重要,因为它会把“函数名”重新变回“表达任务”。

适合场景: 展示数据随时间或连续变量的变化趋势

import matplotlib.pyplot as plt
import numpy as np
# 模拟 12 个月的销售数据
months = np.arange(1, 13)
sales_2023 = [120, 135, 150, 180, 200, 210, 195, 188, 220, 250, 280, 310]
sales_2024 = [140, 155, 170, 195, 230, 245, 225, 210, 260, 290, 320, 350]
fig, ax = plt.subplots(figsize=(10, 6)) # 设置画布大小
ax.plot(months, sales_2023, marker="o", label="2023年", color="#2196F3", linewidth=2)
ax.plot(months, sales_2024, marker="s", label="2024年", color="#FF5722", linewidth=2)
ax.set_title("月度销售额趋势", fontsize=16, fontweight="bold")
ax.set_xlabel("月份", fontsize=12)
ax.set_ylabel("销售额(万元)", fontsize=12)
ax.set_xticks(months)
ax.set_xticklabels([f"{m}月" for m in months])
ax.legend(fontsize=12) # 显示图例
ax.grid(True, alpha=0.3) # 显示网格线
plt.tight_layout() # 自动调整边距
plt.show()

关键参数:

参数作用示例
marker数据点标记"o" 圆点, "s" 方块, "^" 三角
linestyle线型"-" 实线, "--" 虚线, ":" 点线
linewidth线宽2
color颜色"red", "#FF5722", "C0"
label图例标签"2023年"
alpha透明度0.7(0 全透明, 1 不透明)

适合场景: 比较不同类别的数据大小

# 各部门薪资对比
departments = ["技术部", "市场部", "管理部", "财务部", "人事部"]
avg_salary = [18500, 16200, 28000, 15800, 14500]
fig, ax = plt.subplots(figsize=(8, 5))
bars = ax.bar(departments, avg_salary, color=["#4CAF50", "#2196F3", "#FF9800", "#9C27B0", "#607D8B"])
# 在柱子上方显示数值
for bar, val in zip(bars, avg_salary):
ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 300,
f{val:,}", ha="center", fontsize=10)
ax.set_title("各部门平均薪资", fontsize=14)
ax.set_ylabel("薪资(元)")
ax.set_ylim(0, max(avg_salary) * 1.15) # Y 轴留出数值标签的空间
plt.tight_layout()
plt.show()

水平柱状图(数据标签较长时更好看):

fig, ax = plt.subplots(figsize=(8, 5))
ax.barh(departments, avg_salary, color="#4CAF50")
ax.set_xlabel("薪资(元)")
ax.set_title("各部门平均薪资")
plt.tight_layout()
plt.show()

分组柱状图:

# 两年对比
x = np.arange(len(departments))
width = 0.35
fig, ax = plt.subplots(figsize=(10, 5))
ax.bar(x - width/2, [17000, 15000, 26000, 14500, 13000], width, label="2023", color="#64B5F6")
ax.bar(x + width/2, avg_salary, width, label="2024", color="#1565C0")
ax.set_xticks(x)
ax.set_xticklabels(departments)
ax.legend()
ax.set_title("各部门薪资对比(2023 vs 2024)")
plt.tight_layout()
plt.show()

适合场景: 观察两个变量之间的关系

rng = np.random.default_rng(seed=42)
# 模拟身高和体重数据
height = rng.normal(170, 8, 100)
weight = height * 0.65 - 40 + rng.normal(0, 5, 100)
fig, ax = plt.subplots(figsize=(8, 6))
scatter = ax.scatter(height, weight, c=weight, cmap="RdYlGn_r",
s=50, alpha=0.7, edgecolors="white", linewidth=0.5)
ax.set_title("身高与体重的关系", fontsize=14)
ax.set_xlabel("身高(cm)")
ax.set_ylabel("体重(kg)")
plt.colorbar(scatter, label="体重") # 颜色条
plt.tight_layout()
plt.show()

关键参数:

参数作用示例
s点的大小50, 或传入数组让大小不同
c点的颜色"red", 或传入数组做颜色映射
cmap颜色映射"viridis", "RdYlGn", "Blues"
alpha透明度0.7

适合场景: 查看数据的分布情况

rng = np.random.default_rng(seed=42)
scores = rng.normal(75, 12, 500) # 500 个学生的成绩
fig, ax = plt.subplots(figsize=(8, 5))
# 绘制直方图
n, bins, patches = ax.hist(scores, bins=20, color="#42A5F5", edgecolor="white",
alpha=0.8)
# 添加均值线
mean_val = scores.mean()
ax.axvline(mean_val, color="red", linestyle="--", linewidth=2, label=f"均值: {mean_val:.1f}")
ax.set_title("学生成绩分布", fontsize=14)
ax.set_xlabel("分数")
ax.set_ylabel("人数")
ax.legend()
plt.tight_layout()
plt.show()

适合场景: 展示各部分占整体的比例(类别不超过 5-6 个)

labels = ["Python", "JavaScript", "Java", "C++", "其他"]
sizes = [35, 25, 20, 10, 10]
colors = ["#4CAF50", "#FFC107", "#2196F3", "#FF5722", "#9E9E9E"]
explode = (0.05, 0, 0, 0, 0) # 突出 Python
fig, ax = plt.subplots(figsize=(7, 7))
ax.pie(sizes, explode=explode, labels=labels, colors=colors,
autopct="%1.1f%%", startangle=90, shadow=False,
textprops={"fontsize": 12})
ax.set_title("AI 领域编程语言使用占比", fontsize=14)
plt.tight_layout()
plt.show()

flowchart TD
A["我要展示什么?"] --> B{"数据类型"}
B -->|"趋势变化"| C["折线图 📈"]
B -->|"类别对比"| D["柱状图 📊"]
B -->|"两变量关系"| E["散点图 🔵"]
B -->|"数据分布"| F["直方图 📉"]
B -->|"占比构成"| G["饼图 🥧"]
C --> C1["时间序列<br/>股票走势<br/>销售趋势"]
D --> D1["部门对比<br/>产品对比<br/>排行榜"]
E --> E1["身高vs体重<br/>价格vs销量<br/>相关性分析"]
F --> F1["成绩分布<br/>年龄分布<br/>收入分布"]
G --> G1["市场份额<br/>支出占比<br/>(类别不超过6个)"]

一个很适合初学者先记的最小检查表

Section titled “一个很适合初学者先记的最小检查表”

在开始美化之前,先问自己:

  1. 标题有没有说清楚这张图想表达什么?
  2. X 轴和 Y 轴有没有标签和单位?
  3. 图例是不是必要?
  4. 颜色是不是在帮助理解,而不是制造干扰?

只要这 4 件事先做到,图通常就已经比大多数“能跑但不好看”的版本更清楚。

Matplotlib 默认不支持中文,需要配置:

import matplotlib.pyplot as plt
# 方法 1:全局设置(推荐)
plt.rcParams["font.sans-serif"] = ["SimHei", "Arial Unicode MS", "DejaVu Sans"]
plt.rcParams["axes.unicode_minus"] = False # 解决负号显示问题
# macOS 用户可以用
# plt.rcParams["font.sans-serif"] = ["Arial Unicode MS"]
# Linux 用户可以用
# plt.rcParams["font.sans-serif"] = ["WenQuanYi Micro Hei"]
fig, ax = plt.subplots()
ax.plot([1, 2, 3], [4, 5, 6])
ax.set_title("主标题", fontsize=16, fontweight="bold", color="#333")
ax.set_xlabel("X 轴标签", fontsize=12)
ax.set_ylabel("Y 轴标签", fontsize=12)
ax.plot(x, y1, label="数据 A")
ax.plot(x, y2, label="数据 B")
ax.legend(loc="upper left", fontsize=10, frameon=True, shadow=True)
# loc 常用值:
# "best" (自动), "upper left", "upper right", "lower left", "lower right", "center"
ax.grid(True, alpha=0.3, linestyle="--") # 半透明虚线网格
# 使用预设样式(全局设置)
plt.style.use("seaborn-v0_8-whitegrid") # 清爽白色网格
# 其他好看的样式:
# "ggplot", "seaborn-v0_8", "fivethirtyeight", "bmh"

查看所有可用样式:

print(plt.style.available)
fig, ax = plt.subplots(figsize=(8, 5))
x = np.arange(1, 13)
y = [120, 135, 150, 180, 200, 210, 195, 188, 220, 250, 280, 310]
ax.plot(x, y, marker="o")
# 标注最大值
max_idx = np.argmax(y)
ax.annotate(f"最高点: {y[max_idx]}",
xy=(x[max_idx], y[max_idx]), # 箭头指向
xytext=(x[max_idx]-2, y[max_idx]+20), # 文字位置
arrowprops=dict(arrowstyle="->", color="red"),
fontsize=12, color="red")
plt.show()

# 2 行 2 列 = 4 个子图
fig, axes = plt.subplots(2, 2, figsize=(12, 8))
# axes 是一个 2×2 的数组
axes[0, 0].plot([1, 2, 3], [1, 4, 9])
axes[0, 0].set_title("折线图")
axes[0, 1].bar(["A", "B", "C"], [3, 7, 5])
axes[0, 1].set_title("柱状图")
rng = np.random.default_rng(seed=42)
axes[1, 0].scatter(rng.random(50), rng.random(50))
axes[1, 0].set_title("散点图")
axes[1, 1].hist(rng.standard_normal(200), bins=15)
axes[1, 1].set_title("直方图")
fig.suptitle("四种基本图表", fontsize=16, fontweight="bold")
plt.tight_layout()
plt.show()
fig = plt.figure(figsize=(12, 5))
# 左边占 2/3 宽度
ax1 = fig.add_axes([0.05, 0.1, 0.6, 0.8]) # [left, bottom, width, height]
ax1.plot([1, 2, 3, 4], [10, 20, 25, 30])
ax1.set_title("主图")
# 右边占 1/3 宽度
ax2 = fig.add_axes([0.72, 0.1, 0.25, 0.8])
ax2.bar(["A", "B"], [15, 25])
ax2.set_title("辅图")
plt.show()

fig, ax = plt.subplots()
ax.plot([1, 2, 3], [4, 5, 6])
ax.set_title("保存示例")
# 保存为 PNG(最常用)
fig.savefig("my_chart.png", dpi=150, bbox_inches="tight")
# 保存为 SVG(矢量图,放大不模糊)
fig.savefig("my_chart.svg", bbox_inches="tight")
# 保存为 PDF
fig.savefig("my_chart.pdf", bbox_inches="tight")
参数作用推荐值
dpi分辨率150(普通), 300(印刷)
bbox_inches裁剪边距"tight" 自动裁剪
transparent透明背景True(PPT 用)

学完这一页,至少保留这张证据卡:

问题
这张图表回答的是比较、分布、趋势,还是关系
图表选择
折线图、柱状图、散点图、直方图、箱线图、热力图或交互式仪表板
工件
保存的图表图片/html 以及所用的数据切片
失败检查
尺度误导、图表过载、聚合错误或缺少标签
期望产出
带有一句说明洞察的图表成果
图表函数适用场景
折线图ax.plot()趋势、时间序列
柱状图ax.bar() / ax.barh()类别对比
散点图ax.scatter()两变量关系
直方图ax.hist()数据分布
饼图ax.pie()占比(慎用)

核心工作流:

fig, ax = plt.subplots(figsize=(8, 5)) # 1. 创建画布
ax.plot(x, y) # 2. 绑制数据
ax.set_title("标题") # 3. 设置标题/标签
ax.legend() # 4. 添加图例
plt.tight_layout() # 5. 调整布局
plt.show() # 6. 显示
  • Matplotlib 最重要的不是函数多,而是它让你真正理解图是怎么搭起来的
  • 先想表达什么,再选图,再写代码,会比先背 API 更稳
  • Figure / Axes 是后面几乎所有 Python 可视化库都绕不开的底层直觉

# 绘制 sin(x) 和 cos(x) 的曲线图
# x 从 0 到 2π,取 100 个点
# 要求:不同颜色和线型、有图例、有网格、有标题
# 有 6 个城市的房价数据和人均收入数据
# 绘制分组柱状图进行对比
# 要求:在柱子上方标注数值
# 生成 1000 个正态分布随机数
# 在 2×2 的子图中分别展示:
# 1. 折线图(前 100 个数据的走势)
# 2. 直方图(分布)
# 3. 散点图(相邻两个数据点的关系)
# 4. 柱状图(按区间统计频次)
参考实现与讲解
  • 正弦/余弦图应有两条带标签的线、标题、坐标轴标签、图例和浅色网格。如果两条线不好比较,先调整颜色和线型,再考虑装饰。
  • 分组柱状图要显式计算 x 位置,并把柱子或坐标轴标到读者不看代码也能理解的程度。
  • 多图场景使用 fig, ax = plt.subplots(),并用 fig.savefig(...) 保存图像。保存下来的图是证据的一部分,不是事后补充。