2.2 核密度与经验分布 kdeplot 用平滑曲线估计概率密度,形态直觉最好;ecdfplot 画经验累积分布,读数精确且无需任何超参数;rugplot 在轴边原位标记每个观测,是识别样本量与离群点的显微镜。 2.1 的直方图依赖分箱,换个 bins 数结论就可能晃动。本节拆两件不依赖分箱(或依赖更少)的工具,它们和 2.1 一起构成分布三板斧。 核密度:带宽决定一切 核密度的原理一句话:每个观测点贡献一个小钟形,全部叠起来再归一化。带宽就是这个钟形的胖瘦。太瘦,单点噪声各自成峰;太胖,真实的双峰结构被焊死在一起。iris 的花瓣长度存在真实的双峰(setosa 与其他两物种分开),这个结构只有带宽合适时才看得见——形态结论必须先过带宽稳定性检查,这是核密度使用的铁律。
kdeplot 用平滑曲线估计概率密度,形态直觉最好;ecdfplot 画经验累积分布,读数精确且无需任何超参数;rugplot 在轴边原位标记每个观测,是识别样本量与离群点的显微镜。
2.1 的直方图依赖分箱,换个 bins 数结论就可能晃动。本节拆两件不依赖分箱(或依赖更少)的工具,它们和 2.1 一起构成分布三板斧。
import seaborn as sns import matplotlib.pyplot as plt iris = sns.load_dataset('iris') # 带宽扫描:bw_adjust 是默认带宽的缩放系数 fig, axes = plt.subplots(1, 3, figsize=(13, 3.5)) for ax, bw in zip(axes, [0.25, 1, 4]): sns.kdeplot(data=iris, x='petal_length', bw_adjust=bw, fill=True, ax=ax) ax.set_title(f'bw_adjust = {bw}') plt.show() # 输出说明:0.25 时曲线出现多个尖峰(把噪声当结构);4 时曲线过度平滑 # (鸢尾花两个明显的物种簇被抹成一个宽包);1 附近才能看到"双峰"这一关键形态
核密度的原理一句话:每个观测点贡献一个小钟形,全部叠起来再归一化。带宽就是这个钟形的胖瘦。太瘦,单点噪声各自成峰;太胖,真实的双峰结构被焊死在一起。iris 的花瓣长度存在真实的双峰(setosa 与其他两物种分开),这个结构只有带宽合适时才看得见——形态结论必须先过带宽稳定性检查,这是核密度使用的铁律。
# 按物种分组后,双峰之谜解开:每个物种内部各自单峰 ax = sns.kdeplot(data=iris, x='petal_length', hue='species', fill=True, alpha=0.3) # 输出说明:setosa 独占低值区,versicolor 与 virginica 部分重叠—— # 这条信息直接指向第 8 章分类任务里 setosa 几乎可完美分出、后两者边界模糊
fill=True 填充曲线下方,多组比较时记得配 alpha,否则后面的组会被盖住。cut 参数控制曲线延伸范围:默认 3 会把曲线画到数据实际范围之外,界面截图或严格报告里设 cut=0 更稳妥。
# ecdfplot:每个台阶表示"小于等于该值的观测占比" ax = sns.ecdfplot(data=tips, x='total_bill') ax.axhline(0.5, color='gray', linestyle='--', linewidth=1) ax.set_xlabel('总消费(美元)') ax.set_ylabel('累积占比') # 输出说明:曲线与 0.5 虚线的交点横坐标即中位数,约 17.8 美元; # 想读"低于 25 美元的账单占多少",直接在 x=25 处读高度即可,误差只在样本级
ecdf 的三个直方图给不了的优势:没有超参数(不分箱、不选带宽);任意分位精确可读;多组比较时曲线的垂直错位直接对应概率差异。代价是形态直觉弱——它不适合一眼判断"有几个峰"。所以我的习惯是:探索阶段 kde 看形态,汇报阶段 ecdf 给精确数字。
# 按星期分组的累积分布对比 ax = sns.ecdfplot(data=tips, x='total_bill', hue='day') # 输出说明:曲线整体靠左的星期(如 Thursday)账单偏小; # Sunday 的曲线在 20 美元以后才追上来,说明它的大额账单占比更高
# 三层叠加:密度曲线 + 地毯 + 中位数参考线 ax = sns.kdeplot(data=tips, x='total_bill', fill=True, alpha=0.25) sns.rugplot(data=tips, x='total_bill', ax=ax, height=0.04, color='dimgray') ax.axvline(tips['total_bill'].median(), color='crimson', linestyle=':') # rug 的每根短竖线就是一个真实观测: # 竖线密集处曲线的峰有数据支撑,曲线有峰但竖线稀疏处就是带宽画出来的假象
rug 是分布图的"证词层"。核密度可能撒谎(带宽不当),直方图可能撒谎(分箱不当),但地毯图只陈述事实:有多少个点、落在哪里。样本量超过几千时 rug 会糊成实心条,那时它反而成了样本量的诚实指示。
分组 kde 还有一个高频误读:默认 common_norm=True 时所有组共享同一次归一化,概率总量按各组观测数分配——两组样本量悬殊时(比如一组几十条、一组几千条),小组曲线会被整体压得几乎贴地,读者会误判该组数据稀薄甚至分布平坦。想比较各组自身的形状,设 common_norm=False 让每组各自归一,形状对比才公平;想表达谁占总量多少,才保留默认。另一个易错点是 NaN:kdeplot 遇到缺失值会静默丢弃整行,不抛错不出警告,某组缺失集中时就会出现这组的曲线凭空消失的现象——画图前先对映射列 dropna,是分布家族的标准前置动作。长尾数据(收入、房价这类跨数量级的形状)直接画 kde,尾部会把主峰压成一根针,配 log_scale=True 换对数刻度后主结构才回到视野中央,该参数与 histplot 的同名参数语义一致,两节的经验可以直接互通。
| 工具 | 回答的问题 | 超参数 | 典型风险 |
|---|---|---|---|
| histplot | 各区间落了多少点 | 分箱 | 分箱敏感 |
| kdeplot | 分布形状如何、峰在哪 | 带宽 | 平滑出假峰或抹掉真峰 |
| ecdfplot | 低于某值的占比多大 | 无 | 形态直觉弱 |
| rugplot | 观测原位在哪、多密 | 无 | 大样本糊成条 |
💡 关键直觉:把 kde 当"形状镜头"、ecdf 当"读数卡尺"、rug 当"证词记录",三者互相校验,分布结论就很难被超参数骗到。

本节要点回顾
- 带宽是 kde 唯一要害:汇报前用 bw_adjust 扫 0.25、1、4 验证峰的稳定性;
- cut=0 防越界:曲线不延伸到数据范围之外;
- ecdf 无超参数:中位数、分位、占比精确可读,多组比较看垂直错位;
- rug 是事实层:曲线与竖线互相印证,防平滑假象;
- 双峰背后常有隐藏分组:iris 的例子说明先拆组再下形态结论。
单变量分布至此拆完。下一节把第二个变量引进来,看 displot 如何分面、jointplot 如何把联合与边缘装进一张图。