43
0

入门篇:Matplotlib绘图入门

2026-05-12
2026-05-12
入门篇:Matplotlib绘图入门

前言

Matplotlib 是 Python 生态中最经典、使用最广泛的数据可视化库。自 2003 年发布以来,它已经成为数据科学家、科研工作者和工程师的标准工具之一。无论是探索性数据分析、学术论文配图,还是数学建模竞赛中的结果展示,Matplotlib 都能提供强大而灵活的绘图能力。

然而,Matplotlib 的学习曲线并不平坦。它的 API 设计兼顾了简单与复杂两种需求,导致初学者常常在 pyplot 函数式接口和面向对象接口之间感到困惑;中文显示、子图布局、图表美化等常见问题也往往需要查阅大量零散的资料才能解决。

本教程的编写初衷正是为了解决这一问题——用一套系统、简洁、实用的指南,覆盖数学建模和学术写作中最常用的 Matplotlib 功能

适用读者

  • 准备参加数学建模竞赛(如美赛、国赛、MathorCup 等),需要用 Python 生成论文配图的同学

  • 正在撰写学术论文,需要高质量图表的研究生和科研人员

  • 学习 Python 数据分析,希望系统掌握 Matplotlib 的初学者

内容结构

本教程共分为 10 节,按照"从入门到精通"的逻辑组织:

章节

内容

核心收获

第1节

快速入门

理解两种绘图方式,解决中文显示问题

第2节

折线图

线型、颜色、标记点、置信区间填充

第3节

散点图

基础散点、颜色映射、气泡图

第4节

柱状图

分组柱状图、误差棒、水平柱状图

第5节

饼图与环形图

基础饼图、环形图、嵌套饼图

第6节

直方图

分箱策略、多组对比、核密度叠加

第7节

箱线图

基础箱线图、分组对比、异常值展示

第8节

子图与布局

subplots()、GridSpec 不规则布局

第9节

图表美化

坐标轴、配色方案、内置风格

第10节

保存与导出

DPI 选择、格式对比、批量导出

编写特点

  • 面向对象优先:全教程统一采用 OO 方式编写代码,这也是 Matplotlib 官方推荐的做法

  • 中文字体开箱即用:第1节即配置好全局中文字体,后续所有图表中文正常显示

  • 速查表驱动:每节末尾附有速查表,方便在实际使用时快速查阅

  • 数模场景导向:所有示例均围绕数学建模和学术写作的真实场景设计

如何使用本教程

建议初学者按顺序阅读前 3 节,掌握基本绘图方式后再按需跳转到感兴趣的章节。每节内容相对独立,可以直接作为工具书查阅。教程末尾的速查表适合在编写代码时快速翻阅。

所有示例代码均可在附录压缩包的 .py 文件中找到。运行对应的脚本即可重新生成所有示例图片。


祝学习愉快!

第1节 快速入门

Matplotlib 是 Python 中最经典的可视化库,提供了两种绘图风格:pyplot 函数式面向对象(OO)式。本节将对比两种方式,并解决中文显示这一最常见的问题。

1.1 两种绘图方式对比

pyplot 方式

pyplot 是 Matplotlib 提供的 MATLAB 风格接口,通过一系列函数调用来创建和修改图表。它的优势在于代码简洁,适合快速绘图和交互式探索。

import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0, 10, 50)
y1 = np.sin(x)
y2 = np.cos(x)

plt.figure(figsize=(8, 5))
plt.plot(x, y1, label='sin(x)', color='#2196F3', linewidth=2)
plt.plot(x, y2, label='cos(x)', color='#FF5722', linewidth=2, linestyle='--')
plt.title('pyplot 风格绘图(函数式)', fontsize=14, fontweight='bold')
plt.xlabel('x 轴', fontsize=12)
plt.ylabel('y 轴', fontsize=12)
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
quickstart_pyplot.png

pyplot 方式背后维护着一个"当前图形"和"当前坐标轴"的全局状态。每次调用 plt.plot() 都在当前坐标轴上绘图,调用 plt.figure() 创建新图形。这种方式上手简单,但在创建多子图或复杂布局时容易出错。

面向对象方式

面向对象方式直接操作 FigureAxes 对象,是 Matplotlib 官方推荐的方式。它不依赖全局状态,代码更清晰可控,适合复杂图表和批量出图。

fig, ax = plt.subplots(figsize=(8, 5))
ax.plot(x, y1, label='sin(x)', color='#2196F3', linewidth=2)
ax.plot(x, y2, label='cos(x)', color='#FF5722', linewidth=2, linestyle='--')
ax.set_title('面向对象风格绘图(推荐)', fontsize=14, fontweight='bold')
ax.set_xlabel('x 轴', fontsize=12)
ax.set_ylabel('y 轴', fontsize=12)
ax.legend()
ax.grid(True, alpha=0.3)
fig.tight_layout()
fig.show()
quickstart_oo.png

对比可见,OO 方式将操作显式绑定到具体的 ax(坐标轴)对象上,而不是隐式地操作全局状态。fig 是整个画布,ax 是画布上的坐标轴区域,一个 fig 可以包含多个 ax

推荐使用面向对象方式,原因如下:

  • 多子图场景下,每个子图有独立的 ax,不会互相干扰

  • 函数封装更自然——把 ax 作为参数传入,而不是依赖全局状态

  • 与高级布局工具(GridSpec、inset axes)配合更顺畅

  • 符合 Python "显式优于隐式" 的设计哲学

在数学建模论文排版中,经常需要在一张图上拼接多个子图,或者将绘图逻辑封装成函数反复调用,OO 方式能大幅减少调试时间。

1.2 中文字体配置

Matplotlib 默认使用英文字体,中文显示会变成方块(□□□)。这是因为 Matplotlib 的字体管理器找不到合适的中文字体。以下是两种常用配置方法。

方法一:全局设置(推荐)

import matplotlib.pyplot as plt

# 设置全局字体
plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']  # SimHei 是黑体
plt.rcParams['axes.unicode_minus'] = False  # 正常显示负号

通过修改 rcParams,所有后续创建的图表都会自动使用中文字体。SimHei(黑体)是 Windows 系统自带的中文字体;macOS 可用 'PingFang SC';Linux 可用 'WenQuanYi Micro Hei'

axes.unicode_minus 必须设为 False,否则坐标轴上的负号会显示为方块。这是因为某些中文字体没有 Unicode 负号(−)的字形,只有 ASCII 连字符(-)。

方法二:自动检测可用字体

如果你的系统字体不确定是否包含 SimHei,可以编写一个检测函数:

import matplotlib.font_manager as fm

def find_chinese_font():
    candidates = ['SimHei', 'Microsoft YaHei', 'WenQuanYi Micro Hei',
                  'Noto Sans CJK SC', 'SimSun', 'FangSong', 'KaiTi']
    for name in candidates:
        path = fm.findfont(fm.FontProperties(family=name))
        if 'default' not in path.lower():
            return name
    return 'SimHei'  # fallback

font = find_chinese_font()
plt.rcParams['font.sans-serif'] = [font, 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False

FontManager.findfont() 会返回字体文件的完整路径。如果字体不存在,它返回默认字体路径(包含 "default" 或 "unknown"),借此可以判断字体是否可用。

验证中文显示

配置完成后,用下面的代码验证中文是否正常显示:

import matplotlib.pyplot as plt
import numpy as np

# 全局字体设置
plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False

fig, ax = plt.subplots(figsize=(6, 4))
categories = ['一月份', '二月份', '三月份', '四月份', '五月份']
values = [4200, 5800, 6100, 7500, 8200]
colors = ['#4CAF50', '#FF9800', '#2196F3', '#9C27B0', '#F44336']
bars = ax.bar(categories, values, color=colors, edgecolor='white', linewidth=1.5)
ax.set_title('中文字体显示验证', fontsize=14, fontweight='bold')
ax.set_ylabel('销售额(元)', fontsize=12)
ax.set_xlabel('月份', fontsize=12)
for bar, val in zip(bars, values):
    ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 80,
            f'{val}', ha='center', va='bottom', fontsize=11, fontweight='bold')
ax.grid(True, axis='y', alpha=0.3)
fig.tight_layout()
fig.show()
quickstart_chinese.png

标题、轴标签、刻度、数据标注全部正常显示中文,负号也能正确显示。在后续所有章节中,我们都将使用这套全局字体配置。

1.3 本节小结

对比项

pyplot 方式

面向对象方式

API 风格

plt.plot() 等函数调用

ax.plot() 等对象方法

全局状态

依赖(当前图形/坐标轴)

不依赖

多子图

容易混淆

清晰独立

封装复用

较困难

自然

适用场景

快速探索、简单图

论文排版、复杂布局

关键要点:

  • 全局设置中文字体后,后续所有图表自动生效

  • 记得同时设置 axes.unicode_minus = False

  • 后续章节统一采用面向对象方式编写代码

第2节 折线图

折线图是展示数据随连续变量变化趋势最常用的图表,广泛应用于时间序列分析、信号处理、函数可视化等场景。

2.1 基础折线与线型

ax.plot() 是绘制折线图的核心方法。通过 linestyle(或简写 ls)参数可以控制线型,color 控制颜色,linewidth(或简写 lw)控制线宽。

import matplotlib.pyplot as plt
import numpy as np

plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False

x = np.linspace(0, 12*np.pi, 500)

fig, ax = plt.subplots(figsize=(8, 5))
ax.plot(x, np.sin(x), label='sin(x)', color='#2196F3', lw=2, linestyle='-')
ax.plot(x, np.cos(x), label='cos(x)', color='#FF5722', lw=2, linestyle='--')
ax.plot(x, np.sin(x)*np.exp(-x/10), label='衰减正弦波', color='#4CAF50', lw=2, ls='-.')
ax.plot(x, np.cos(x)*np.exp(-x/15), label='衰减余弦波', color='#9C27B0', lw=2, ls=':')
ax.set_title('折线图基础 — 多种线型与颜色', fontsize=14, fontweight='bold')
ax.set_xlabel('x', fontsize=12)
ax.set_ylabel('y', fontsize=12)
ax.legend(loc='upper right', frameon=True, shadow=True)
ax.grid(True, alpha=0.3)
fig.tight_layout()
fig.show()
lineplot_styles.png

Matplotlib 支持四种基础线型:

  • - 实线(默认)

  • -- 虚线

  • -. 点划线

  • : 点线

在数模论文中,多线图经常需要区分多条曲线。除了颜色区分,建议同时使用不同线型,因为论文打印后颜色可能不易区分。

2.2 多数据线与置信区间

实际数据分析中,经常需要在同一坐标系中绘制多条折线,并用填充区域表示置信区间或误差范围。ax.fill_between() 方法用于填充两条曲线之间的区域。

fig, ax = plt.subplots(figsize=(8, 5))
x_fill = np.linspace(0, 4*np.pi, 200)
y_upper = np.sin(x_fill) + 0.5  # 预测上限
y_lower = np.sin(x_fill) - 0.5  # 预测下限

ax.plot(x_fill, y_upper, color='#2196F3', linewidth=2, label='上限')
ax.plot(x_fill, y_lower, color='#FF5722', linewidth=2, label='下限')
ax.fill_between(x_fill, y_upper, y_lower,
                color='#2196F3', alpha=0.15, label='置信区间')
ax.set_title('折线图 — 多数据线与置信区间填充', fontsize=14, fontweight='bold')
ax.set_xlabel('时间', fontsize=12)
ax.set_ylabel('温度(℃)', fontsize=12)
ax.legend(loc='best')
ax.grid(True, alpha=0.3)
fig.tight_layout()
fig.show()
lineplot_multiline.png

fill_between() 的关键参数:

  • 前三个参数为 x 和两条 y 边界

  • color 指定填充颜色

  • alpha 控制透明度(0 完全透明,1 完全不透明)

  • where 参数可以按条件选择性填充

where 参数在大数据可视化中非常实用。例如只填充预测值超过阈值的部分:

ax.fill_between(x_fill, y_upper, y_lower,
                where=y_upper > 0.3,
                color='red', alpha=0.2, label='超出阈值区域')

2.3 带标记的折线图

当数据点较少时,在折线上添加标记点可以突出关键数据。marker 参数控制标记形状。

x_sparse = np.linspace(0, 8, 9)
y_data = np.array([2.1, 3.5, 2.8, 5.2, 4.1, 6.8, 5.5, 8.2, 7.6])

fig, ax = plt.subplots(figsize=(8, 5))
ax.plot(x_sparse, y_data,
        marker='o', markersize=8, linewidth=2,
        color='#FF5722',
        markerfacecolor='white',      # 标记内部白色
        markeredgecolor='#FF5722',    # 标记边框颜色
        markeredgewidth=2,             # 标记边框宽度
        label='观测值')
ax.set_title('带标记点的折线图', fontsize=14, fontweight='bold')
ax.set_xlabel('采样序号', fontsize=12)
ax.set_ylabel('传感器读数', fontsize=12)
ax.legend()
ax.grid(True, alpha=0.3)
fig.tight_layout()
fig.show()
lineplot_markers.png

常用标记形状:

标记符号

形状

说明

'o'

圆形

最常用

's'

方形

'^'

三角向上

'D'

菱形

'*'

星形

'+'

加号

'x'

叉号

空心标记技巧:同时设置 markerfacecolor='white'markeredgecolor 即可得到空心效果,视觉上更轻盈,适合数据点密集的场景。

2.4 折线图速查表

功能

代码示例

基本折线

ax.plot(x, y)

设置颜色

color='red'color='#FF5722'

设置线型

linestyle='--'ls=':'

设置线宽

linewidth=2lw=2

添加标记

marker='o'

标记大小

markersize=8ms=8

标记空心

markerfacecolor='white', markeredgecolor='red'

填充区域

ax.fill_between(x, y1, y2, alpha=0.2)

条件填充

ax.fill_between(x, y1, y2, where=y1>0)

2.5 本节小结

  • 折线图用 ax.plot() 绘制,核心参数是 linestylecolorlinewidth

  • 数模论文中建议颜色 + 线型双重区分,保证黑白打印后可读

  • fill_between() 用于置信区间、误差范围等区域填充

  • 数据点稀疏时用标记点突出数据,密集时用空心标记减轻视觉负担

第3节 散点图

散点图用于展示两个变量之间的关系,是探索数据分布、发现相关性、聚类分析等任务的首选图表。

3.1 基础散点图

ax.scatter() 是绘制散点图的核心方法。最基本的用法只需要 x 和 y 两个数组。

import matplotlib.pyplot as plt
import numpy as np

plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False

np.random.seed(42)
x1 = np.random.randn(100)
y1 = 2 * x1 + np.random.randn(100) * 0.5  # 正相关
x2 = np.random.randn(100) + 3
y2 = -1.5 * (x2 - 3) + np.random.randn(100) * 0.8 + 5  # 负相关

fig, ax = plt.subplots(figsize=(8, 5))
ax.scatter(x1, y1, color='#2196F3', s=50, alpha=0.7,
           label='类别 A', edgecolors='white', linewidth=1)
ax.scatter(x2, y2, color='#FF5722', s=50, alpha=0.7,
           label='类别 B', edgecolors='white', linewidth=1)
ax.set_title('基础散点图 — 两类数据分布', fontsize=14, fontweight='bold')
ax.set_xlabel('特征 X1', fontsize=12)
ax.set_ylabel('特征 X2', fontsize=12)
ax.legend()
ax.grid(True, alpha=0.3)
fig.tight_layout()
fig.show()
scatter_basic.png

散点图的两个核心参数:

  • s:点的大小(标量或数组),默认 40

  • alpha:透明度(0~1),数据密集时调低可避免遮挡

  • edgecolors:点边缘颜色,设白色可让重叠点更清晰

3.2 颜色映射散点图

当散点需要展示第三维数据时,可以用颜色映射。c 参数传入数值数组,cmap 选择配色方案。

np.random.seed(0)
x = np.random.rand(200)
y = np.random.rand(200)
z = np.sqrt((x - 0.5)**2 + (y - 0.5)**2)  # 到中心点的距离

fig, ax = plt.subplots(figsize=(7, 6))
sc = ax.scatter(x, y, c=z, cmap='viridis', s=60, alpha=0.8, edgecolors='none')
cbar = fig.colorbar(sc, ax=ax, label='到中心距离')
ax.set_title('颜色映射散点图', fontsize=14, fontweight='bold')
ax.set_xlabel('X 坐标', fontsize=12)
ax.set_ylabel('Y 坐标', fontsize=12)
ax.set_aspect('equal')
ax.grid(True, alpha=0.3)
fig.tight_layout()
fig.show()
scatter_colormap.png

关键要点:

  • scatter() 返回一个 PathCollection 对象,将其传给 fig.colorbar() 即可添加颜色条

  • cmap 选择配色方案,常用选项:

    • 'viridis':从紫色到黄色,感知均匀(默认推荐)

    • 'plasma':从紫色到黄色,更鲜艳

    • 'coolwarm':从蓝色到红色,适合正负值

    • 'jet':彩虹色,不推荐(感知不均匀)

  • edgecolors='none' 去除点的边框,大面积散点时渲染更快

在大数据场景(上万数据点)中,建议结合 alpha 透明度来观察密度分布:越密集的区域颜色越深。

3.3 气泡图

气泡图是散点图的扩展,用点的大小表示第三个维度的数值,颜色表示第四个维度,实现四维数据可视化。

np.random.seed(1)
n = 25
x = np.random.randint(1, 10, n)        # X 维度
y = np.random.randint(100, 1000, n)     # Y 维度(GDP)
sizes = np.random.randint(50, 500, n)   # 气泡大小(人口)
values = np.random.randn(n)             # 颜色值(指标)

fig, ax = plt.subplots(figsize=(8, 6))
sc = ax.scatter(x, y, s=sizes, c=values, cmap='coolwarm',
                alpha=0.6, edgecolors='gray', linewidth=1)
cbar = fig.colorbar(sc, ax=ax, label='指标值')
ax.set_title('气泡图 — 三维数据可视化', fontsize=14, fontweight='bold')
ax.set_xlabel('城市编号', fontsize=12)
ax.set_ylabel('GDP(亿元)', fontsize=12)
ax.grid(True, alpha=0.3)
fig.tight_layout()
fig.show()
scatter_bubble.png

气泡图的四维映射关系:

视觉通道

数据维度

参数

X 位置

第一维

x

Y 位置

第二维

y

气泡大小

第三维

s

气泡颜色

第四维

c + cmap

在数模比赛中,气泡图非常适合展示城市经济数据(编号→GDP→人口→增长率),一图容纳多维度信息。注意气泡大小 s 的单位是像素平方,通常需要通过实验调整缩放因子使气泡大小合适。

3.4 散点图速查表

功能

代码示例

基础散点

ax.scatter(x, y)

设置大小

s=50s=np.array([...])

设置颜色

color='blue'c=values, cmap='viridis'

设置透明度

alpha=0.5

空心点

facecolors='none', edgecolors='red'

添加颜色条

cbar = fig.colorbar(sc, ax=ax)

等比例坐标轴

ax.set_aspect('equal')

3.5 本节小结

  • 散点图用 ax.scatter() 绘制,适合探索变量间关系

  • 大数据量时调低 alpha 观察密度分布

  • 颜色映射用 c + cmap,避免用 'jet' 配色

  • 气泡图通过 sc 参数实现多维数据可视化

  • 数模论文中散点图常与回归线配合使用(见第2节折线图)

第4节 柱状图

柱状图是展示分类数据对比最常用的图表,适用于排名比较、组成分析、误差展示等场景。

4.1 基础柱状图

ax.bar() 绘制垂直柱状图。最基本的用法需要传入类别标签和对应的数值。

import matplotlib.pyplot as plt
import numpy as np

plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False

categories = ['北京', '上海', '广州', '深圳', '杭州']
values = [12500, 11800, 9200, 10600, 8500]
colors = ['#4CAF50', '#2196F3', '#FF9800', '#F44336', '#9C27B0']

fig, ax = plt.subplots(figsize=(8, 5))
bars = ax.bar(categories, values, color=colors, edgecolor='white', linewidth=1.5)
ax.set_title('各城市 GDP 对比(亿元)', fontsize=14, fontweight='bold')
ax.set_ylabel('GDP(亿元)', fontsize=12)
for bar, val in zip(bars, values):
    ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 150,
            f'{val}', ha='center', va='bottom', fontsize=11, fontweight='bold')
ax.grid(True, axis='y', alpha=0.3)
fig.tight_layout()
fig.show()
bar_basic.png

关键点:

  • bar() 返回 BarContainer 对象,可遍历每个 bar 获取位置信息

  • ax.text() 在柱顶添加数值标注,bar.get_x() + bar.get_width()/2 是柱子中心

  • edgecolor='white' 给柱子加白色边框,视觉效果更清爽

  • 柱状图建议只保留 y 轴网格线,避免 x 轴网格线干扰柱子视觉

4.2 分组柱状图

分组柱状图用于在相同分类下对比多个数据系列。关键是计算每个系列柱子的 x 位置偏移。

categories = ['一季度', '二季度', '三季度', '四季度']
product_a = [320, 450, 380, 520]
product_b = [280, 390, 420, 480]
x_pos = np.arange(len(categories))
width = 0.35

fig, ax = plt.subplots(figsize=(8, 5))
ax.bar(x_pos - width/2, product_a, width, label='产品 A', color='#2196F3')
ax.bar(x_pos + width/2, product_b, width, label='产品 B', color='#FF5722')
ax.set_title('分组柱状图 — 两种产品季度销量对比', fontsize=14, fontweight='bold')
ax.set_ylabel('销量(万件)', fontsize=12)
ax.set_xticks(x_pos)
ax.set_xticklabels(categories)
ax.legend()
ax.grid(True, axis='y', alpha=0.3)
fig.tight_layout()
fig.show()
bar_grouped.png

分组柱状图的核心技巧:

  • np.arange() 生成整数 x 位置

  • 每个系列的 x 位置向左右偏移 width/2

  • 如果有 n 个系列,总宽度为 n * width,需调整 width 避免重叠

  • set_xticks()set_xticklabels() 手动设置刻度标签

4.3 误差棒柱状图

在数模论文中展示实验结果时,经常需要在柱状图上添加误差棒表示标准差或置信区间。yerr 参数直接传入误差值即可。

categories = ['算法1', '算法2', '算法3', '算法4', '算法5']
mean_acc = [0.82, 0.88, 0.91, 0.85, 0.79]
std_acc = [0.03, 0.02, 0.04, 0.05, 0.03]

fig, ax = plt.subplots(figsize=(8, 5))
bars = ax.bar(categories, mean_acc, yerr=std_acc, capsize=8,
              color='#2196F3', edgecolor='white', linewidth=1.5,
              error_kw={'elinewidth': 2, 'ecolor': '#333333'})
ax.set_title('算法准确率对比(含标准差误差棒)', fontsize=14, fontweight='bold')
ax.set_ylabel('准确率', fontsize=12)
ax.set_ylim(0, 1.1)
for i, (bar, val) in enumerate(zip(bars, mean_acc)):
    ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + std_acc[i] + 0.03,
            f'{val:.2f}', ha='center', va='bottom', fontsize=11, fontweight='bold')
ax.grid(True, axis='y', alpha=0.3)
fig.tight_layout()
fig.show()
bar_errorbar.png

误差棒关键参数:

  • yerr:误差值,可以是标量(所有柱子相同)或数组(每个柱子不同)

  • capsize:误差棒顶部横线宽度(点单位)

  • error_kw:字典,控制误差棒样式(elinewidth 线宽、ecolor 颜色)

数模论文中展示算法对比时,误差棒是必须的,它告诉读者结果的稳定性。没有误差棒的柱状图在学术评审中通常被认为不够严谨。

4.4 水平柱状图

当类别标签较长或类别数量较多时,水平柱状图(barh())是更好的选择。

fig, ax = plt.subplots(figsize=(8, 5))
bars_h = ax.barh(categories, values, color=colors[::-1],
                 edgecolor='white', linewidth=1.5)
ax.set_title('水平柱状图 — 城市 GDP 排名', fontsize=14, fontweight='bold')
ax.set_xlabel('GDP(亿元)', fontsize=12)
for bar, val in zip(bars_h, values[::-1]):
    ax.text(bar.get_width() + 100, bar.get_y() + bar.get_height()/2,
            f'{val}', va='center', fontsize=11, fontweight='bold')
ax.grid(True, axis='x', alpha=0.3)
fig.tight_layout()
fig.show()
bar_horizontal.png

barh()bar() 的区别:

  • 参数顺序变为 (y, width) 而非 (x, height)

  • 文本标注改用 bar.get_width()bar.get_y()

  • 网格线改为 x 轴方向

4.5 柱状图速查表

功能

代码示例

垂直柱状图

ax.bar(x, height)

水平柱状图

ax.barh(y, width)

设置颜色

color='blue' 或颜色列表

柱宽

width=0.5(0~1 之间)

误差棒

yerr=std_values, capsize=8

柱顶数值

ax.text(bar.get_x() + w/2, h + offset, text)

堆叠柱状图

第二个 bar()bottom=前一个柱子高度

4.6 本节小结

  • 垂直柱状图用 bar(),水平用 barh(),长标签用水平

  • 分组柱状图用 np.arange() + 偏移量计算位置

  • 数模论文中实验对比必须加误差棒(yerr 参数)

  • 柱顶标注数值时注意与误差棒不重叠

第5节 饼图与环形图

饼图用于展示部分与整体的比例关系,适合类别数量较少(通常不超过 7 个)的场景。

5.1 基础饼图

ax.pie() 绘制饼图。最基本的用法只需传入各部分的数值数组。

import matplotlib.pyplot as plt
import numpy as np

plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False

labels = ['直接访问', '搜索引擎', '社交媒体', '邮件营销', '其他']
sizes = [335, 410, 280, 150, 95]
colors = ['#2196F3', '#4CAF50', '#FF9800', '#9C27B0', '#607D8B']
explode = (0.08, 0, 0, 0, 0)

fig, ax = plt.subplots(figsize=(8, 6))
wedges, texts, autotexts = ax.pie(
    sizes, labels=labels, colors=colors,
    explode=explode, autopct='%1.1f%%',
    startangle=90,
    textprops={'fontsize': 11},
    pctdistance=0.75,
    wedgeprops={'edgecolor': 'white', 'linewidth': 2})
for autotext in autotexts:
    autotext.set_color('white')
    autotext.set_fontweight('bold')
    autotext.set_fontsize(12)
ax.set_title('网站流量来源分布', fontsize=14, fontweight='bold', pad=20)
fig.show()
pie_basic.png

pie() 的关键参数:

  • explode:元组,控制每块饼的"突出"程度(0 为不突出,值越大越突出)

  • autopct:格式化字符串,'%1.1f%%' 表示保留 1 位小数的百分比

  • startangle:起始角度(度),90 表示从 12 点钟方向开始

  • pctdistance:百分比文字距圆心的相对距离(0~1)

  • wedgeprops:饼块样式字典,edgecolor='white' 添加白色分隔线

pie() 返回三个对象:wedges(饼块)、texts(标签)、autotexts(百分比文字),可以分别设置样式。

5.2 环形图

环形图是饼图的变体,中间挖空。视觉上更现代,中间区域可用于放置标题或汇总信息。

labels = ['CPU', '内存', '硬盘', '网络', '其他']
sizes = [35, 25, 20, 12, 8]
colors = ['#F44336', '#2196F3', '#4CAF50', '#FF9800', '#9E9E9E']

fig, ax = plt.subplots(figsize=(7, 7))
wedges, texts, autotexts = ax.pie(
    sizes, labels=labels, colors=colors,
    autopct='%1.1f%%', startangle=90,
    pctdistance=0.8,
    wedgeprops={'width': 0.45, 'edgecolor': 'white', 'linewidth': 2},
    textprops={'fontsize': 12})
for autotext in autotexts:
    autotext.set_color('white')
    autotext.set_fontweight('bold')
    autotext.set_fontsize(13)
ax.text(0, 0, '资源\n消耗', ha='center', va='center',
        fontsize=16, fontweight='bold')
ax.set_title('服务器资源环形图', fontsize=14, fontweight='bold', pad=20)
fig.show()
pie_donut.png

环形图的关键在于 wedgeprops={'width': 0.45}

  • width 控制环的宽度(0~1),值越小环越细

  • 中心空白区域用 ax.text(0, 0, ...) 添加文字

  • 环形图比饼图更节省空间,适合嵌入仪表盘或报告

5.3 嵌套饼图

嵌套饼图用两层(或多层)饼展示层级数据,外圈表示大类,内圈表示子类。

# 外圈:大类
labels_outer = ['前端', '后端', '数据']
sizes_outer = [40, 35, 25]
colors_outer = ['#2196F3', '#4CAF50', '#FF9800']
# 内圈:子类
labels_inner = ['Vue', 'React', 'Django', 'Flask', 'MySQL', 'MongoDB']
sizes_inner = [22, 18, 20, 15, 15, 10]
colors_inner = ['#64B5F6', '#42A5F5', '#81C784', '#66BB6A', '#FFB74D', '#FFA726']

fig, ax = plt.subplots(figsize=(8, 7))
wedges1, texts1 = ax.pie(sizes_outer, radius=1.0, labels=labels_outer,
                         colors=colors_outer, startangle=90,
                         wedgeprops={'width': 0.3, 'edgecolor': 'white'},
                         textprops={'fontsize': 13, 'fontweight': 'bold'})
wedges2, texts2, _ = ax.pie(sizes_inner, radius=0.65, labels=labels_inner,
                            colors=colors_inner, startangle=90,
                            wedgeprops={'width': 0.25, 'edgecolor': 'white'},
                            textprops={'fontsize': 10},
                            autopct='%1.0f%%')
ax.set_title('技术栈分布 — 嵌套饼图', fontsize=14, fontweight='bold', pad=20)
fig.show()
pie_nested.png

嵌套饼图技巧:

  • 调用两次 pie(),分别设置不同的 radius(外圈半径大)

  • width 参数让饼块变为环形

  • 内圈可以用 autopct 显示比例,外圈只显示标签

  • 注意内外圈的数据比例要一致(内圈各子类之和 = 外圈对应大类的值)

5.4 饼图速查表

功能

代码示例

基础饼图

ax.pie(sizes)

添加标签

labels=labels_list

百分比标注

autopct='%1.1f%%'

突出某块

explode=(0.1, 0, 0, ...)

起始角度

startangle=90

环形图

wedgeprops={'width': 0.4}

自定义颜色

colors=['#hex1', '#hex2', ...]

返回对象

wedges, texts, autotexts = ax.pie(...)

5.5 本节小结

  • 饼图适合类别少(≤ 7 类)的比例展示,类别多时改用柱状图

  • explode 参数可突出关键数据块

  • 环形图通过 wedgeprops={'width': ...} 实现,中间可放置汇总信息

  • 嵌套饼图适合展示层级数据,外圈大类、内圈子类

  • 数模论文中饼图常用于展示资源分配、构成比例分析

第6节 直方图

直方图用于展示连续型数据的分布特征,是数据分析中探索数据分布形态、发现异常值、判断正态性的基础工具。

6.1 基础直方图

ax.hist() 绘制直方图。最基本的用法只需传入一维数据数组。

import matplotlib.pyplot as plt
import numpy as np

plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False

np.random.seed(42)
data = np.random.normal(loc=50, scale=10, size=1000)

fig, ax = plt.subplots(figsize=(8, 5))
n, bins, patches = ax.hist(data, bins=30, color='#2196F3',
                           edgecolor='white', alpha=0.8)
ax.set_title('学生考试成绩分布(N=1000)', fontsize=14, fontweight='bold')
ax.set_xlabel('分数', fontsize=12)
ax.set_ylabel('频数', fontsize=12)
ax.axvline(data.mean(), color='red', linestyle='--', linewidth=2,
           label=f'均值 = {data.mean():.1f}')
ax.axvline(np.median(data), color='green', linestyle=':', linewidth=2,
           label=f'中位数 = {np.median(data):.1f}')
ax.legend()
ax.grid(True, axis='y', alpha=0.3)
fig.tight_layout()
fig.show()
hist_basic.png

hist() 返回三个值:

  • n:每个区间的频数(计数)

  • bins:区间边界数组(长度为 n+1

  • patches:每个柱子的 Rectangle 对象列表

常用参数:

  • bins:分箱数量(整数)或边界数组。数据量大时建议 30~50 箱,数据量少时 10~20 箱

  • density:设为 True 时纵轴变为概率密度(总面积为 1)

  • edgecolor='white':加白色边框使相邻柱子分隔清晰

  • ax.axvline():添加垂直参考线,常用于标注均值、中位数

6.2 多组数据叠加直方图

对比多组数据的分布时,可以传入数据列表给 hist()histtype 参数控制叠加方式。

np.random.seed(0)
group_a = np.random.normal(loc=100, scale=15, size=800)
group_b = np.random.normal(loc=120, scale=20, size=800)
group_c = np.random.normal(loc=85, scale=10, size=800)

fig, ax = plt.subplots(figsize=(8, 5))
ax.hist([group_a, group_b, group_c], bins=30,
        label=['A 批次', 'B 批次', 'C 批次'],
        color=['#2196F3', '#FF5722', '#4CAF50'],
        alpha=0.6, edgecolor='white', histtype='bar')
ax.set_title('三批次产品质量指标分布对比', fontsize=14, fontweight='bold')
ax.set_xlabel('质量指标', fontsize=12)
ax.set_ylabel('频数', fontsize=12)
ax.legend()
ax.grid(True, axis='y', alpha=0.3)
fig.tight_layout()
fig.show()
hist_multi.png

histtype 参数可选值:

效果

'bar'

并排柱状(默认,多组数据并排显示)

'barstacked'

堆叠柱状

'step'

阶梯线图

'stepfilled'

填充阶梯线

多组数据叠加时注意:

  • 各组样本量不同会导致高度不可比,建议设 density=True 归一化

  • 用不同颜色 + 透明度区分各组分布

  • 组间均值和方差的差异一目了然

6.3 直方图 + 核密度曲线

在大数据场景下,将归一化直方图与核密度估计(KDE)曲线叠加,可以同时展示数据分布的离散特征和连续趋势。

import numpy as np
from scipy.stats import gaussian_kde

np.random.seed(1)
data = np.random.exponential(scale=2, size=2000)

fig, ax = plt.subplots(figsize=(8, 5))
n, bins, patches = ax.hist(data, bins=40, density=True,
                           color='#2196F3', alpha=0.5,
                           edgecolor='white', label='直方图(归一化)')
# 核密度估计
kde = gaussian_kde(data)
x_kde = np.linspace(bins[0], bins[-1], 300)
ax.plot(x_kde, kde(x_kde), color='#FF5722', linewidth=2.5,
        label='核密度曲线')
ax.set_title('服务器请求响应时间分布', fontsize=14, fontweight='bold')
ax.set_xlabel('响应时间(秒)', fontsize=12)
ax.set_ylabel('概率密度', fontsize=12)
ax.legend()
ax.grid(True, axis='y', alpha=0.3)
fig.tight_layout()
fig.show()
hist_kde.png

关键点:

  • density=True 使直方图纵轴变为概率密度,与 KDE 曲线量纲一致

  • gaussian_kde 来自 scipy.stats,自动估计最优带宽

  • KDE 曲线平滑地展示了数据的概率密度函数

  • 在数模论文中,这种组合图常用于展示实验数据的分布特性

注意:density=True(Matplotlib 2.1+)替代了旧的 normed=True 参数。旧代码中如果看到 normed,需要改为 density

6.4 直方图速查表

功能

代码示例

基础直方图

ax.hist(data, bins=30)

设置箱数

bins=20bins=np.arange(0, 100, 5)

概率密度

density=True

多组数据

ax.hist([data1, data2], ...)

叠加方式

histtype='bar' / 'barstacked' / 'step'

均值线

ax.axvline(data.mean(), color='red', ls='--')

中位线

ax.axvline(np.median(data), color='green', ls=':')

核密度

from scipy.stats import gaussian_kde

6.5 本节小结

  • 直方图用 ax.hist() 绘制,展示连续数据的分布形态

  • bins 控制分箱粒度,数据量大时可适当增加

  • 多组数据对比用列表传入,注意样本量不同时应归一化

  • density=True + 核密度曲线是展示分布的高级组合

  • 数模论文中直方图常用于探索性数据分析(EDA)阶段

第7节 箱线图

箱线图(Box Plot)通过五数概括(最小值、第一四分位数、中位数、第三四分位数、最大值)展示数据分布,同时标出异常值,是探索性数据分析的核心工具。

7.1 基础箱线图

ax.boxplot() 绘制箱线图。最基本用法只需传入数据列表。

import matplotlib.pyplot as plt
import numpy as np

plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False

np.random.seed(42)
data1 = np.random.normal(loc=50, scale=8, size=200)
data2 = np.random.normal(loc=55, scale=12, size=200)
data3 = np.random.normal(loc=45, scale=6, size=200)
data4 = np.random.normal(loc=60, scale=10, size=200)

fig, ax = plt.subplots(figsize=(8, 5))
bp = ax.boxplot([data1, data2, data3, data4],
                tick_labels=['实验组 A', '实验组 B', '实验组 C', '实验组 D'],
                patch_artist=True,
                boxprops=dict(facecolor='#2196F3', alpha=0.6),
                medianprops=dict(color='red', linewidth=2),
                whiskerprops=dict(color='#333', linewidth=1.5),
                capprops=dict(color='#333', linewidth=1.5),
                flierprops=dict(marker='o', color='#FF5722', alpha=0.5, markersize=5))
ax.set_title('四组实验数据箱线图对比', fontsize=14, fontweight='bold')
ax.set_ylabel('测量值', fontsize=12)
ax.grid(True, axis='y', alpha=0.3)
fig.tight_layout()
fig.show()
boxplot_basic.png

箱线图的五个关键组成部分:

  • 箱体:从 Q1(25% 分位数)到 Q3(75% 分位数),高度为 IQR

  • 中位线:箱体中的红线,表示中位数(50% 分位数)

  • 须线:从箱体延伸到非异常值的最大/最小值(默认 1.5×IQR 范围)

  • 异常值:超出须线范围的点,用圆圈标注

  • patch_artist=True:使箱体可填充颜色(否则只画边框)

样式控制通过字典参数:

  • boxprops:箱体样式

  • medianprops:中位线样式

  • whiskerprops:须线样式

  • capprops:须线末端横线样式

  • flierprops:异常值样式

7.2 分组箱线图

在数模比赛中,经常需要对比多种算法在多个指标上的表现分布。分组箱线图可以同时展示两个维度的对比。

np.random.seed(0)
n_models = 5
n_runs = 50
tick_labels = ['LR', 'SVM', 'RF', 'XGBoost', 'NN']
precision = [np.random.normal(0.80 + i*0.02, 0.03, n_runs) for i in range(n_models)]
recall = [np.random.normal(0.75 + i*0.03, 0.04, n_runs) for i in range(n_models)]

fig, ax = plt.subplots(figsize=(10, 5))
bp1 = ax.boxplot(precision, positions=np.arange(n_models) - 0.2, widths=0.35,
                 patch_artist=True,
                 boxprops=dict(facecolor='#2196F3', alpha=0.6),
                 medianprops=dict(color='red', linewidth=2),
                 flierprops=dict(marker='.', alpha=0.5, markersize=4))
bp2 = ax.boxplot(recall, positions=np.arange(n_models) + 0.2, widths=0.35,
                 patch_artist=True,
                 boxprops=dict(facecolor='#FF5722', alpha=0.6),
                 medianprops=dict(color='red', linewidth=2),
                 flierprops=dict(marker='.', alpha=0.5, markersize=4))
ax.set_xticks(np.arange(n_models))
ax.set_xticklabels(tick_labels)
ax.set_title('五种分类算法 Precision 与 Recall 分布', fontsize=14, fontweight='bold')
ax.set_ylabel('指标值', fontsize=12)
ax.legend([bp1['boxes'][0], bp2['boxes'][0]], ['Precision', 'Recall'])
ax.grid(True, axis='y', alpha=0.3)
fig.tight_layout()
fig.show()
boxplot_grouped.png

分组箱线图技巧:

  • positions 参数手动控制每个箱线图的位置

  • 两个系列的 x 位置分别偏移 -0.2+0.2,形成并排效果

  • widths 控制箱体宽度,避免重叠

  • 图例通过 bp['boxes'][0] 获取箱体对象来创建

这种图在数模论文的实验部分非常实用:一眼就能看出哪个算法不仅均值高,而且稳定性好(箱体窄、异常值少)。

7.3 水平箱线图

当类别标签较长时,水平箱线图(vert=False)更易读。

np.random.seed(1)
categories = ['算法 A', '算法 B', '算法 C', '算法 D', '算法 E']
data = [np.random.normal(loc=50 + i*5, scale=8 + i, size=150) for i in range(5)]

fig, ax = plt.subplots(figsize=(8, 5))
bp = ax.boxplot(data, vert=False,
                tick_labels=categories,
                patch_artist=True,
                boxprops=dict(facecolor='#4CAF50', alpha=0.6),
                medianprops=dict(color='red', linewidth=2),
                flierprops=dict(marker='o', color='#F44336', alpha=0.5, markersize=5))
ax.set_title('水平箱线图 — 算法性能分布', fontsize=14, fontweight='bold')
ax.set_xlabel('运行时间(秒)', fontsize=12)
ax.grid(True, axis='x', alpha=0.3)
fig.tight_layout()
fig.show()
boxplot_horizontal.png

vert=False 将箱线图旋转为水平方向,x 轴变为数值轴。

7.4 箱线图速查表

功能

代码示例

基础箱线图

ax.boxplot([data1, data2])

填充颜色

patch_artist=True, boxprops=dict(facecolor='blue')

中位线样式

medianprops=dict(color='red', lw=2)

异常值样式

flierprops=dict(marker='o', color='red', alpha=0.5)

水平方向

vert=False

手动位置

positions=[1, 3, 5]

箱体宽度

widths=0.5

异常值判定

默认 1.5×IQR 规则

7.5 本节小结

  • 箱线图用 ax.boxplot() 绘制,展示五数概括和异常值

  • patch_artist=True 是填充颜色的前提

  • 数模论文中箱线图是算法对比的标准图表,比柱状图+误差棒传递更多信息

  • 分组箱线图通过 positions 参数实现并排效果

  • 长标签用 vert=False 改为水平方向

第8节 子图与布局

数模论文和数据分析报告经常需要将多张图表组合在一个画布上。Matplotlib 提供了三种层次的子图布局方案,从简单到灵活依次增强。

8.1 基础子图:subplots()

plt.subplots() 是最常用的子图创建方式,返回一个 Figure 对象和一个 Axes 数组。

import matplotlib.pyplot as plt
import numpy as np

plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False

x = np.linspace(0, 10, 100)

fig, axes = plt.subplots(2, 2, figsize=(10, 8))
axes[0, 0].plot(x, np.sin(x), color='#2196F3', linewidth=2)
axes[0, 0].set_title('sin(x)', fontsize=12)
axes[0, 0].grid(True, alpha=0.3)

axes[0, 1].plot(x, np.cos(x), color='#FF5722', linewidth=2)
axes[0, 1].set_title('cos(x)', fontsize=12)
axes[0, 1].grid(True, alpha=0.3)

axes[1, 0].plot(x, np.exp(-x/5), color='#4CAF50', linewidth=2)
axes[1, 0].set_title('e^(-x/5)', fontsize=12)
axes[1, 0].grid(True, alpha=0.3)

axes[1, 1].plot(x, x**2, color='#9C27B0', linewidth=2)
axes[1, 1].set_title('x^2', fontsize=12)
axes[1, 1].grid(True, alpha=0.3)

fig.suptitle('2×2 基础子图布局', fontsize=14, fontweight='bold', y=1.02)
fig.tight_layout()
fig.show()
subplot_basic.png

关键点:

  • plt.subplots(nrows, ncols) 返回 (fig, axes)axes 是二维数组

  • 通过 axes[row, col] 访问每个子图的坐标轴

  • fig.suptitle() 设置整个画布的大标题

  • fig.tight_layout() 自动调整子图间距,避免标签重叠(必加

  • y=1.02 让大标题略高于画布,避免与子图标题重叠

8.2 多类型图表组合面板

数模论文的实验结果展示经常需要在一张图中组合多种图表类型。通过 fig.add_subplot() 可以灵活放置不同类型的图表。

import matplotlib.gridspec as gridspec

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

# 第一行三个子图
ax1 = fig.add_subplot(gs[0, 0])
ax1.hist(np.random.normal(0, 1, 1000), bins=30, color='#2196F3', alpha=0.7, edgecolor='white')
ax1.set_title('正态分布直方图', fontsize=12)

ax2 = fig.add_subplot(gs[0, 1])
ax2.bar(['A', 'B', 'C', 'D'], [35, 28, 22, 15],
        color=['#FF5722', '#4CAF50', '#2196F3', '#FF9800'])
ax2.set_title('分类柱状图', fontsize=12)

ax3 = fig.add_subplot(gs[0, 2])
x_sc = np.random.rand(200)
y_sc = np.random.rand(200)
z = np.sqrt((x_sc - 0.5)**2 + (y_sc - 0.5)**2)
ax3.scatter(x_sc, y_sc, c=z, cmap='viridis', s=40, alpha=0.7)
ax3.set_title('散点图(颜色映射)', fontsize=12)

# 第二行:折线图跨两列
ax4 = fig.add_subplot(gs[1, :2])
t = np.linspace(0, 4*np.pi, 200)
ax4.plot(t, np.sin(t), color='#2196F3', lw=2, label='sin')
ax4.plot(t, np.cos(t), color='#FF5722', lw=2, label='cos')
ax4.set_title('折线图组合', fontsize=12)
ax4.legend()

# 第二行:饼图
ax5 = fig.add_subplot(gs[1, 2])
ax5.pie([35, 28, 22, 15], labels=['A', 'B', 'C', 'D'],
        autopct='%1.0f%%', wedgeprops={'edgecolor': 'white'})
ax5.set_title('饼图', fontsize=12)

fig.suptitle('多类型图表组合面板', fontsize=14, fontweight='bold')
fig.show()
subplot_mixed.png

GridSpec 索引语法:

  • gs[0, 0]:第 0 行第 0 列

  • gs[0, :2]:第 0 行,第 0~1 列(跨列)

  • gs[1, :]:第 1 行,所有列

  • gs[0:2, 0]:第 0~1 行,第 0 列(跨行)

8.3 GridSpec 不规则布局

当需要主次分明的布局(一个大图 + 多个小图)时,GridSpec 提供了最大的灵活性。

fig = plt.figure(figsize=(10, 8))
gs = fig.add_gridspec(3, 3, hspace=0.35, wspace=0.3)

# 主图:占据左上 2×2 区域
ax_main = fig.add_subplot(gs[0:2, 0:2])
x = np.linspace(0, 10, 200)
ax_main.plot(x, np.sin(x)*np.exp(-x/8), color='#2196F3', lw=2.5)
ax_main.fill_between(x, 0, np.sin(x)*np.exp(-x/8), alpha=0.2, color='#2196F3')
ax_main.set_title('主图 — 衰减正弦波', fontsize=13, fontweight='bold')
ax_main.set_xlabel('时间', fontsize=11)
ax_main.set_ylabel('幅值', fontsize=11)
ax_main.grid(True, alpha=0.3)

# 右侧两个小图
ax_small1 = fig.add_subplot(gs[0, 2])
ax_small1.bar(['A', 'B', 'C'], [30, 45, 25], color='#4CAF50')
ax_small1.set_title('柱状图', fontsize=11)

ax_small2 = fig.add_subplot(gs[1, 2])
ax_small2.scatter(np.random.randn(80), np.random.randn(80),
                  c=np.random.randn(80), cmap='coolwarm', s=30, alpha=0.7)
ax_small2.set_title('散点图', fontsize=11)

# 底部横跨3列
ax_bottom = fig.add_subplot(gs[2, :])
x2 = np.linspace(0, 2*np.pi, 100)
ax_bottom.plot(x2, np.sin(3*x2), color='#FF5722', lw=2)
ax_bottom.plot(x2, np.cos(3*x2), color='#2196F3', lw=2)
ax_bottom.set_title('底部 — 高频信号对比', fontsize=13)
ax_bottom.grid(True, alpha=0.3)

fig.suptitle('GridSpec 不规则布局', fontsize=14, fontweight='bold')
fig.show()
subplot_gridspec.png

GridSpec 的核心优势:

  • 将画布划分为 n×m 网格,每个子图可以占据任意矩形区域

  • 适合论文中"主结果图 + 辅助分析图"的排版需求

  • hspacewspace 控制行间距和列间距

  • 可以混合使用不同图表类型,视觉层次分明

8.4 子图布局速查表

功能

代码示例

创建 2×2 子图

fig, axes = plt.subplots(2, 2)

访问子图

axes[0, 1]

画布大标题

fig.suptitle('标题')

自动调整间距

fig.tight_layout()

GridSpec 网格

gs = fig.add_gridspec(2, 3)

添加子图

ax = fig.add_subplot(gs[0, 1])

跨列

gs[0, :2]gs[:, 0]

跨行

gs[0:2, 0]

行间距

fig.add_gridspec(2, 2, hspace=0.3, wspace=0.3)

8.5 本节小结

  • subplots() 适合规则网格布局,返回 axes 数组方便循环操作

  • GridSpec 适合不规则布局,主图+小图组合

  • fig.tight_layout() 是避免标签重叠的关键(经常忘记)

  • 数模论文中常用"大图展示主要结果,小图展示辅助分析"的布局策略

第9节 图表美化

数模论文和学术报告对图表的美观度有较高要求。本节系统介绍 Matplotlib 的图表美化方法,从基础元素到整体风格。

9.1 完整美化示例

一个高质量的图表需要在标题、标签、坐标轴、图例、网格、标注等多个方面精细调整。

import matplotlib.pyplot as plt
import numpy as np

plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False

np.random.seed(42)
x = np.linspace(0, 10, 200)
y1 = np.sin(x) * np.exp(-x/8)
y2 = np.cos(x) * np.exp(-x/10)

fig, ax = plt.subplots(figsize=(10, 6))

# 绘制数据线 + 填充区域
ax.plot(x, y1, color='#2196F3', linewidth=2.5, label='信号 A')
ax.plot(x, y2, color='#FF5722', linewidth=2.5, label='信号 B')
ax.fill_between(x, y1, y2, where=y1 > y2, color='#2196F3', alpha=0.1)
ax.fill_between(x, y1, y2, where=y1 <= y2, color='#FF5722', alpha=0.1)

# 标题与标签
ax.set_title('信号对比分析', fontsize=16, fontweight='bold', pad=15)
ax.set_xlabel('时间(秒)', fontsize=13, labelpad=10)
ax.set_ylabel('幅值', fontsize=13, labelpad=10)

# 坐标轴美化:隐藏上、右边框
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.spines['left'].set_linewidth(1.5)
ax.spines['bottom'].set_linewidth(1.5)
ax.tick_params(axis='both', which='major', labelsize=12, length=6, width=1.5)

# 参考线
ax.axhline(y=0, color='gray', linestyle='-', linewidth=0.8, alpha=0.5)
ax.axvline(x=5, color='gray', linestyle='--', linewidth=1, alpha=0.5,
           label='t = 5 时刻')

# 标注关键点
peak_x = x[np.argmax(y1)]
peak_y = np.max(y1)
ax.annotate(f'峰值 ({peak_x:.1f}, {peak_y:.2f})',
            xy=(peak_x, peak_y), xytext=(peak_x + 1, peak_y + 0.15),
            fontsize=11, fontweight='bold', color='#2196F3',
            arrowprops=dict(arrowstyle='->', color='#2196F3', lw=1.5))

# 图例
ax.legend(loc='upper right', fontsize=12, frameon=True,
          framealpha=0.9, shadow=True, edgecolor='gray')

# 网格
ax.grid(True, linestyle=':', alpha=0.4)

fig.tight_layout()
fig.show()
beautify_complete.png

坐标轴美化是学术图表的关键步骤:

  • spines['top'].set_visible(False)spines['right'].set_visible(False) 隐藏上、右边框,使图表更开放清爽(这是现代学术图表的标准做法)

  • tick_params() 统一控制刻度样式:labelsize 字号、length 刻度线长度、width 线宽

  • labelpad 控制轴标签与坐标轴的距离

  • ax.annotate() 添加带箭头的文字标注,xy 是目标点,xytext 是文字位置

  • arrowprops 控制箭头样式,arrowstyle='->' 是简洁的单向箭头

9.2 内置风格

Matplotlib 提供了多种内置风格,一键切换图表整体外观。

styles = ['default', 'seaborn-v0_8', 'grayscale', 'classic']
fig, axes = plt.subplots(2, 2, figsize=(12, 10))

for ax, style in zip(axes.flat, styles):
    plt.style.use(style)
    # style 会覆盖字体配置,需重新设置
    plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']
    x = np.linspace(0, 2*np.pi, 100)
    ax.plot(x, np.sin(x), linewidth=2, label='sin(x)')
    ax.plot(x, np.cos(x), linewidth=2, label='cos(x)')
    ax.set_title(f'style: {style}', fontsize=13)
    ax.legend(fontsize=10)
    ax.grid(True, alpha=0.3)

plt.style.use('default')  # 恢复默认
fig.suptitle('Matplotlib 内置风格对比', fontsize=15, fontweight='bold')
fig.tight_layout()
fig.show()
beautify_styles.png

常用内置风格:

风格

特点

适用场景

default

原始 Matplotlib 风格

默认

seaborn-v0_8

Seaborn 风格,带网格和柔和配色

数据探索

grayscale

灰度风格

黑白打印

classic

经典 MATLAB 风格

怀旧/对比

重要提示plt.style.use() 会覆盖全局字体配置,切换风格后需重新设置 font.sans-serif

查看所有可用风格:plt.style.available

9.3 配色方案

Matplotlib 提供了丰富的配色方案(colormap),分为连续型、发散型和定性型三类。

fig, axes = plt.subplots(1, 3, figsize=(14, 5))
x = np.linspace(0, 10, 100)
colormaps = ['viridis', 'plasma', 'coolwarm']

for ax, cmap_name in zip(axes, colormaps):
    cmap = plt.get_cmap(cmap_name)
    for i in range(8):
        y = np.sin(x + i * 0.5) * np.exp(-x/10) + i * 0.5
        ax.plot(x, y, color=cmap(i/7), linewidth=2.5)
    ax.set_title(f'配色方案: {cmap_name}', fontsize=13, fontweight='bold')
    ax.grid(True, alpha=0.3)

fig.suptitle('Matplotlib 常用配色方案', fontsize=15, fontweight='bold')
fig.tight_layout()
fig.show()
beautify_colormaps.png

常用配色方案分类:

  • 连续型viridis(推荐)、plasmamagmainfernocividis

  • 发散型coolwarmRdBuPiYG(适合正负值数据)

  • 定性型tab10tab20Set1Set2(适合分类数据)

  • 序列型BluesGreensOrangesReds(单色渐变)

数模论文配色建议:

  • 优先使用 viridis,感知均匀且色盲友好

  • 避免使用 jet(彩虹色),感知不均匀且容易引起误导

  • 多线图用 tab10Set1 获取区分度高的颜色

  • 论文打印前用 grayscale 风格预览,确保黑白打印后仍可区分

9.4 美化速查表

功能

代码示例

标题加粗

ax.set_title('标题', fontweight='bold')

隐藏边框

ax.spines['top'].set_visible(False)

刻度字号

ax.tick_params(labelsize=12)

标签间距

ax.set_xlabel('x', labelpad=10)

参考线

ax.axhline(y=0, color='gray', ls='--')

文字标注

ax.annotate('文本', xy=(x,y), xytext=(xt,yt), arrowprops={...})

图例阴影

ax.legend(shadow=True, framealpha=0.9)

虚线网格

ax.grid(True, linestyle=':', alpha=0.4)

切换风格

plt.style.use('seaborn-v0_8')

获取配色

cmap = plt.get_cmap('viridis'); cmap(i/7)

9.5 本节小结

  • 学术图表建议隐藏上、右边框(spines 设置)

  • plt.style.use() 切换风格后会覆盖字体配置,需重新设置

  • 配色首选 viridis,避免 jet

  • annotate() 是标注关键数据点的核心工具

  • 论文提交前用灰度风格预览,确保打印效果

第10节 保存与导出

图表绘制完成后,正确地保存和导出是确保论文排版质量的关键一步。不同格式和参数会直接影响最终效果。

10.1 基础保存与 DPI

fig.savefig() 是保存图表的核心方法。最重要的参数是 dpi(每英寸点数),它决定了图像的清晰度。

import matplotlib.pyplot as plt
import numpy as np

plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False

x = np.linspace(0, 10, 200)
y = np.sin(x) * np.exp(-x/8)

fig, ax = plt.subplots(figsize=(6, 4))
ax.plot(x, y, color='#2196F3', linewidth=2)
ax.set_title('示例图表', fontsize=12)
ax.grid(True, alpha=0.3)
fig.tight_layout()

# 不同 DPI 保存
fig.savefig('output_72.png', dpi=72)    # 屏幕预览
fig.savefig('output_150.png', dpi=150)  # 文档使用
fig.savefig('output_300.png', dpi=300)  # 论文印刷
fig.savefig('output_600.png', dpi=600)  # 高质量出版
plt.close()

DPI 选择建议:

DPI

文件大小

适用场景

72

最小

屏幕预览、网页展示

150

中等

Word/PPT 文档插入

300

较大

学术论文印刷(推荐)

600

最大

高质量出版、海报

bbox_inches='tight' 参数自动裁剪多余的空白边距,使图表更紧凑:

fig.savefig('output.png', dpi=300, bbox_inches='tight')

10.2 文件格式选择

Matplotlib 支持多种文件格式,各有优劣。

# 常见格式导出
fig.savefig('chart.png', dpi=300)   # PNG:无损压缩,推荐
fig.savefig('chart.jpg', dpi=300)   # JPEG:有损压缩,文件小
fig.savefig('chart.pdf')            # PDF:矢量格式,无限缩放
fig.savefig('chart.svg')            # SVG:矢量格式,网页友好
fig.savefig('chart.eps')            # EPS:LaTeX 传统格式
fig.savefig('chart.tiff', dpi=300)  # TIFF:印刷出版标准

格式对比:

格式

类型

透明背景

适用场景

PNG

位图

支持

通用首选,无损压缩

JPEG

位图

不支持

照片类图表

PDF

矢量

支持

论文提交、无限缩放

SVG

矢量

支持

网页、可编辑

EPS

矢量

部分

LaTeX 老版本

数模论文推荐

  • 插入 Word:用 PNG(300 DPI)

  • 插入 LaTeX:用 PDF 或 EPS(矢量,放大不模糊)

  • 在线提交:用 PNG(兼容性好)

  • 需要后期编辑:用 SVG(可用 Inkscape 编辑)

10.3 透明背景

当需要将图表叠加到演示文稿或其他背景上时,透明背景非常有用。

fig, ax = plt.subplots(figsize=(6, 4))
ax.plot(x, y, color='#2196F3', linewidth=2.5)
ax.set_title('透明背景示例', fontsize=14, fontweight='bold')
ax.set_xlabel('时间', fontsize=12)
ax.set_ylabel('幅值', fontsize=12)
ax.grid(True, alpha=0.3, linestyle=':')
fig.tight_layout()
fig.savefig('transparent.png', dpi=150,
            bbox_inches='tight', transparent=True)
plt.close()
transparent.png

transparent=True 使图表背景(包括 facecoloredgecolor)变为透明。注意:

  • 仅 PNG、PDF、SVG 支持透明背景

  • JPEG 不支持透明(会自动填充白色背景)

  • 透明图表插入深色背景时效果最佳

10.4 批量导出

数模比赛中经常需要批量生成多张图表。将绘图逻辑封装成函数,用循环批量导出。

import os

def save_algorithm_chart(algorithm_name, data, output_dir='images'):
    """保存单个算法的性能图表"""
    fig, ax = plt.subplots(figsize=(7, 5))
    ax.boxplot(data, patch_artist=True,
               boxprops=dict(facecolor='#2196F3', alpha=0.6),
               medianprops=dict(color='red', linewidth=2))
    ax.set_title(f'{algorithm_name} 性能分布', fontsize=13, fontweight='bold')
    ax.set_xlabel('训练轮次', fontsize=12)
    ax.set_ylabel('准确率', fontsize=12)
    ax.grid(True, axis='y', alpha=0.3)
    fig.tight_layout()

    # 安全文件名(去除空格和特殊字符)
    safe_name = algorithm_name.replace(' ', '_').replace('/', '_')
    filepath = os.path.join(output_dir, f'{safe_name}.png')
    fig.savefig(filepath, dpi=150, bbox_inches='tight')
    plt.close(fig)
    return filepath

# 批量导出
algorithms = ['随机森林', 'SVM', '逻辑回归', '神经网络', 'XGBoost']
for alg in algorithms:
    np.random.seed(hash(alg) % 10000)
    data = [np.random.normal(0.85, 0.03, 50) for _ in range(5)]
    save_algorithm_chart(alg, data)

批量导出最佳实践:

  • 每次导出后调用 plt.close() 释放内存,防止大量图表占用过多内存

  • 文件名用 algorithm_name.replace(' ', '_') 替换特殊字符

  • os.path.join() 构建路径,跨平台兼容

  • 可以添加 try-except 处理保存失败的情况

10.5 保存参数速查表

参数

说明

示例

dpi

分辨率

dpi=300

bbox_inches

裁剪方式

bbox_inches='tight'

transparent

透明背景

transparent=True

facecolor

背景色

facecolor='white'

edgecolor

边框色

edgecolor='none'

pad_inches

边距

pad_inches=0.1

quality

JPEG 质量

quality=95

10.6 本节小结

  • fig.savefig() 是保存图表的唯一方法

  • 数模论文推荐 PNG(300 DPI)PDF(矢量)

  • bbox_inches='tight' 自动裁剪空白,必加

  • 批量导出时每次 plt.close() 释放内存

  • 透明背景用 transparent=True,仅 PNG/PDF/SVG 支持


附录:完整代码获取

本教程所有代码均可通过以下链接下载:

matplot教程示例代码.zip

评论