目录10

文章Matplotlib第 14 章

误差棒

Matplotlib · 第 14 / 43 章

误差棒 errorbar()

误差棒图 = 数据点 + 不确定度。参数分两组:一组管点和线(fmt 及其透传给 plot 的 **kwargs),一组管误差棒本身(yerr ecolor elinewidth capsize …)。两组互相独立——点和棒可以完全不同的颜色。

Python18 行
Axes.errorbar(
    x, y,                 # 数据点位置
    yerr=None,            # 纵向误差:标量 / 数组 / (2, N) 非对称
    xerr=None,            # 横向误差,形态同上
    fmt="",               # ★ 点线格式串;"" = 实线无标记,"none" = 什么都不画
    ecolor=None,          # 误差棒颜色,独立于 color
    elinewidth=None,      # 误差棒线宽(pt)
    capsize=None,         # ★ 端帽长度(pt),默认 None = 无端帽
    capthick=None,        # 端帽线宽(pt)
    barsabove=False,      # 误差棒画在数据点之上
    lolims=False,         # 该点只有下限 → 下侧画成箭头
    uplims=False,         # 该点只有上限
    xlolims=False,        # 横向:只有左限
    xuplims=False,        # 横向:只有右限
    errorevery=1,         # 每隔几个点画一根误差棒
    data=None,
    **kwargs              # 其余传给数据线,同 plot
)
参数 一句话 默认值的影响
yerr / xerr 误差大小 不给就是纯数据线,和 plot 一样
fmt 点线样式 "" 是实线、"none" 才是全不画
capsize 端帽长度 None → 误差棒像裸线段,不好读
ecolor / elinewidth 棒的颜色/线宽 不设就跟随线的颜色
capthick 端帽粗细 默认跟随 elinewidth
errorevery 抽样画棒 1 = 每点都画
lolims 系列 单向箭头 False;开了表示「只测到边界」

fmt:"" 和 "none" 不是一回事

这是全节最容易错的一处。三个值差别巨大:

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

x = np.arange(1, 8)
y = np.array([2.1, 3.4, 3.0, 4.6, 4.2, 5.3, 5.0])
yerr = np.array([0.35, 0.5, 0.25, 0.6, 0.4, 0.55, 0.3])

fig, ax = plt.subplots()
ax.errorbar(x, y, yerr=yerr, fmt="o", capsize=4)
plt.show()

误差棒最小示例:点 + 端帽 + 误差棒

Python4 行
ax.errorbar(x, y, yerr=yerr, fmt="")          # 实线(无标记点)
ax.errorbar(x, y, yerr=yerr, fmt="none")      # 只有误差棒,没有点也没有线
ax.errorbar(x, y, yerr=yerr, fmt="o")         # 只有点
ax.errorbar(x, y, yerr=yerr, fmt="s--")       # 方块 + 虚线

fmt 三态:实线 / 只有棒 / 只有点

fmt="none" 时数据线根本不存在,返回值第一项是 None——这时再调 eb[0].set_color(...) 会直接崩:

Python3 行
eb = ax.errorbar(x, y, yerr=yerr, fmt="none")
print(eb[0])
# 输出: None

yerr:标量 / 数组 / (2, N)

Python3 行
ax.errorbar(x, y, yerr=0.3)                        # 标量:每点都 ±0.3
ax.errorbar(x, y, yerr=yerr)                       # 数组:逐点不同
ax.errorbar(x, y, yerr=[yerr * 0.4, yerr * 1.8])   # (2, N):非对称

(2, N) 的第一行是下误差、第二行是上误差。顺序写反图上看不出异常,但含义正好颠倒——这是误差棒图最隐蔽的错误。

非对称误差才是真实测量的常态(下界受物理约束、上界受检出限约束),画成对称的只是习惯。

xerr:横向误差

Python1 行
ax.errorbar(x, y, xerr=0.2, yerr=yerr, fmt="o", capsize=4)

xerr 和 yerr 可以同时给。此时返回值里的 barlinecols 变成 2 个(横竖各一组),caplines 变成 4 个(每侧 2 个)。

capsize / capthick:端帽不是装饰

Python3 行
ax.errorbar(x, y, yerr=yerr, fmt="o", capsize=0)     # 无端帽
ax.errorbar(x, y, yerr=yerr, fmt="o", capsize=6)     # 明显端帽
ax.errorbar(x, y, yerr=yerr, fmt="o", capsize=6, capthick=2)   # 端帽更粗

没有端帽的误差棒只是一根垂线,读者分不清它的端点是「数据边界」还是「压在某条网格线上」。端帽长度单位是 pt,不是数据坐标——capsize=6 在任何缩放下都是 6 磅,数据范围变了它不会跟着缩放。

ecolor / elinewidth:棒和点分开配色

Python2 行
ax.errorbar(x, y, yerr=yerr, fmt="o", color="#1f77b4",
            ecolor="#C44E52", elinewidth=2.5, capsize=5, capthick=2)

color 只作用在点和线上,ecolor 只作用在棒和端帽上。常用的语义是「点 = 测量值、棒 = 不确定度」,两种颜色能一眼区分。

errorevery:点太密时抽着画

Python3 行
xd = np.arange(20)
ax.errorbar(xd, 3 + 1.5 * np.sin(xd / 3), yerr=0.35,
            fmt="o-", capsize=2, errorevery=4)

20 个点每点一根棒,棒会互相压住变成一片。errorevery=4 表示每 4 个点画一根棒,但点和线仍然全画——它只跳过误差棒,不跳过数据。

lolims / uplims:只知道边界的数据

Python2 行
ax.errorbar(x, y, yerr=yerr, fmt="o", capsize=3, lolims=True)    # 该点是下限
ax.errorbar(x, y, yerr=yerr, fmt="o", capsize=3, uplims=True)    # 该点是上限

打开后该侧的端帽变成箭头:lolims=True 表示「真实值只可能更高」,所以箭头朝上。这对「检测限」「只能测到阈值以下」的数据是必须的——用一个普通误差棒会暗示「真值在区间内」,那是错的。

也可以传布尔数组逐点控制,只标出真正被截断的那几个点。

返回对象
Python13 行
eb = ax.errorbar(x, y, yerr=yerr, capsize=5)

print(len(eb))
# 输出: 3

print(type(eb[0]).__name__)
# 输出: Line2D

print(len(eb[1]), len(eb[2]))
# 输出: 2 1

print(eb.has_xerr, eb.has_yerr)
# 输出: False True
  • eb[0] → Line2D,点和线(fmt="none" 时是 None)
  • eb[1] → caplines,端帽。每个方向 2 个 Line2D(下端一组、上端一组),一个 Line2D 装下所有点的端帽
  • eb[2] → barlinecols,误差棒线段。每个方向 1 个
  • eb.lines → (eb[0], eb[1], eb[2])
Python11 行
eb = ax.errorbar(x, y, yerr=yerr, capsize=5)

eb[0].set_markersize(10)          # 数据点
eb[0].set_color("#000000")        # 数据线/点

for cap in eb[1]:                 # 2 个(或 4 个)
    cap.set_color("#C44E52")

for coll in eb[2]:                # 1 个(或 2 个)
    coll.set_linewidth(2.5)
plt.show()

数量对应关系:只给 yerr → caplines 2、barlinecols 1;xerr + yerr 都给 → 4 和 2。

注意事项

  • fmt="" 是实线,不是「什么都不画」;要只留误差棒请用 fmt="none"。
  • fmt="none" 时 eb[0] 是 None,后续对它调方法会 AttributeError。
  • yerr 的 (2, N) 顺序是「下、上」,写反不报错但语义反了。
  • capsize 默认 None → 没有端帽;这是「误差棒看起来像裸线」的原因。
  • capsize / capthick / elinewidth 单位是 pt,yerr 单位是数据坐标。
  • errorevery 只跳过误差棒,不跳过点和线。
  • 大量点叠误差棒会糊成色块,先考虑 errorevery 或改画置信带(fill_between)。

综合示例:对称 / 非对称 / 上限数据

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

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

rng = np.random.default_rng(5)
x = np.arange(1, 9)
y = np.array([2.0, 3.1, 2.8, 4.4, 4.0, 5.2, 4.8, 5.6])
err = np.array([0.3, 0.45, 0.25, 0.55, 0.35, 0.5, 0.3, 0.4])

fig, axes = plt.subplots(1, 3, figsize=(15, 4.4))

# ---- (a) 对称误差 + 端帽 ----
axes[0].errorbar(x, y, yerr=err, fmt="o-", color="#1f77b4",
                 ecolor="#1f77b4", capsize=5, capthick=1.5,
                 elinewidth=1.5, label="±1 标准差")
axes[0].axhline(4.0, color="gray", linestyle="--", linewidth=0.9)
axes[0].legend(fontsize=8)
axes[0].set_title("(a) 对称误差棒")

# ---- (b) 非对称误差:下界被地板效应截住 ----
lo = np.minimum(err, y - 0.5)          # 不能低于 0.5
hi = err * 2.0
axes[1].errorbar(x, y, yerr=[lo, hi], fmt="s-", color="#DD8452",
                 ecolor="#DD8452", capsize=5, capthick=1.5, elinewidth=1.5)
axes[1].axhline(0.5, color="#C44E52", linestyle=":", linewidth=1)
axes[1].set_title("(b) 非对称误差(下界被截)")

# ---- (c) 部分点只有上限:换成箭头 ----
y_lim = y.copy()
bad = (rng.random(len(x)) < 0.4).astype(bool)   # 随机挑几个点当作「未检出」
y_lim[bad] = 0.8
axes[2].errorbar(x, y_lim, yerr=0.25, fmt="o", capsize=4,
                 color="#55A868", ecolor="#55A868",
                 uplims=bad, elinewidth=1.5)
axes[2].set_ylim(0, 7)
axes[2].set_title("(c) uplims:箭头表示只有上限")

plt.tight_layout()
plt.show()

(c) 里 uplims=bad 直接吃一个布尔数组——这是 errorbar 比 plot 强在「能表达数据质量」的地方。