跳到正文
本地阅读 · 多子图、标注及统计图

多子图、标注及统计图

计算机基础 · Matplotlib

前置知识:Figure/Axes 与基础图形 · 缺失值、统计与数值稳定性

本章目录

本章学习目标

  • 规划共享坐标和多子图布局
  • 解释直方图、箱线图与误差线
  • 用标注强调证据而不遮盖数据

一、多子图先确定比较方式

多子图用于并排回答相关问题,不是把所有指标塞到一张图。用户数与金额单位不同,适合分图;两个渠道同一指标则可以共享纵轴,便于直接比较。plt.subplots(2,2) 默认返回二维 Axes 数组,可用 axes.flat 遍历;只有一个子图时返回单对象的行为容易导致索引错误,想保持统一二维结构可以指定 squeeze=False。

sharex 与 sharey 共享坐标范围和刻度配置,不会自动保证两个数据来源、单位和聚合口径一致。例如两个股票的原始价格差别很大,共享价格轴可能压扁低价股票;若问题是比较涨幅,可先以同一起点归一化,再使用共同轴。归一化改变展示指标,必须在标题标明基期和公式,不能让读者误认为仍是原始价格。

二、直方图描述数值分布

直方图把连续数值按区间计数,横轴是数值范围,纵轴默认是落入该区间的数量;柱图的横轴则是类别,两者含义不同。订单金额 [1,2,2,3,8,9] 用边界 [0,3,6,10] 时前三个数进入第一箱,三进入第二箱,八和九进入第三箱,计数为三、一、二。除最后一个区间包含右端点外,普通分箱通常左闭右开,边界值必须知道落在哪一箱。

bins 数量和边界会改变视觉形状,比较两组分布应使用相同边界。density=True 表示面积归一到一的密度,不是每根柱高都直接等于概率;箱宽不同尤其容易误读。小样本不能仅凭平滑外观判断正态性,极端值也不该只为美观删去。可同时标注样本数或叠加原始点,让读者知道图形建立在多少观测上。

三、箱线图与误差线不是万能结论

箱线图展示中位数、四分位数和须线规则,常用须线范围是四分位距的一点五倍,并非均值加减三倍标准差。超出须线的点仅表示按该规则较极端,不自动证明错误或需要删除。两组箱体相近也不证明两组分布完全一样,样本量、偏态和尾部仍可能不同;必要时补充原始点和具体统计量。

误差线可以表示标准差、标准误或置信区间,这三者不可互换。标准差描述个体分散,标准误描述均值估计的不确定性,置信区间还需要方法和假设。图例或图注必须明写误差线的含义。若数据按用户聚集,不能把同一用户事件当独立样本计算标准误。绘图函数只接收 yerr,并不会核实你传入的数字是否具有正确统计解释。

四、标注应说明原因与位置

annotate 可以把文本放在指定位置,用箭头指向数据点;xy 是目标数据坐标,xytext 和 textcoords 控制文字放置方式。用偏移点单位能让文字与点保持合适间隔。标注峰值时先计算峰值位置,再指向相应 x、y,不要用固定索引指向已经排序变化的记录。箭头和文字避免覆盖其他关键点,边界附近需检查是否被裁切。

文字可以说“活动启动后出现峰值”,但不应仅凭一张时间图写“活动导致增长”,那需要额外因果设计。平均线、阈值线和参考区间也必须说明来源。不要绘制一条看起来科学的目标线却不说明为何选该数值。颜色的意义保持一致,例如同一渠道在不同子图使用同一颜色;对黑白打印也用线型和标记做补充区分。

五、布局与统计结果共同检查

使用 constrained 布局通常能减少标题和标签重叠,但不是自动保证所有尺寸都正确。大标题、长类别名、外置图例和高密度日期可能仍需调整 figsize、旋转刻度或减少标注。检查导出文件时确认最外侧文字完整、子图间隔均匀、共享坐标刻度可读。不要在报告里缩小到无法辨认数字后仍认为图片“完整”。

配套脚本输出四个子图:共同边界直方图、箱线图、两组均值及样本标准差误差线、峰值标注折线,并精确核对直方计数与误差线来源。新增空组时不要硬算均值,新增单元素组时样本标准差不适用。面试回答统计图时要同时解释视觉编码和统计假设,而不是只说调用 hist 或 boxplot;图形可读、指标正确和解释克制三者缺一不可。

完整示例与实际输出

from pathlib import Path
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from matplotlib import font_manager
ROOT=Path(__file__).resolve().parents[1]; out=ROOT/'outputs'; out.mkdir(exist_ok=True)
names={f.name for f in font_manager.fontManager.ttflist}
font=next((f for f in ['Microsoft YaHei','SimHei','Noto Sans CJK SC'] if f in names),None)
if font:
    plt.rcParams['font.sans-serif']=[font]; plt.rcParams['axes.unicode_minus']=False
else:
    print('No CJK font: English labels used.')
a=np.array([1.,2.,2.,3.,8.,9.]); b=np.array([2.,3.,4.,5.,6.,7.])
edges=[0,3,6,10]; counts,_=np.histogram(a,bins=edges)
assert counts.tolist()==[3,1,2]
fig,axs=plt.subplots(2,2,figsize=(9,6),layout='constrained')
axs[0,0].hist([a,b],bins=edges,label=['A','B'],alpha=.7)
axs[0,0].set(title='Common bin edges',xlabel='Amount',ylabel='Count'); axs[0,0].legend()
axs[0,1].boxplot([a,b],tick_labels=['A','B'])
axs[0,1].set(title='Median / quartiles / 1.5 IQR whiskers',ylabel='Amount')
means=[a.mean(),b.mean()]; sd=[a.std(ddof=1),b.std(ddof=1)]
axs[1,0].errorbar([0,1],means,yerr=sd,fmt='o',capsize=5)
axs[1,0].set(xticks=[0,1],xticklabels=['A','B'],title='Mean ± sample SD',ylabel='Amount')
x=np.arange(1,5); y=np.array([2,6,9,5]); peak=int(y.argmax())
axs[1,1].plot(x,y,marker='o')
axs[1,1].annotate('Peak',xy=(x[peak],y[peak]),xytext=(0,10),textcoords='offset points',ha='center')
axs[1,1].set(title='Peak annotation',xlabel='Day',ylabel='Orders',ylim=(0,12))
path=out/'mpl-subplots.png'; fig.savefig(path,dpi=150,bbox_inches='tight'); plt.close(fig)
assert path.stat().st_size>1000 and peak==2
print('hist counts',counts.tolist(),'means',means,'sample SD',sd)
print('saved',path.name)

实际运行通过CPU Python execution.

hist counts [3, 1, 2] means [np.float64(4.166666666666667), np.float64(4.5)] sample SD [np.float64(3.430257521916783), np.float64(1.8708286933869707)]
saved mpl-subplots.png
多子图、标注及统计图:配套脚本实际生成的图形
配套示例实际生成 · 合成教学数据

五道自测:先回答,再核对

01 · sharey 是否保证两个子图可直接比较?

查看提示

单位与口径是否相同?

查看参考答案

不保证;还需同单位、同指标口径和合理数据范围。

02 · 直方图 density=True 的柱高直接是概率吗?

查看提示

面积需要乘箱宽。

查看参考答案

不是,一般面积代表概率,特别是箱宽不等时不能直接读柱高。

03 · 箱线图须外的点一定是数据错误吗?

查看提示

规则与事实不同。

查看参考答案

不一定,可能是合法尾部观测,应核查原因。

04 · 标准差和标准误分别描述什么?

查看提示

个体与估计。

查看参考答案

标准差描述样本分散,标准误描述均值估计不确定性。

05 · 活动后峰值能直接证明活动导致增长吗?

查看提示

时间顺序不等于因果。

查看参考答案

不能,需要对照、混杂控制或实验等额外设计。

两项动手任务

任务 1 · 用 [0,2,4,6] 作为箱边界统计 [0,1,2,3,4,6],解释右端点规则。

提示

最后一个区间包含最右端点。

完整参考解答与检查方式

三箱计数二、二、二;六属于最后一箱。

import numpy as np
values=np.array([0,1,2,3,4,6]); counts,edges=np.histogram(values,bins=[0,2,4,6])
assert counts.tolist()==[2,2,2]
print(counts.tolist(),edges.tolist())

任务 2 · 比较两组 [1,2,3] 和 [10,11,12] 的均值,用样本标准差误差线并明确图题。

提示

ddof=1;图题写 SD,不写 CI。

完整参考解答与检查方式

两组均值二和十一,样本标准差均为一;这不是置信区间。

from pathlib import Path
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
groups=[np.array([1.,2.,3.]),np.array([10.,11.,12.])]
means=[x.mean() for x in groups]; sd=[x.std(ddof=1) for x in groups]
assert np.allclose(means,[2,11]) and np.allclose(sd,[1,1])
ROOT=Path(__file__).resolve().parents[1]; out=ROOT/'outputs'; out.mkdir(exist_ok=True)
fig,ax=plt.subplots(); ax.errorbar([0,1],means,yerr=sd,fmt='o',capsize=5)
ax.set(xticks=[0,1],xticklabels=['A','B'],title='Mean ± sample SD (n=3 per group)',ylabel='Value')
p=out/'task-errorbars.png'; fig.savefig(p,dpi=150,bbox_inches='tight'); plt.close(fig)
assert p.is_file()
print(means,sd)

官方资料与掌握状态

完成两项任务后,按独立完成程度记录。状态会尽量保存在当前浏览器,可导出备份。

动手任务记录

掌握状态与复习

关掉资料独立完成后记 A;提示后完成记 B;需要重学记 C。

记录尽量保存在当前浏览器,建议定期导出备份。