Matplotlib

技术栈
工具链
python数据可视化绘图图表可视化pyplot

概览

Matplotlib 技术栈概览

Matplotlib 是 Python 最基础和最强大的二维可视化库,由 John D. Hunter 于 2003 年创建。它提供类似 MATLAB 的绘图 API,是 Seaborn、Pandas 可视化、Plotnine 等高层库的底层引擎。

核心特性:

  • 📈 全面图表类型 — 折线图、散点图、柱状图、饼图、直方图、热力图、3D 图等
  • 🎨 像素级控制 — 颜色、线型、字体、刻度、图例全部可定制
  • 📐 科学绘图 — LaTeX 数学公式渲染、对数坐标、极坐标
  • 🖼️ 多子图布局 — subplots、GridSpec、subplot2grid 灵活排版
  • 💾 多格式输出 — PNG、SVG、PDF、EPS 矢量格式
  • 🔗 无缝集成 — Jupyter Notebook inline 显示、Pandas 直接绘图

适用场景: 科学论文插图、数据报告可视化、仪表板、数据探索、出版级图形。

安装

1. 环境准备

  • 操作系统: Windows 10+ / macOS 11+ / Linux
  • Python 版本: Python 3.9 - 3.12
  • 依赖项: NumPy(Matplotlib 自动安装)

2. 安装命令

# === 基础安装 ===
pip install matplotlib

# === 安装完整可视化栈 ===
pip install matplotlib seaborn plotly jupyter

# === 验证安装 ===
python -c "import matplotlib; print(f'Matplotlib {matplotlib.__version__}')"

# === 在 Jupyter 中启用 inline 显示 ===
# 在 Notebook 开头加入:
# %matplotlib inline

3. 常见安装问题

问题 1:中文显示为方框 □□□

这是最常见的问题——缺少中文字体:

import matplotlib.pyplot as plt
plt.rcParams["font.sans-serif"] = ["SimHei", "Microsoft YaHei", "WenQuanYi Micro Hei"]
plt.rcParams["axes.unicode_minus"] = False  # 解决负号显示问题

问题 2:macOS 后端问题

# 如果报 NSWindow 相关错误
echo "backend: TkAgg" >> ~/.matplotlib/matplotlibrc

问题 3:pip 安装慢

pip install matplotlib -i https://pypi.tuna.tsinghua.edu.cn/simple

问题 4:服务器无 GUI 环境

# 使用非交互后端
import matplotlib
matplotlib.use("Agg")  # 必须在 import pyplot 之前
import matplotlib.pyplot as plt
# 之后 plt.savefig() 正常工作,plt.show() 不可用

示例

Matplotlib 绘图基础 —— 从 pyplot 到面向对象

目标

  • 掌握 pyplot 快速绘图
  • 理解 Figure/Axes 面向对象模型
  • 掌握常见图表类型的绘制
  • 中文显示与样式配置

完整代码

import matplotlib.pyplot as plt
import matplotlib
import numpy as np

# ============================================================
# 0. 全局配置(推荐放在开头)
# ============================================================
plt.rcParams.update({
    "figure.figsize": (10, 6),
    "figure.dpi": 100,
    "font.sans-serif": ["SimHei", "Microsoft YaHei"],  # 中文字体
    "axes.unicode_minus": False,                        # 负号正常显示
    "axes.grid": True,
    "grid.alpha": 0.3,
})

# ============================================================
# 1. 折线图(Line Plot)
# ============================================================
x = np.linspace(0, 2 * np.pi, 100)

fig, ax = plt.subplots(figsize=(10, 4))
ax.plot(x, np.sin(x), label="sin(x)", color="#2196F3", linewidth=2)
ax.plot(x, np.cos(x), label="cos(x)", color="#FF5722", linewidth=2, linestyle="--")
ax.axhline(y=0, color="gray", linewidth=0.5)  # 水平参考线
ax.set_title("三角函数", fontsize=14, fontweight="bold")
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.legend(loc="upper right", frameon=True)
fig.tight_layout()
fig.savefig("line_plot.png", dpi=150, bbox_inches="tight")
plt.show()

# ============================================================
# 2. 散点图(Scatter Plot)
# ============================================================
np.random.seed(42)
n = 200
x = np.random.randn(n)
y = 2 * x + np.random.randn(n) * 0.5
colors = np.sqrt(x**2 + y**2)
sizes = np.abs(np.random.randn(n) * 50 + 100)

fig, ax = plt.subplots()
scatter = ax.scatter(x, y, c=colors, s=sizes, alpha=0.6, cmap="viridis")
ax.set_title("散点图(颜色表示距离,大小随机)")
fig.colorbar(scatter, ax=ax, label="距离原点")
plt.show()

# ============================================================
# 3. 柱状图(Bar Chart)
# ============================================================
categories = ["Python", "JavaScript", "Java", "C++", "Rust"]
values = [85, 70, 65, 55, 40]
errors = [3, 5, 4, 6, 2]

fig, ax = plt.subplots()
bars = ax.bar(categories, values, yerr=errors, capsize=5,
              color=["#2196F3", "#FFC107", "#F44336", "#4CAF50", "#9C27B0"],
              edgecolor="white", linewidth=1)
# 在柱上标注数值
for bar, val in zip(bars, values):
    ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 2,
            str(val), ha="center", fontsize=11)
ax.set_title("编程语言受欢迎度")
ax.set_ylabel("评分")
ax.set_ylim(0, 100)
plt.show()

# ============================================================
# 4. 直方图(Histogram)
# ============================================================
data = np.random.randn(10000)

fig, ax = plt.subplots()
ax.hist(data, bins=50, density=True, alpha=0.7, color="#4CAF50", edgecolor="white")
# 叠加理论正态分布曲线
x = np.linspace(-4, 4, 200)
ax.plot(x, 1/np.sqrt(2*np.pi) * np.exp(-x**2/2), "r-", linewidth=2, label="N(0,1)")
ax.set_title("标准正态分布直方图")
ax.legend()
plt.show()

# ============================================================
# 5. 饼图(Pie Chart)
# ============================================================
labels = ["工程部", "销售部", "市场部", "HR", "财务"]
sizes = [35, 25, 20, 12, 8]
explode = (0.05, 0, 0, 0, 0)  # 突出第一块

fig, ax = plt.subplots()
wedges, texts, autotexts = ax.pie(
    sizes, labels=labels, explode=explode, autopct="%1.1f%%",
    startangle=90, colors=plt.cm.Set3.colors,
)
# 调整百分比文字样式
for t in autotexts:
    t.set_fontsize(10)
    t.set_fontweight("bold")
ax.set_title("部门人员分布")
plt.show()

# ============================================================
# 6. 多子图布局(GridSpec)
# ============================================================
from matplotlib.gridspec import GridSpec

fig = plt.figure(figsize=(12, 8))
gs = GridSpec(2, 3, figure=fig, hspace=0.3, wspace=0.3)

ax1 = fig.add_subplot(gs[0, 0])
ax1.plot(np.random.randn(50).cumsum())
ax1.set_title("随机游走")

ax2 = fig.add_subplot(gs[0, 1:])
ax2.scatter(np.random.randn(100), np.random.randn(100), alpha=0.5)
ax2.set_title("散点图")

ax3 = fig.add_subplot(gs[1, :2])
ax3.hist(np.random.randn(1000), bins=30, color="skyblue", edgecolor="white")
ax3.set_title("直方图")

ax4 = fig.add_subplot(gs[1, 2])
ax4.pie([30, 20, 50], labels=["A", "B", "C"], autopct="%d%%",
        colors=["gold", "lightcoral", "lightskyblue"])
ax4.set_title("饼图")

fig.suptitle("GridSpec 多子图示例", fontsize=16, fontweight="bold")
plt.show()

# ============================================================
# 7. 样式与主题
# ============================================================
print("可用样式:", plt.style.available[:5])

# 使用内置样式
with plt.style.context("seaborn-v0_8-darkgrid"):
    fig, ax = plt.subplots()
    ax.plot(x, np.sin(x))
    ax.set_title("seaborn-darkgrid 样式")

# 或全局设置
# plt.style.use("ggplot")

关键要点

概念 说明
plt.subplots() 推荐:同时创建 Figure 和 Axes
fig, ax = ... 面向对象风格,比 pyplot 状态机更清晰
ax.set_*() Axes 级别的标题/标签/刻度设置
fig.savefig(dpi=) 保存图片,支持 PNG/SVG/PDF
plt.rcParams 全局样式配置
GridSpec 复杂的子图布局

教程

Matplotlib 进阶 —— 面向对象绘图与出版级定制

本章目标

  • 深入理解 Figure / Axes / Artist 对象模型
  • 掌握出版级图形的定制技巧
  • 学会使用 Seaborn 快速美化

1. Matplotlib 对象模型

Figure(画布)
  ├── Axes(坐标轴/子图)         # 这就是你绘图的地方
  │   ├── Line2D(线条)
  │   ├── Scatter(散点)
  │   ├── Text(文字)
  │   ├── XAxis / YAxis
  │   └── Legend
  └── Axes
      └── ...

一个 Figure 可以有多个 Axes
每个 Axes 可以有多个 Artist(图形元素)

三种绘图方式

# ① pyplot 状态机(简单但不推荐大量使用)
plt.plot(x, y)
plt.title("Title")
plt.show()

# ② 面向对象(推荐)
fig, ax = plt.subplots()
ax.plot(x, y)
ax.set_title("Title")
plt.show()

# ③ Artist 底层(极少需要)
fig = plt.figure()
ax = fig.add_axes([0.1, 0.1, 0.8, 0.8])
line = plt.Line2D(x, y)
ax.add_line(line)

2. 出版级图形定制

fig, ax = plt.subplots(figsize=(8, 5), dpi=150)

# 绘制
ax.plot(x, y, label="数据", color="#2196F3", linewidth=1.5, marker="o", markersize=4)

# 标题(可含 LaTeX)
ax.set_title(r"$\int_0^\infty e^{-x^2} dx = \frac{\sqrt{\pi}}{2}$", fontsize=14)

# 坐标轴标签
ax.set_xlabel("时间 (ms)", fontsize=12)
ax.set_ylabel("振幅 (mV)", fontsize=12)

# 刻度定制
ax.tick_params(labelsize=10, direction="in", top=True, right=True)
ax.minorticks_on()

# 图例
ax.legend(frameon=True, fancybox=True, shadow=True, loc="best")

# 注释
ax.annotate("峰值", xy=(np.pi/2, 1), xytext=(np.pi/2+1, 0.8),
            arrowprops=dict(arrowstyle="->", color="red"),
            fontsize=11, color="red")

# 网格
ax.grid(True, alpha=0.3, linestyle="--")

# 保存
fig.tight_layout()
fig.savefig("publication_figure.pdf", format="pdf", bbox_inches="tight",
            facecolor="white", edgecolor="none")

3. 高级图表类型

3.1 双 Y 轴

fig, ax1 = plt.subplots()

ax1.plot(x, y1, "b-", label="温度")
ax1.set_ylabel("温度 (°C)", color="b")

ax2 = ax1.twinx()
ax2.plot(x, y2, "r-", label="湿度")
ax2.set_ylabel("湿度 (%)", color="r")

fig.tight_layout()

3.2 热力图

fig, ax = plt.subplots()
im = ax.imshow(matrix, cmap="viridis", aspect="auto")
fig.colorbar(im, ax=ax, label="强度")
ax.set_xticks(range(len(labels)))
ax.set_xticklabels(labels, rotation=45)

3.3 3D 图形

fig = plt.figure()
ax = fig.add_subplot(111, projection="3d")

X, Y = np.meshgrid(np.linspace(-5, 5, 50), np.linspace(-5, 5, 50))
Z = np.sin(np.sqrt(X**2 + Y**2))
ax.plot_surface(X, Y, Z, cmap="coolwarm", edgecolor="none")

4. Seaborn 快速美化

import seaborn as sns

# Seaborn 自动设置更美观的默认样式
sns.set_theme(style="whitegrid", palette="muted")

# 统计分析图(一行代码)
sns.boxplot(data=df, x="category", y="value")
sns.violinplot(data=df, x="group", y="score")
sns.pairplot(df, hue="species")
sns.heatmap(df.corr(), annot=True, cmap="coolwarm")

Matplotlib vs Seaborn

特性 Matplotlib Seaborn
灵活度 极高 中等(预定义布局)
默认美观 一般 优秀
DataFrame 集成 手动 原生支持
学习曲线 陡峭 平缓
适用场景 出版级定制 数据分析可视化

思考题

  1. plt.plot()ax.plot() 有什么区别?为什么推荐后者?
  2. 如何让 Matplotlib 图形在不同屏幕/设备上保持一致的尺寸?
  3. Seaborn 相比 Matplotlib 有什么本质区别(不是"更好看"这个层次)?
  4. 矢量格式(SVG/PDF)与位图格式(PNG)的选择标准是什么?

参考资料

  1. [1] Matplotlib Development Team. Matplotlib 官方文档. 2024.
  2. [2] Nicolas P. Rougier. Scientific Visualization: Python + Matplotlib. 2021.
  3. [3] Jake VanderPlas. Python Data Science Handbook. 2023.
  4. [4] Chris Moffitt. Effectively Using Matplotlib. 2020.