图表
Histogram - 直方图
一个分布长什么形状。
用法
什么时候用它
来自@misoto22/folio/charts
import { Histogram } from '@misoto22/folio/charts'说明
一个分布的形状——分量压在哪里、往哪边偏、里面是不是不止一个峰。
它回答的正是 <BoxPlot> 从构造上答不了的那些:两簇聚集、一道硬下限、一堆积压在超时值上的观测。要把好几个分布并排比较时,去拿箱线图;要把某一个分布看懂时,拿这一个。
直方图的形状是它分箱宽度的属性,而不只是它数据的属性。同一批数字切成八个分箱和切成八十个分箱,是两张不同的图;两个峰之间的那道空隙,挪一下分箱边界就能造出来,也能抹掉。这不是待修的缺陷,分箱本来就是这么回事,能守住的办法是说清楚这张图是按哪条规则画的——这里的默认是 Freedman–Diaconis——并且在相信某个特征之前多看几个宽度。不等宽的分箱还多一个陷阱:在 frequency 下,底层发生率相同时,宽一倍的分箱就站得高一倍,而 mode="density" 存在的意义就是把这件事纠正过来。
Recharts 在这里挣得的位置是坐标轴、网格和 tooltip,但不包括那些柱子:柱状图的柱子是按分类定位的,而直方图的柱子是由一个连续的度量来定位、并且决定宽度的。所以横轴是数值轴,每根柱子都由它自己的两条边画出来——正因如此,一个不等宽的分箱才能保持它真实的宽度,而不是被压平成邻居旁边一个等宽的槽位。
结构
| 部件 | 说明 |
|---|---|
| 图框必填 | ChartFigure 的那个 <figure>,当分箱之后没有桶留下时会给出空状态——零个有限观测得到的就是这个结果。 |
| 柱子必填 | <Histogram.Bars>,每根都由它所属那个桶自己的两条边画出来,而不是塞进一个等宽的分类槽位,所以宽度不均的桶才能是它真实的宽度。radius 默认是 0,和 BarChart 不同:一个圆角会在两个本该相接的桶之间画出一道缝。 |
| 度量轴必填 | <Histogram.XAxis>,数值轴,从第一个桶的下边界一直走到最后一个桶的上边界。 |
| 计数轴 | <Histogram.YAxis>。在 mode="frequency" 下它是计数;在 mode="density" 下它是计数除以 n 再除以宽度,此时所有柱子围出的面积为 1。 |
| 分箱规则 | 它不是屏幕上的任何一个标记,却是这张图里后果最重的部分:bins 接受一个桶数、或者明确的边界,默认是 Freedman-Diaconis 并封顶在 200 个桶,四分位距为零时退回 Sturges。明确给出的边界同时也是一个范围——落在第一条和最后一条边界之外的观测没有桶可归,它会被计进 tooltip 的占比、以及表格里的 Below 或 Above 行,而不是被丢掉。 |
| 隐藏数据表 | 那张 sr-only 表格把每个桶的两条边印成行表头,计数印在旁边——这是一张分箱图能给出的唯一精确读数,因为每根柱子代表的是一个区间、而不是一个值。落在明确边界之外的观测有自己的 Below 行和 Above 行,因为它们在任何地方都没有柱子。 |
实践建议
推荐
- 把分箱规则写在页面上。同一批数切成 8 个桶和切成 80 个桶,是两张不同的图,而两个峰之间的那道谷,挪动一条边界就能造出来、也能抹掉——所以在相信一个特征之前,多看几种宽度。
- 只要桶宽不均,就设 mode="density"。在 frequency 下,同样的底层密度,宽一倍的桶会高一倍,而从指标后端拿来的、预先计数好的桶,正是一头栽进这个陷阱里的。
- values 和 data 给一个,绝不要两个都给。两个都来时 data 胜出,于是 values 数组既没有被谁分箱、也没有被谁画出来,而且任何地方都没有警告。
避免
- 不要把 tooltip 里的占比读成在各根柱子里的占比。它是在样本里的占比,所以一组桶加起来是 96%,说的是另外那 4% 落到了你自己给的边界之外——而这恰恰是一个按画出来的桶去算的占比永远给不出的读数,因为那种算法总是加到 100。
- 不要用它来比较多个分布。两张直方图叠在一起会互相遮挡,六张并排又放不下;那是 BoxPlot 的事,它每个分布只花十分之一的墨。
- 不要以为自动规则保住了你要的分辨率。Freedman-Diaconis 是拿四分位距去除的,所以一个中间很紧、尾巴很长的分布,会要求画出几万根不到一个像素宽的柱子——200 个桶的封顶把它变成一张粗糙的直方图,而不是一个卡死的标签页,而一张粗糙的直方图是另一张图。
- 不要把只有一个取值的分布读成一个形状。所有观测都落在同一个数上时它照样画得出来:两条边界变成那个值加减二分之一,结果是一根老实的柱子,而它不是一个分布。
示例
默认
一个分布的形状,而这正是箱线图从构造上就展示不了的:这 75 个样本里有两个峰——一条热缓存路径和一条冷的——而同样这些数字画出来是一个整整齐齐的箱子,对哪一个都只字未提。放任不管时,图表按 Freedman–Diaconis 分箱,即 2 倍 IQR 乘以 n 的负三分之一次方,它读的是四分位距而不是全距,所以一个远处的离群点没法把这幅画拉成一根高柱加四十个空槽。要像这里的描述那样,说清是哪条规则画出了这张直方图,因为这个形状既是数据的属性,也同样是分箱宽度的属性。
| label | Count |
|---|---|
| 62 – 103 | 37 |
| 103 – 144 | 6 |
| 144 – 185 | 22 |
| 185 – 226 | 5 |
| 226 – 267 | 3 |
| 267 – 308 | 2 |
分箱宽度
同样这些数字,来四遍。bins 收的是等宽分箱的个数,而挪动它就是在挪动这幅画:五个的时候两个峰并成一个,四十个的时候形状消解成一排计数为一的柱子,而 auto 把这个选择交还给 Freedman–Diaconis。这不是一个该被调掉的缺陷——分箱本来就是这么回事——所以能守住的办法,是像标题那样说清是哪条规则画出了这幅画,以及在相信「两个峰之间那道空隙」这类特征之前,多看几个宽度。
| label | Count |
|---|---|
| 62 – 103 | 37 |
| 103 – 144 | 6 |
| 144 – 185 | 22 |
| 185 – 226 | 5 |
| 226 – 267 | 3 |
| 267 – 308 | 2 |
密度
已经数好了才送来的分箱,宽度是某个指标后端挑的,而且随着数值增大越来越宽。在 frequency 下,100 到 250 那个分箱读起来像一个真实的肩部,而它之所以在那儿,只是因为那个分箱比第一个宽十五倍——一个更宽的分箱,在同样的底层发生率下会收进更多观测。density 把每个计数除以 n 乘以分箱宽度,于是这些柱子围出的面积为 1,那条尾巴也就塌回数据真正说的样子。只要分箱不等宽,就去拿它;只要两张样本量不同的直方图必须相互比较,也去拿它。
| label | Count |
|---|---|
| 0 – 10 | 480 |
| 10 – 25 | 610 |
| 25 – 50 | 540 |
| 50 – 100 | 420 |
| 100 – 250 | 310 |
| 250 – 500 | 120 |
| 500 – 1,000 | 40 |
类型
export type HistogramMode = 'frequency' | 'density'无障碍
- 形状是分箱宽度的属性,不只是数据的属性:同一批数切成 8 个桶和切成 80 个桶,是两张不同的图。画出这张图的规则——默认 Freedman–Diaconis——写在页面上。
- 横轴是数值轴,每根柱子由它自己的两条边画出来,所以宽度不均的桶就是它真实的宽度,而不会被压成和邻居一样的等宽槽位。
- mode="density" 修的正是不等宽分箱制造的陷阱:在 frequency 下,同样的底层密度,宽一倍的桶会高一倍。
- 隐藏的表格视图印出每个桶的两条边和它的计数——这是一张分箱图唯一能给出的精确读数。