【Python系列课程】24 Matplotlib(三):子图布局、图像保存与综合实战

> Matplotlib三部曲的最后一篇,我们解决两个核心问题:如何在一个画布上放多个子图(subplot布局),如何把画好的图保存为图片文件(savefig)。最后用一个综合可视化项目,把三篇所学全部串联起来。

一、子图布局:plt.subplot()

subplot() 让你在一个画布上排列多个子图,格式为 subplot(行数, 列数, 编号)

1.1 基础用法:2x2 四宫格

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

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

x = np.linspace(-3, 3, 50)
y1 = 2*x + 1
y2 = x**2
y3 = np.sin(x)
y4 = np.tan(x)

plt.figure(figsize=(10, 8))

2行2列,第1个子图


plt.subplot(2, 2, 1)
plt.plot(x, y1, color='blue')
plt.title('直线 y=2x+1')

2行2列,第2个子图


plt.subplot(2, 2, 2)
plt.plot(x, y2, color='red')
plt.title('抛物线 y=x²')

2行2列,第3个子图


plt.subplot(2, 2, 3)
plt.plot(x, y3, color='green')
plt.title('正弦曲线 y=sin(x)')

2行2列,第4个子图


plt.subplot(2, 2, 4)
plt.plot(x, y4, color='purple')
plt.title('正切曲线 y=tan(x)')

plt.tight_layout() # 自动调整子图间距
plt.show()
`

> [图1:2×2四宫格子图——四张图排列在同一个画布上,分别展示四种函数]

1.2 编号规则:按行从上到下,从左到右

`
subplot(2, 2, 1) subplot(2, 2, 2)
┌──────────────┐ ┌──────────────┐
│ #1 │ │ #2 │
└──────────────┘ └──────────────┘
subplot(2, 2, 3) subplot(2, 2, 4)
┌──────────────┐ ┌──────────────┐
│ #3 │ │ #4 │
└──────────────┘ └──────────────┘
`

1.3 不规则布局:跨行跨列

subplot() 的一个强大特性是:可以跨越多个格子

`python
plt.figure(figsize=(12, 8))

第1行,占2列(即2行3列布局中的第1、2、3个格子合成一个大图)


plt.subplot(2, 1, 1) # 2行1列,第1个 → 占上半部分
plt.plot(x, y1, color='blue')
plt.title('上半部分大图')

第2行,占3列中的第1列


plt.subplot(2, 3, 4) # 2行3列,第4个(即第2行第1列)
plt.plot(x, y2, color='red')
plt.title('左下')

第2行,第2列


plt.subplot(2, 3, 5)
plt.plot(x, y3, color='green')
plt.title('中下')

第2行,第3列


plt.subplot(2, 3, 6)
plt.plot(x, y4, color='purple')
plt.title('右下')

plt.tight_layout()
plt.show()
`

> [图2:不规则子图布局——上方一张大图占满整行,下方三张小图并排]

关键理解subplot(行数, 列数, 编号) 中的行数列数定义了网格系统,编号表示你当前要填充第几个格子。你可以在不同位置定义不同的网格系统(比如上半部分用2行1列,下半部分用2行3列)。

1.4 tight_layout() —— 子图间距的救星

不加 tight_layout() 时,子图之间可能会重叠:

`python

没有 tight_layout → 标签可能重叠


plt.subplot(2, 2, 1)
plt.plot(x, y1)
plt.xlabel('这个标签可能会和上方的图重叠')

有 tight_layout → 自动调整间距,不重叠


plt.tight_layout()
`

二、保存图像:plt.savefig()

画好的图不只是用来在屏幕上看的——保存为图片文件才能写进报告、发邮件、做PPT。

2.1 基础用法

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

x = np.linspace(-3, 3, 50)
y = np.sin(x)

plt.plot(x, y, color='blue')
plt.title('sin曲线')

保存为PNG图片


plt.savefig('sin_curve.png')
print("图片已保存为 sin_curve.png")
`

> [图3:savefig保存的sin_curve.png文件在Finder/资源管理器中的预览]

2.2 保存前先清理画布:plt.clf()

如果你在循环中画多张图,一定要在每次画新图前清理画布:

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

x = np.linspace(-3, 3, 50)

循环画图并保存


for i, func in enumerate([np.sin, np.cos, lambda x: x**2]):
plt.clf() # 清除当前画布!否则图会叠加
y = func(x)
plt.plot(x, y)
plt.title(f'图{i+1}')
plt.savefig(f'figure_{i+1}.png')
print(f'figure_{i+1}.png 已保存')

plt.clf() 只清除数据,不关闭画布窗口


如果想关闭整个画布(释放内存),用 plt.close()


`

2.3 savefig() 的关键参数

| 参数 | 默认值 | 说明 |
|

|

--|

|
| fname | — | 文件名(支持 .png .jpg .svg .pdf 等) |
| dpi | 100 | 分辨率,数字越大图片越清晰(打印用300) |
| bbox_inches | None | 'tight' 表示裁掉多余白边 |
| transparent | False | True 表示透明背景 |
| facecolor | 'white' | 背景颜色 |

`python

高清导出(适合打印/出版)


plt.savefig('high_quality.png',
dpi=300, # 300dpi = 打印级别
bbox_inches='tight', # 裁掉多余白边
facecolor='white', # 白色背景
edgecolor='none') # 无边框
`

2.4 保存为矢量格式(SVG/PDF)

矢量格式可以无限放大不失真,适合论文和出版:

`python
plt.savefig('chart.svg') # SVG矢量图
plt.savefig('chart.pdf') # PDF矢量图
`

> [图4:同一张图的PNG(放大后模糊)和SVG(放大后清晰)对比]

三、综合实战:数据分析全流程可视化

把 Matplotlib 三篇所学的全部内容串起来,做一个完整的项目:

需求:分析某公司三个部门(技术、销售、市场)四个季度的业绩数据,生成一份包含以下内容的可视化报告:
1. 各部门各季度业绩的折线趋势图
2. 各部门总业绩的对比条形图
3. 各部门的月度业绩分布散点图
4. 将以上三张图放在一个画布上,保存为高清图片

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

========== 准备工作 ==========


plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
np.random.seed(42)

========== 模拟数据 ==========


departments = ['技术', '销售', '市场']
quarters = ['Q1', 'Q2', 'Q3', 'Q4']

各部门各季度业绩数据(百万)


performance = {
'技术': [120, 135, 128, 156],
'销售': [200, 185, 210, 230],
'市场': [80, 90, 95, 110]
}

各月随机波动数据(用于散点图)


monthly = {}
for dept in departments:
base = np.mean(performance[dept]) / 3 # 季度均值 ÷ 3 ≈ 月均值
monthly[dept] = base + np.random.randn(12) * 10 # 12个月 + 噪声

========== 创建画布 ==========


plt.figure(figsize=(14, 10))

========== 子图1:折线趋势图(2行2列,第1个) ==========


plt.subplot(2, 2, 1)
colors = ['blue', 'green', 'orange']
markers = ['o', 's', '^']
for i, dept in enumerate(departments):
plt.plot(quarters, performance[dept],
color=colors[i], marker=markers[i],
linewidth=2.5, markersize=8,
label=dept)
plt.xlabel('季度', fontsize=12)
plt.ylabel('业绩 (万元)', fontsize=12)
plt.title('各部门季度业绩趋势', fontsize=14, fontweight='bold')
plt.legend(fontsize=10)
plt.grid(True, alpha=0.3, linestyle='--')

在每个数据点上标注数值


for i, dept in enumerate(departments):
for j, (q, val) in enumerate(zip(quarters, performance[dept])):
plt.text(j, val + 3, str(val), ha='center', fontsize=8, color=colors[i])

========== 子图2:总业绩对比条形图(2行2列,第2个) ==========


plt.subplot(2, 2, 2)
totals = [sum(performance[d]) for d in departments]
bars = plt.bar(departments, totals,
color=['skyblue', 'lightgreen', 'orange'],
edgecolor='black', linewidth=1.5, width=0.5)
plt.xlabel('部门', fontsize=12)
plt.ylabel('年度总业绩 (万元)', fontsize=12)
plt.title('各部门年度总业绩对比', fontsize=14, fontweight='bold')

在条形上方标注数值


for bar, total in zip(bars, totals):
plt.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 5,
str(total), ha='center', fontsize=11, fontweight='bold')

========== 子图3:月度分布散点图(2行2列,第3个) ==========


plt.subplot(2, 2, 3)
months = np.arange(1, 13)
for i, dept in enumerate(departments):
plt.scatter(months, monthly[dept],
s=80, c=colors[i], marker=markers[i],
alpha=0.7, edgecolors='black', linewidth=0.5,
label=dept)
plt.xlabel('月份', fontsize=12)
plt.ylabel('月业绩 (万元)', fontsize=12)
plt.title('各部门月度业绩分布', fontsize=14, fontweight='bold')
plt.xticks(months)
plt.legend(fontsize=10)
plt.grid(True, alpha=0.3, linestyle='--')

========== 子图4:年度汇总表(2行2列,第4个) ==========


plt.subplot(2, 2, 4)
plt.axis('off') # 关闭坐标轴,做成"表格"效果

构建表格数据


table_data = []
table_data.append(['部门', 'Q1', 'Q2', 'Q3', 'Q4', '总计', '平均'])
for dept in departments:
row = [dept] + performance[dept] + [sum(performance[dept]),
f"{np.mean(performance[dept]):.0f}"]
table_data.append(row)

创建表格


table = plt.table(cellText=table_data,
cellLoc='center',
loc='center',
colWidths=[0.12]*7)
table.auto_set_font_size(False)
table.set_fontsize(11)
table.scale(1, 2) # 调整行高

plt.title('年度业绩汇总表', fontsize=14, fontweight='bold', y=1.02)

========== 全局调整 ==========


plt.suptitle('XX公司2024年度业绩分析报告', fontsize=18, fontweight='bold', y=1.01)
plt.tight_layout()

保存高清图片


plt.savefig('业绩分析报告.png',
dpi=300,
bbox_inches='tight',
facecolor='white')

print("✅ 业绩分析报告.png 已生成!")
plt.show()
`

> [图5:综合实战最终输出——一张画布上四张子图 + 表格 + 公司标题的完整报告]

输出效果:

`
✅ 业绩分析报告.png 已生成!
`

这张图包含了:
- 折线图:看趋势变化
- 条形图:比总量大小
- 散点图:看分布离散度
- 表格:展示精确数字

四、Matplotlib 进阶路线图

三篇Matplotlib的内容足以应对80%的日常可视化需求。如果你想继续深入,以下是进阶方向:

| 方向 | 说明 | 推荐学习 |
|

|

|

|
| Seaborn | 统计图表(箱线图、热力图、小提琴图) | Seaborn官方教程 |
| Pandas绘图 | df.plot() 直接画图,语法更简洁 | Pandas文档 |
| 交互式图表 | Plotly、Bokeh,鼠标悬停显示数据 | Plotly Express |
| 地图可视化 | Folium、GeoPandas | GeoPandas文档 |
| 动画 | Matplotlib的 FuncAnimation | Matplotlib Animation指南 |

五、本文总结

| 知识点 | 核心要点 |
|

--|

|
| 子图布局 | plt.subplot(行, 列, 编号),编号按行从左到右 |
| 不规则布局 | 同一画布内定义不同网格系统,灵活排列 |
| 防止重叠 | plt.tight_layout() |
| 清除画布 | plt.clf() 清除数据,plt.close() 关闭窗口 |
| 保存图片 | plt.savefig('文件名.png', dpi=300, bbox_inches='tight') |
| 矢量格式 | .svg.pdf 无限缩放不失真 |

六、Matplotlib三篇回顾 + 24篇系列总结

Matplotlib三篇回顾

| 篇目 | 核心内容 | 关键函数 |
|

|

|

|
| 第22篇 | 绘图基础 | plot(), scatter(), bar(), imshow(), figure() |
| 第23篇 | 坐标轴与标注 | xlabel(), ylabel(), xticks(), yticks(), legend(), text(), axes(), gca(), spines |
| 第24篇(本篇) | 子图与导出 | subplot(), tight_layout(), clf(), close(), savefig() |

24篇系列完整回顾

| 阶段 | 篇目 | 主题 |
|

|

|

|
| Python基础 | 01~03 | 入门、环境搭建、六大数据类型 |
| 流程控制 | 04~06 | 运算符、条件语句、循环语句 |
| 函数 | 07~08 | def基础、lambda、作用域、闭包 |
| 面向对象 | 09~10 | 类与对象、封装继承多态 |
| 工程能力 | 11~13 | 异常处理、模块与包、文件操作 |
| 正则表达式 | 14~15 | 元字符、量词、环视、日志实战 |
| NumPy | 16~17 | 数组创建、索引切片、向量化、广播、聚合 |
| Pandas | 18~21 | Series/DataFrame、数据清洗、分组聚合、文件读写 |
| Matplotlib | 22~24 | 基础图表、坐标轴定制、子图与导出 |

恭喜你!如果你一路跟着代码敲下来,现在已经具备了用Python做数据分析和可视化的完整能力。下一步可以去Kaggle找真实数据集练手,或者用Pandas + Matplotlib 分析自己的工作数据——学以致用,才是最好的学习方式。

动手练习

1. 创建一个 2×3 的子图布局,用 np.random.randn(100) 生成6组不同的正态分布数据,分别画出6张直方图(plt.hist()),用 tight_layout() 调整间距。

2. 画一张折线图,分别保存为 .png(dpi=300)和 .svg 两种格式,对比文件大小。

3. 在循环中画3张图,每张画完用 plt.savefig() 保存,画下一张前用 plt.clf() 清空画布。

4. 设计一个你自己的综合可视化项目:至少包含2个子图(一个折线图 + 一个条形图),添加标题、图例、坐标轴标签,最后保存为高清PNG。
系列完结 感谢你的阅读,祝你在Python和数据分析的道路上越走越远!

posted @ 2026-06-27 08:45  AI视界尼奥  阅读(8)  评论(0)    收藏  举报