2.1 直方图 histplot histplot 把数值轴切成连续区间(箱),统计落入每箱的观测数并画成柱形,是回答"数据集中在哪、有多散"的第一张图。 第 1 章备好了拆解台和数据,本章从最简单的映射开始:一个数值列。本节专注 histplot,把分箱、统计口径、分组叠加三个问题讲透。 分箱是直方图唯一的"超参数" 分箱数在"欠拟合"与"过拟合"之间取平衡:太少掩盖结构,太多让随机波动伪装成峰。经验起点是 10 到 30,再根据数据量微调。也可以只给 ,让箱数由数据范围自动决定——单位明确的数据(金额、年龄)我更倾向给 binwidth,语义更直观: stat 的三种口径 y 轴统计什么,是初学者最容易含糊的地方。 count 适合报告样本量;
histplot 把数值轴切成连续区间(箱),统计落入每箱的观测数并画成柱形,是回答"数据集中在哪、有多散"的第一张图。
第 1 章备好了拆解台和数据,本章从最简单的映射开始:一个数值列。本节专注 histplot,把分箱、统计口径、分组叠加三个问题讲透。
import seaborn as sns import matplotlib.pyplot as plt tips = sns.load_dataset('tips') # 同一份数据,三种分箱,结论可能完全不同 fig, axes = plt.subplots(1, 3, figsize=(13, 3.5)) for ax, bins in zip(axes, [5, 20, 60]): sns.histplot(data=tips, x='total_bill', bins=bins, ax=ax) ax.set_title(f'bins = {bins}') plt.show() # 输出说明:bins=5 时分布看起来又低又平,细节被抹掉; # bins=60 时柱形抖动剧烈,像是噪声;bins=20 附近主峰与右偏形态都清晰
分箱数在"欠拟合"与"过拟合"之间取平衡:太少掩盖结构,太多让随机波动伪装成峰。经验起点是 10 到 30,再根据数据量微调。也可以只给 binwidth,让箱数由数据范围自动决定——单位明确的数据(金额、年龄)我更倾向给 binwidth,语义更直观:
# 每 5 美元一箱,语义比"40 个箱"清楚得多 ax = sns.histplot(data=tips, x='total_bill', binwidth=5)
y 轴统计什么,是初学者最容易含糊的地方。
# 三种统计口径对比 ax1 = sns.histplot(data=tips, x='total_bill', stat='count') # 每箱观测数 ax2 = sns.histplot(data=tips, x='total_bill', stat='probability') # 每箱占比,各箱高度和为 1 ax3 = sns.histplot(data=tips, x='total_bill', stat='density') # 密度,各箱面积和为 1
count 适合报告样本量;probability 便于口头沟通("约三成的账单落在 10 到 20 美元");density 是与核密度曲线叠加的唯一合法口径——直方图与 kde 曲线能画在同一坐标系的前提,就是两边都归一化成密度。
# 按餐段分组的层叠直方图 ax = sns.histplot(data=tips, x='total_bill', hue='time', multiple='layer', alpha=0.5) # 输出说明:两层半透明柱重叠,午餐层明显更薄——晚餐账单才是主力 # 三种 multiple 模式的差别 sns.histplot(data=tips, x='total_bill', hue='time', multiple='stack') # 堆叠,看总量 sns.histplot(data=tips, x='total_bill', hue='time', multiple='fill') # 填充,看各区间内占比 sns.histplot(data=tips, x='total_bill', hue='time', multiple='dodge') # 并排,防遮挡
选择逻辑:关心各组的绝对量用 stack;关心"高消费区间里晚餐占比是否更高"用 fill;两组柱形高度接近、重叠看不清时用 dodge。layer 配合 alpha 适合组数少、形态差异大的场合。
背景:一家餐厅想确认周末账单是否比工作日更"两极分化"——既多小额也多大额。
操作:先做星期到工作日标签的映射(数据层),再画 fill 直方图看各消费区间构成。
tips['is_weekend'] = tips['day'].isin(['Sat', 'Sun']) ax = sns.histplot(data=tips, x='total_bill', hue='is_weekend', binwidth=5, multiple='fill', palette='Set2') ax.set_xlabel('总消费(美元,每 5 美元一箱)') ax.set_ylabel('组内占比') ax.set_title('各消费区间的周末账单占比')
结果解读:成图显示低消费区间(10 至 15 美元)周末占比明显回落,高消费区间(30 美元以上)周末占比抬升,中间段变化平缓。"两极分化"假设在高端一侧成立、低端一侧被否定——如果不画这张图,仅凭均值(周末 21 美元对工作日 17 美元)会得出错误的单边结论。
变式:换成 element='step' 得到阶梯轮廓,比柱形更适合多组叠加;加 kde=True 在柱形上直接叠密度曲线(自动切到 density 口径)。
⚠️ 常见坑:
hue配了列但忘了multiple,两组柱形互相遮挡,读者只能看到前面那组。默认的 layer 在组多、形态接近时几乎必然重叠,养成显式写 multiple 的习惯。
本节要点回顾
- binwidth 优于 bins:单位明确的数据给箱宽,语义可解释;
- density 是叠加 kde 的唯一口径:面积归一化才能和密度曲线同台;
- multiple 四选一:layer 看形态、stack 看总量、fill 看占比、dodge 防遮挡;
- fill 直方图是构成分析的利器:比饼图多保留一个维度,比堆叠柱图更连续;
- 分箱敏感:汇报结论前换两三组分箱参数检查形态是否稳定。
追问一:箱与箱之间有空隙正常吗? 数值直方图默认无缝;若看到空隙,多半是数据里真的存在取值空洞(比如金额集中在整数附近),或者误用了类别轴。检查方法是给 binrange 显式设区间再画一版对比。
追问二:为什么我的直方图有一条尖刺直达顶部? 通常是一个取值占了极高频率——检查是否存在默认值填充(如 0 填充的缺失)。这一根刺本身就是数据质量的证词,删掉它之前先在报告里记一笔。
追问三:log 轴下直方图怎么画? 先 ax.set_xscale('log') 再画,并把 bins 换成对数等距(np.logspace 生成),否则每箱宽度在原尺度上相等,对数轴右半边会被过度切分。
下一节走出"数箱子"的框架,用平滑曲线和累积占比换两个角度看同一个分布。