跳转至

Matplotlib — 画图、直方图与曲线拟合

这一章讲 Python 的画图库 Matplotlib。 作者给它定的调子值得放在最前面: 光看一堆数字,你根本看不到有用的信息;画出来,直到看出有趣的模式为止。 所以这一章不只是 API——它是「用图思考」的入门。

1. 这一章讲什么

Matplotlib 是 Python 的画图库,功能多到「几乎什么都能画」;原书只教基本款, 给的学习路径也很务实:「去 Matplotlib 画廊(gallery,官方示例集锦)找长得像你想要的效果, 抄下来改」——画图代码不必优雅,能用就行。1

本章四块:

  折线图(plot + show)  →  标注(轴标签、单位、图例)
  →  直方图(hist:频率分布)  →  曲线拟合(curve_fit)与「怎么判断拟合好不好」
  图说:前两块是「画得出」,后两块是「画完之后,图反过来检查你」。

2. 核心原理一:折线图——plot 攒数据,show 一口气显示

解决什么问题: 手上一串 (x, y) 数据点,想看清它的形状。

怎么做(主线例子:正弦曲线):

import matplotlib.pyplot as plt     # 行业惯例缩写 plt
import numpy as np

x = np.linspace(0, 10, 100)   # 0 到 10 均匀取 100 个点
y = np.sin(x)                 # 每个点算正弦
plt.plot(x, y)                # 画线:点按顺序连起来
plt.show()                    # 显示

走一遍:plt.plot 接受普通列表或 NumPy 数组,每次调用画一条线; plt.show() 把此前所有 plot 的结果一次性弹出来。想画两条线?plot 两次,show 一次:

x1 = np.linspace(0, 5, 20);  y1 = x1**2
x2 = np.linspace(0, 4, 5);   y2 = 16 - x2**2
plt.plot(x1, y1)
plt.plot(x2, y2)
plt.show()        # 一张图里两条曲线

为什么有效: 「攒完再 show」的模型让你可以自由组合多条曲线、多张子图于一个程序; 边界:plt.rcParams.update({'font.size': 12}) 那类全局设置(书里的示例用来调字号)是可选的,与画图逻辑无关。2

3. 核心原理二:标注——没有轴标签的图一文不值

解决什么问题: 一条曲线画出来,读者不知道横轴是什么、纵轴是什么、线代表什么。

怎么做:

plt.plot(t, y, label="y=sin(t)")   # 给线起名
plt.xlabel("t [s]")                # 横轴:时间,单位秒
plt.ylabel("y [m]")                # 纵轴:位移,单位米
plt.legend()                       # 把线的名字画进图里
plt.show()

加框的原话:「永远给坐标轴加标签。没标签的图毫无价值。别忘了单位!」 曲线名(label + legend())同样不是装饰:多条线同图时,它是唯一的说书人。3

顺带一个书里的细节:图例默认放右上,若和曲线撞了,Matplotlib 会自动挪到左下——小事,但说明它在意可读性。

3.1 标注三件套,各管什么

把三个标注动作的分工说透:3

动作 回答的问题 不做的后果
plt.xlabel("t [s]") 横轴是什么、单位是什么 读者自己猜,十有八九猜错
plt.ylabel("y [m]") 纵轴是什么、单位是什么 同上;sin 曲线没有单位就是一条波浪
plt.legend() 图里每条线各代表什么 两条线以上时,读者无法引用你的结论

书里的原话是命令句:「永远给坐标轴加标签。没标签的图毫无价值。别忘了单位!」 单位写在方括号里是物理学的惯例(t [s] = t,单位秒),照抄即可;你的领域有自己的惯例就跟领域的。3

4. 核心原理三:直方图——看「频率分布」的形状

解决什么问题: 一千个测量值逐个看毫无意义;你想看「值落在各区间的有多少」。

怎么做: 用第 09 章的高斯随机数造 1000 个样本,hist 分 10 个桶(bin)统计:

import random
random.seed(1)
nums = [random.gauss(100, 50) for _ in range(1000)]   # 均值100、标准差50 的钟形分布
plt.hist(nums, bins=10)
plt.xlabel("Value"); plt.ylabel("Number of occurrences")
plt.show()

直方图还会把数据「吐回来」——这是本章最容易错过的实用点:

n, bins, _ = plt.hist(data, bins=30)     # n:每个桶的计数;bins:桶的边界
bin_average = (bins[1:] + bins[:-1]) / 2  # 用第 04 章的切片技巧取每个桶的中点
plt.clf()                                  # 清掉直方图
plt.plot(bin_average, n)                   # 改画成曲线:直方图的平滑版

plt.clf()(clear figure)把直方图抹掉、只留曲线——「不牺牲直方图的便利,又能得到曲线」。4

4.1 直方图的桶,怎么数

plt.hist(nums, bins=10) 的输出分两部分理解:4

  视觉上:横轴被切成 10 个连续区间,每个区间一根柱,柱高 = 落进该区间的样本数
  返回值:n(每个桶的计数列表)、bins(每个桶的边界列表)——画图是副产品,统计是主产品

书里的高斯例子:1000 个样本(均值 100、标准差 50)分 10 桶——中间几桶(100 附近)柱子最高, 两边递减,钟形的轮廓直接从柱子的起伏里长出来。 这就是为什么直方图是分布的「第一眼检查」:不用算任何统计量,形状已经说明问题。4

4.2 直方图转曲线的切片技巧

n, bins, _ = plt.hist(data, bins=30)       # 画图的同时拿到数据
bin_average = (bins[1:] + bins[:-1]) / 2    # 相邻边界取中点 → 每个桶的中心位置
plt.clf()                                    # 清掉直方图,画布空了
plt.plot(bin_average, n)                     # 用桶中心当横轴、计数当纵轴 → 平滑曲线

bins[1:] + bins[:-1] 是第 04 章的切片错位加法,在这里直接复用; plt.clf()(clear figure)把直方图抹掉,只留曲线—— 「既享直方图的自动分桶,又得曲线的干净形状」,书里这句总结就是这两行的价值。4

5. 核心原理四:曲线拟合——以及比参数更重要的「拟合质量」

5.1 主线例子:抛物线拟合

解决什么问题: 实验测了一组 (t, 位置) 数据点,想反推出物理参数 (初始位置 s₀、初速度 v₀、重力加速度 a)。这是「从数据反推参数」的标准动作——曲线拟合(curve fitting)。

怎么做: 假装我们做了一次抛体实验:真值 s₀=10、v₀=20、a=−9.81, 按公式算每个时刻的位置,再叠加高斯噪声模拟测量误差(random.gauss(0, 2),零均值、标准差 2 的抖动), 然后用 scipy.optimize.curve_fit 反拟合:5

from scipy.optimize import curve_fit

def f(t, s_0, v_0, a):                 # 物理模型:抛物线
    return s_0 + v_0*t + 1/2*a*t**2

popt, pcov = curve_fit(f, t, y, p0=[10, 20, -10])   # p0:给参数的初始猜测
plt.plot(t, y, "o")                    # 数据点画成圆点
plt.plot(t, f(t, *popt))               # 拟合曲线;*popt 把三个参数拆给 f

走一遍:f 是「带未知参数的模型」;curve_fit 反复调整 (s₀, v₀, a) 使曲线离数据点总偏差最小, 返回两样东西——popt(拟合出的最优参数,书里把它直接织进图例:y(t)=10.01m+19.98m/s·t+…), 和 pcov(协方差矩阵,下面讲)。5

为什么有效: 「最小化总偏差」是最小二乘的思想(书里没点名);模型对、噪声适中时, 拟合值会收敛到真值附近——书里的演示结果确实贴回 10、20、−9.81 附近。

5.2 怎么判断拟合靠不靠谱(本章的灵魂)

书里说这一步「非常深刻」,给两条互补的判据:6

  1. 数学判据:看协方差矩阵 pcov。 「数值高说明拟合不稳,要再仔细看。」 (直觉:pcov 对角元大 ⟺ 参数的方向很「平」——数据对那个参数几乎不敏感,换个噪声样本拟合值就乱跑。)
  2. 工程判据(作者更推的):把数据和拟合曲线画在同一张图里,亲眼看。 「你立刻会看出拟合的质量。一张图胜过一千个数。」

5.3 局部最小值:拟合会悄悄失败

curve_fit 的搜索不是全局的——初始猜测 p0 给得不好,算法会卡在一个局部最小值, 返回一条明显对不上数据的曲线(书里图 11.6 的样子)。

作者的坦白很有意思:「我没能故意制造一次坏拟合,只好画了条随机曲线来演示—— 但别以为坏拟合是例外:初始值选得差,它会无缘无故地发生。所以永远要检查拟合参数和协方差矩阵, 或者干脆画出来看。」7

顺带一句作者的吐槽(加框):「有人做曲线拟合,然后宣称自己在做机器学习——我认为不对,这里没有『学习』发生; 当然,机器学习也是在数据里找模式,两者确实有相似。」8

6. 作者的判断与证据

书里给了证据的: 正弦图、双曲线图的完整可运行代码;直方图取回数据再画曲线; 拟合例子的参数确实拟回真值附近。25

作者的判断(立场):

  • 「画图代码不必优雅」——务实,正确;
  • 「一张图胜过一千个数」——本章金句,也是数据工作的通用纪律;
  • 「拟合不是机器学习」——他的口径;广义上拟合是最基础的「从数据学参数」,算不算 ML 取决于定义,不必纠结。

「高维」指参数多到无法逐对画图检查(几百个以上)——真到那一步,「画出来看」会失灵。

判断(我们的,不是书里的): 「数据+拟合曲线同图」这一条,等价于机器学习里「先建近似、再验证效果」的那套可视化做法。

作者 2026 年还在手动 curve_fit,而他的读者大概率会走向 sklearn/scipy 全家桶—— 但「永远把模型输出和真实数据画在一起」这个习惯,在任何工具链里都保值。

如果错,会错在: 那时协方差矩阵、残差(数据点离拟合线的距离)分析等数学判据升为主要手段——本章的排序应倒过来。

6.1 本章的最小可运行骨架

把全章压成一个能直接跑的最小脚本,收藏备用:2

import matplotlib.pyplot as plt
import numpy as np

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

plt.plot(x, y, label="y=sin(x)")   # 画线并命名
plt.xlabel("x")                     # 横轴标签
plt.ylabel("y")                     # 纵轴标签
plt.legend()                        # 显示图例
plt.show()                          # 弹窗显示

七行,含标签和图例——比书里第一个例子多两行,但可直接当模板。 把 np.sin 换成你的数据、把标签换成你的单位,就能用;直方图版把 plot 换成 hist 即可。2

6.2 「图先于数」的工作法

书里那句「plot them until you see some interesting patterns」值得单独成章。 科学数据的工作顺序,作者默认是:先画图,再决定算什么——

  一堆数字 → 直接算统计量(均值、方差……)→ 容易得出「平均正确、形状全错」的结论
  一堆数字 → 先画出来 → 形状一眼可见(双峰?长尾?离群点?)→ 再选统计量

两条路径的差别在「离群点」上最明显:均值对离群点敏感,图对离群点诚实。 算之前画一下,是成本最低的质检;「一张图胜过一千个数」的下半句是: 一千个数可能合谋骗你,一张图很难。1

7. 边界与局限

  • 子图(subplot)、散点图 scatter、误差棒 errorbar、(刻度按倍数而不是按等差排的)对数坐标等常用件没讲;
  • curve_fit 的数学(最小二乘、优化算法)完全黑箱;
  • 直方图 bins 怎么选没有指导(太少糊、太多噪);
  • 图的保存(savefig)没提——本章所有图都靠 show() 弹窗。

8. 可带走的

本章主线例子,一行写完: 正弦:linspace 取点 → plot(x, y) → show(); 拟合:curve_fit(f, t, y, p0=…) 得 popt/pcov → 数据点画 "o"、拟合线画实线同图核对。25

  1. 数字本身没用,画出来看模式——Matplotlib 就是干这个的;画图代码抄画廊、改到能用即可。
  2. plot 攒线、show 显示;多条线 plot 多次。
  3. 轴标签 + 单位 + 图例,一个不能少;没标签的图毫无价值。
  4. 直方图看分布;hist 会返回计数和桶边界,能二次利用(直方图转曲线)。
  5. 拟合 = 模型 + curve_fit + 初始猜测;结果参数织进图例,一眼可读。
  6. 拟合质量两条判据:协方差矩阵(数学)+ 数据与曲线同图(亲眼);后者优先。
  7. 初始值差会陷局部最小值——坏拟合不是例外,永远检查。
  8. 「一张图胜过一千个数」——全数据工作的通用纪律。

9. 原文地图

主题 原书章 原文位置
「画出来看模式」的立场 11 Matplotlib text/27-ch11-11-matplotlib.txt:7(搜「plot them」)
画廊学习法 11 Matplotlib text/27-ch11-11-matplotlib.txt:12(搜「gallery」)
正弦主线例子 11 Matplotlib text/27-ch11-11-matplotlib.txt:29(搜「np.sin」)
多线一图、show 的语义 11 Matplotlib text/27-ch11-11-matplotlib.txt:50(搜「several times」)
标注三件套与「没标签一文不值」 11 Matplotlib text/27-ch11-11-matplotlib.txt:71(搜「Labeling」) · text/27-ch11-11-matplotlib.txt:90(搜「worthless」)
直方图与取回数据画曲线 11 Matplotlib text/27-ch11-11-matplotlib.txt:93(搜「Histograms」) · text/27-ch11-11-matplotlib.txt:116(搜「n, bins」)
曲线拟合主线例子 11 Matplotlib text/27-ch11-11-matplotlib.txt:130(搜「Curve Fitting」) · text/27-ch11-11-matplotlib.txt:147(搜「curve_fit」)
协方差矩阵与「画出来看」 11 Matplotlib text/27-ch11-11-matplotlib.txt:174(搜「covariance matrix」) · text/27-ch11-11-matplotlib.txt:182(搜「more than a thousand numbers」)
「拟合不是机器学习」框 11 Matplotlib text/27-ch11-11-matplotlib.txt:184(搜「machine learning」)
局部最小值警告 11 Matplotlib text/27-ch11-11-matplotlib.txt:192(搜「local minimum」) · text/27-ch11-11-matplotlib.txt:197(搜「without any apparent reason」)

  1. 出处:「11 Matplotlib」第 5–14 段(text/27-ch11-11-matplotlib.txt:7,搜「plot them」;画廊建议见第 12 段,搜「gallery」)。 ↩↩

  2. 出处:「11 Matplotlib」第 16–66 段(text/27-ch11-11-matplotlib.txt:26,搜「rcParams」;多线示例见第 55–64 段)。 ↩↩↩↩↩

  3. 出处:「11 Matplotlib」第 71–92 段(text/27-ch11-11-matplotlib.txt:80,搜「label=」;「别忘单位」见第 91 段,搜「units」)。 ↩↩↩

  4. 出处:「11 Matplotlib」第 93–129 段(text/27-ch11-11-matplotlib.txt:110,搜「plt.hist」;取回数据见第 115–128 段,搜「clf」)。 ↩↩↩↩

  5. 出处:「11 Matplotlib」第 130–176 段(text/27-ch11-11-matplotlib.txt:147,搜「curve_fit」;参数织进图例见第 165–167 段)。 ↩↩↩↩

  6. 出处:「11 Matplotlib」第 172–182 段(text/27-ch11-11-matplotlib.txt:174,搜「High” values」;「一张图胜过一千个数」见第 181 段,搜「more than a thousand numbers」)。 ↩

  7. 出处:「11 Matplotlib」第 189–197 段(text/27-ch11-11-matplotlib.txt:192,搜「stuck in a local minimum」;「无缘无故地发生」见第 196 段,搜「without any apparent reason」)。 ↩

  8. 出处:「11 Matplotlib」第 184–187 段(加框「Curve Fitting and Machine Learning」,text/27-ch11-11-matplotlib.txt:186,搜「false」)。 ↩