4.1 类别散点 strip 与 swarm stripplot 在每个类别带的纵向撒点(默认加随机抖动),swarmplot 用算法把点排成互不重叠的蜂群形。它们是分类图压缩光谱的零压缩端:每个观测都在场上。 第 3 章末尾说 x 轴换成类别后位置语义改变。本节先看最诚实的做法——不摘要、不聚合,把观测原样摆出来,再讨论它们与摘要图的叠加。 抖动是特性不是缺陷 注意抖动只发生在 x(类别)方向,y 值纹丝不动——数据没有被篡改,只是展示上错开。 可以手动控制幅度,超过 0.5 点会跑出类别带,读者就分不清归属了。 蜂群:算法排位,形状即分布 strip 与 swarm 的取舍:strip 快、能扛大样本(几千点也只是带变宽);
stripplot 在每个类别带的纵向撒点(默认加随机抖动),swarmplot 用算法把点排成互不重叠的蜂群形。它们是分类图压缩光谱的零压缩端:每个观测都在场上。
第 3 章末尾说 x 轴换成类别后位置语义改变。本节先看最诚实的做法——不摘要、不聚合,把观测原样摆出来,再讨论它们与摘要图的叠加。
import seaborn as sns import matplotlib.pyplot as plt tips = sns.load_dataset('tips') fig, axes = plt.subplots(1, 2, figsize=(10, 4)) sns.stripplot(data=tips, x='day', y='total_bill', ax=axes[0]) axes[0].set_title('jitter 默认 True') sns.stripplot(data=tips, x='day', y='total_bill', jitter=False, ax=axes[1]) axes[1].set_title('jitter 关闭') plt.show() # 输出说明:关掉抖动后同金额的点完全重合,周四的带看起来只有十几个点, # 实际有 62 条观测——jitter 是在 x 方向加随机偏移,让重叠的点互相让位
注意抖动只发生在 x(类别)方向,y 值纹丝不动——数据没有被篡改,只是展示上错开。jitter=0.25 可以手动控制幅度,超过 0.5 点会跑出类别带,读者就分不清归属了。
# swarmplot:每个点精确互斥排列,点的轮廓天然呈现密度 ax = sns.swarmplot(data=tips, x='day', y='total_bill', size=4) ax.set_xlabel('星期') ax.set_ylabel('总消费(美元)') # 输出说明:周日带的轮廓上宽下窄再收细——中部最厚,两端稀疏, # 一个"右偏分布"的形状不需要任何统计量就直接可读
strip 与 swarm 的取舍:strip 快、能扛大样本(几千点也只是带变宽);swarm 形状信息丰富,但算法是精确排位,样本过千时计算慢、带宽溢出(点会横向溢出所属类别区),Seaborn 会直接警告并拒绝绘制。经验线:百级样本用 swarm,千级以上用 strip 加透明度。
# hue 分组时 strip 可以错位 sns.stripplot(data=tips, x='day', y='total_bill', hue='time', dodge=True) # dodge=True 让午餐晚餐在类别带内左右让位,各自一条竖列
单独的蜂群图已经很好,但和箱线图叠加后信息量再上一层——第 3.1 节"散点兜底、聚合结论"的思路在分类场景的翻版:
# 箱体做摘要,蜂群做证词 ax = sns.boxplot(data=tips, x='day', y='total_bill', showfliers=False, # 关掉箱线图自己的离群点标记,避免与蜂群打架 boxprops={'facecolor': 'none'}) # 箱体只描边不填充,不挡点 sns.swarmplot(data=tips, x='day', y='total_bill', ax=ax, size=3.5, color='0.3') ax.set_xlabel('星期') ax.set_ylabel('总消费(美元)') # 输出说明:灰黑色蜂群穿过透明箱体,中位线横在点云腰部, # 离群点是真实观测还是摘要判定一眼可查
叠加的两个细节:showfliers=False 防止箱线图把离群点画成空心圈与蜂群点重叠;箱体去填充让下层点可见。这套"空箱加蜂群"的组合是论文与报告里的常客。
背景:一份体检汇总表,医生怀疑某年龄段的血糖异常值集中在少数人身上,需要定位到个体。
操作:
# 构造演示数据:三组各 80 人,第三组混入 6 个高位异常 import numpy as np import pandas as pd rng = np.random.default_rng(7) df = pd.DataFrame({ 'age_group': np.repeat(['30-39', '40-49', '50-59'], 80), 'glucose': np.concatenate([ rng.normal(5.1, 0.5, 80), rng.normal(5.4, 0.6, 80), np.concatenate([rng.normal(5.8, 0.6, 74), rng.uniform(9.5, 12.0, 6)]), ]), }) ax = sns.swarmplot(data=df, x='age_group', y='glucose', size=4) ax.axhline(6.1, color='crimson', linestyle='--', linewidth=1) # 临床参考上限 ax.set_xlabel('年龄组') ax.set_ylabel('空腹血糖(mmol/L)')
结果解读:50-59 组的蜂群顶部清晰地分离出 6 个孤立点,全部越过红色参考线;它们与主群体之间有一段空白区,说明是"另一群人"而不是连续尾巴的末端。摘要图只能报告"该组均值偏高 0.4",定位不到这 6 人——零压缩展示的价值就在此。变式:给这 6 个点追加标注(ax.annotate 逐点写编号),图就从分析图升级成行动清单。
⚠️ 常见坑:swarmplot 对大数据集的警告不是吓唬人——超过约 1000 点时会因带宽溢出抛错。那时换 stripplot 加
alpha=0.3,或先df.sample(800)抽样。
本节要点回顾
- 抖动只在类别方向:y 值不失真,jitter 控制在 0.5 以内;
- 蜂群形状即密度:带宽轮廓直接呈现偏态与多峰;
- 样本量分界:百级 swarm、千级 strip、万级改画分布摘要;
- 空箱加蜂群:showfliers 关闭、箱体去填充,证词与判决同框;
- dodge 处理双类别:hue 分组时让竖列互相让位。
下一节沿压缩光谱向上,看箱线、小提琴如何摘要分布,柱状、点图如何只留一个统计量。