目录10
文章/Matplotlib/第 14 章
误差棒
Matplotlib · 第 14 / 43 章
误差棒 errorbar()
误差棒图 = 数据点 + 不确定度。参数分两组:一组管点和线(fmt 及其透传给 plot 的 **kwargs),一组管误差棒本身(yerr ecolor elinewidth capsize …)。两组互相独立——点和棒可以完全不同的颜色。
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" 不是一回事
这是全节最容易错的一处。三个值差别巨大:
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()
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="none" 时数据线根本不存在,返回值第一项是 None——这时再调 eb[0].set_color(...) 会直接崩:
eb = ax.errorbar(x, y, yerr=yerr, fmt="none")
print(eb[0])
# 输出: Noneyerr:标量 / 数组 / (2, N)
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:横向误差
ax.errorbar(x, y, xerr=0.2, yerr=yerr, fmt="o", capsize=4)xerr 和 yerr 可以同时给。此时返回值里的 barlinecols 变成 2 个(横竖各一组),caplines 变成 4 个(每侧 2 个)。
capsize / capthick:端帽不是装饰
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:棒和点分开配色
ax.errorbar(x, y, yerr=yerr, fmt="o", color="#1f77b4",
ecolor="#C44E52", elinewidth=2.5, capsize=5, capthick=2)color 只作用在点和线上,ecolor 只作用在棒和端帽上。常用的语义是「点 = 测量值、棒 = 不确定度」,两种颜色能一眼区分。
errorevery:点太密时抽着画
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:只知道边界的数据
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 表示「真实值只可能更高」,所以箭头朝上。这对「检测限」「只能测到阈值以下」的数据是必须的——用一个普通误差棒会暗示「真值在区间内」,那是错的。
也可以传布尔数组逐点控制,只标出真正被截断的那几个点。
返回对象
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 Trueeb[0]→Line2D,点和线(fmt="none"时是None)eb[1]→caplines,端帽。每个方向 2 个 Line2D(下端一组、上端一组),一个 Line2D 装下所有点的端帽eb[2]→barlinecols,误差棒线段。每个方向 1 个eb.lines→(eb[0], eb[1], eb[2])
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)。
综合示例:对称 / 非对称 / 上限数据
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 强在「能表达数据质量」的地方。