3D 图要 projection='3d',这个参数漏了会报错说没有 plot_surface。 subplots 一次拿回一堆 axes,用 axes[行, 列] 取;subplot 编号从 1 起、先行后列。 dpi 从 100 到 300,字节涨 3.7 倍。印刷才需要 300,屏幕 100 够。
前面十篇都是一张纸一块图。这一篇讲一张纸贴好几块,还有三维曲面。 多子图是「一屏说清一件事」的唯一办法,比存五张图挨个发有效得多。
一、3D 曲面
np.meshgrid 造 40 乘 40 的网格,高度取 sin(到原点的距离),得到一圈圈水波纹。
fig.add_subplot(111, projection='3d') 拿到一块 3D 的 axes。
projection='3d' 漏了会报 AttributeError,说没有 plot_surface 这个方法。
3D 图好看但难读数:同一个高度,从不同角度看位置完全不同。 能压成二维热力图就压,热力图读数准得多。3D 适合看形状,不适合报数。
二、多子图:subplots 一次拿一堆
fig, axes = plt.subplots(2, 2, figsize=(8, 6)) 拿到 2 乘 2 的数组。
用 axes[0, 0]、axes[0, 1]、axes[1, 0]、axes[1, 1] 逐块画。
四张图一张纸 34229 字节,比四张单独的图加起来小得多。
一张纸贴多张图时一定要 tight_layout,不然标题和标签互相压。
三、subplot 与 subplots 的区别
| 写法 | 给什么 | 怎么取 |
|---|---|---|
| subplots(2, 2) | 一次给整个数组 | axes[行, 列],从 0 起 |
| subplot(2, 2, 1) | 一个一个加 | 编号从 1 起,先行后列 |
subplots 省事,subplot 灵活(比如只画 2 乘 2 布局里的第 1 块和第 4 块)。 subplot 的编号从 1 开始,这一点和 Python 别处的 0 起不一样,容易记混。
四、保存:格式与 dpi
同样一张 6 乘 4 的图:dpi=100 是 17293 字节,dpi=300 是 63367 字节,差 3.7 倍。
理论上面积差 9 倍,压缩之后实际只到 3.7 倍。
savefig 按扩展名决定格式:.png 位图、.pdf 矢量、.svg 矢量。
论文和报告用 pdf 或 svg,放多大都不糊。png 放大就出马赛克。
五、动手 Demo:3D 图、子图与保存
画一张 3D 曲面,再画一张 2 乘 2 四子图,最后对比 dpi 100 和 300 的字节数。 四子图里故意混四种图,看布局能不能撑住。
import os
import matplotlib
matplotlib.use("Agg")
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, 40)
Y = np.linspace(-3, 3, 40)
XX, YY = np.meshgrid(X, Y)
ZZ = np.sin(np.sqrt(XX ** 2 + YY ** 2))
fig = plt.figure(figsize=(6, 5))
ax = fig.add_subplot(111, projection="3d")
ax.plot_surface(XX, YY, ZZ, cmap="viridis")
fig.savefig("surf.png")
YW = [88, 92, 75, 85, 90, 68, 95, 72, 83, 79]
fig, axes = plt.subplots(2, 2, figsize=(8, 6))
axes[0, 0].plot(range(6), [128, 116, 143, 151, 138, 162])
axes[0, 0].set_title("折线")
axes[0, 1].bar(["一", "二", "三"], [3, 1, 2])
axes[0, 1].set_title("柱形")
axes[1, 0].hist(YW, bins=5)
axes[1, 0].set_title("直方")
axes[1, 1].pie([5, 3, 2], labels=["A", "B", "C"])
axes[1, 1].set_title("饼图")
fig.tight_layout()
fig.savefig("subplots.png")
print("四子图 %d 字节" % os.path.getsize("subplots.png"))
for d in (100, 300):
f, a = plt.subplots(figsize=(6, 4))
a.plot(range(6), [128, 116, 143, 151, 138, 162])
f.savefig("dpi%d.png" % d, dpi=d)
print("dpi=%3d -> %d 字节" % (d, os.path.getsize("dpi%d.png" % d)))
plt.close(f)
实跑输出:
--------------------------------------------------------------------
3D 图、子图与保存
--------------------------------------------------------------------
① 3D 曲面
--------------------------------------------------------------------
40 乘 40 的网格,曲面的高度是 sin(到原点的距离)——一个水波纹。
3D 图要 projection='3d',这个参数漏了会报错说没有 plot_surface。
! 3D 图好看但难读数。能压成二维热力图就压,热力图读数准得多。
② 多个子图:subplots
--------------------------------------------------------------------
subplots(2, 2) 一次拿到 2 乘 2 的 axes 数组,用 axes[行, 列] 取。
四张图一张纸,字节数 34229——比四张单独的图加起来小得多。
! 一张纸贴多张图时一定要 tight_layout。不然标题和标签互相压。
③ 单张索引写法:subplot
--------------------------------------------------------------------
add_subplot(2, 2, 1) 的意思是「2 行 2 列的第 1 个」,编号从 1 起、先行后列。
subplots 一次给一堆,subplot 一个一个加。前者省事,后者灵活。
! 编号从 1 开始,这一点和 Python 其他地方的 0 起不一样,容易记混。
④ 保存:格式与 dpi
--------------------------------------------------------------------
同样一张图,dpi=100 是 17293 字节,dpi=300 是 63367 字节,差 3.7 倍。
dpi 是每英寸点数。300 是印刷标准,屏幕上看 100 就够。
savefig 按扩展名决定格式:.png 位图、.pdf 矢量、.svg 矢量。
! 论文和报告用 pdf 或 svg,放多大都不糊。png 放大就出马赛克。
np.meshgrid 把两个一维坐标铺成两张二维网格,曲面图必须这么给。
四子图里 pie 没有 x 轴 y 轴,所以它那块不会显示刻度,其余三块都有。
30 秒实验
- 把 projection='3d' 去掉,看报什么错。
- 把 subplots(2, 2) 改成 (3, 1),重新分配四块图。
- 想一想:为什么矢量格式(pdf、svg)放大不糊,png 会糊?
常见误区
- 3D 图漏了 projection:报没有 plot_surface。
- subplot 编号当成 0 起:布局乱掉,或者直接报错。
- 多子图不 tight_layout:标题互相压,坐标轴叠在一起。
- 屏幕图也用 dpi=300:文件大十倍,肉眼看不出区别。
自测三问(点开对答案)
1. subplots 和 subplot 有什么区别? —— subplots 一次返回整个 axes 数组(0 起索引),subplot 一次加一块(编号 1 起)。 2. 为什么四子图比四张单独的图小? —— 共用一张画布,坐标轴、字体、配色表这些公共部分只存一份。 3. 什么时候该用 pdf 而不是 png? —— 要打印、要放大查看、要放进报告排版时。pdf 是矢量,放多大都不糊。最后一篇换工具:Seaborn。一行一个参数就换风格,还能直接吃 DataFrame。
- ☐ (本篇完,下一篇 5.12 Seaborn 与五个综合案例)