☰
茎叶图:小样本数据分布诊断的高效工具与实战技巧
2026/10/29 13:39:47 网站建设 项目流程

茎叶图这个东西,第一次见的人多半会觉得它有点“土”——一堆数字竖着排,中间一条竖线隔开,左边一位右边一串,看起来既不像正经图表,也不像能直接拿去汇报的东西。但我在实际做数据探查和分布诊断的时候,用它的频率反而比直方图还高。原因很简单:它把“形状”和“原始数值”同时保留下来了。直方图告诉你数据大概长什么样,但分箱一划,具体值就没了;箱线图告诉你四分位和异常值,但中间那坨数据怎么堆的,它说不清楚。茎叶图刚好卡在中间,既能一眼看出分布形态、集中趋势、离散程度,又能把每一个原始数据点原封不动地摆在你面前。

这篇内容适合谁看?如果你是刚接触数据分析、统计基础还在补课阶段的人,茎叶图是理解“分布”这个概念最便宜的工具,纸笔就能画;如果你已经天天跟数据打交道,但习惯性只丢给绘图库出图,那茎叶图在小样本快速诊断场景下能帮你省掉不少来回折腾的时间。全文我会从设计思路、构造细节、手工与代码两条实操路径、常见坑位排查几个角度拆开讲,尽量把每个“为什么这么设计”说透,而不是只给你一个画图公式。

1. 茎叶图到底解决什么问题:设计思路与方案取舍

1.1 从“看形状”和“看数值”这对矛盾说起

做数据分布诊断,本质上要回答两个问题:数据整体长什么样,以及具体每个值落在哪。这两件事在传统图表里往往是矛盾的。直方图为了画出形状,必须把连续区间切成若干箱,箱宽一旦定下来,箱内的个体差异就被抹平了。你看到某根柱子很高,只知道“这个区间里数据多”,但到底是集中在区间左端还是右端、有没有双峰被合并进同一个箱,光看柱子是看不出来的。反过来,如果你把原始数据一条条列出来,数值是全了,但几十上百个数排在一起,人眼根本抓不住形态。

茎叶图的设计思路就是用“茎”承担分箱的职责,用“叶”保留箱内的原始精度。举个最直白的例子,数据 23、25、27、31、34,以十位为茎、个位为叶,画出来是:

2 | 3 5 7 3 | 1 4

左边“2”代表 20 到 29 这个区间,右边“3 5 7”就是落在这个区间里的个位数。你既能看到 20 段有 3 个数据、30 段有 2 个数据(形状),又能精确读到 23、25、27 这些原始值(数值)。这就是它区别于直方图的核心价值——分箱不丢信息。

这个特性决定了它的适用边界:样本量小的时候它是神器,样本量大的时候它会“爆行”。你想想,如果有一千个数据,叶子那一列能排到天边去,可读性直接归零。所以业内比较一致的经验是,样本量在 20 到 200 之间是茎叶图最舒服的区间,超过这个范围就该考虑直方图或者密度图了。这不是硬规定,但你在实际用的时候会发现,超过两三百个数据,光是数叶子就够你受的。

1.2 为什么不用直方图或箱线图替代

有人会问,现在绘图库这么方便,直方图一行代码就出来了,为什么还要折腾茎叶图?我的回答是:它们回答的问题不一样,不是替代关系。

直方图的核心参数是箱宽(bin width),这个参数选得好不好,直接决定你看到的形状是不是真的。箱宽太宽,双峰会被糊成一个胖峰;箱宽太窄,噪声会被放大成一堆毛刺。而箱宽怎么选,本身就是一个需要经验判断的事,新手很容易在这里翻车。茎叶图没有这个烦恼,因为它的“箱”是由数据的数量级天然决定的——十位、百位、十分位,这些是数据自带的刻度,不需要你人为设定。换句话说,茎叶图把“分箱”这个主观决策变成了客观的进位规则,这对刚上手的人来说友好得多。

箱线图则是另一个极端,它把数据压缩成五个数(最小值、Q1、中位数、Q3、最大值),异常值单独标出来。它擅长的是快速比较多个组的分布位置和离散程度,但中间那 50% 的数据到底怎么堆的,它完全不告诉你。我遇到过不少情况,两组数据的箱线图看起来几乎一样,但茎叶图一画,一组是单峰对称,另一组是明显的双峰,后续的分析方向完全不同。所以我的习惯是:箱线图用来横向对比多组,茎叶图用来纵向深挖单组,两者配合着看,比单用任何一个都靠谱。

1.1 补充一句关于“手工友好”的价值

还有一点容易被忽略:茎叶图是少数不依赖任何工具就能画出来的统计图。纸笔、白板、甚至聊天窗口里打几行字,都能快速画一个出来。这在很多场景下非常实用——开会时临时要看一组数怎么分布,你不可能当场打开电脑跑代码,但你可以直接在白板上把茎叶图列出来,边列边讲。这种“低门槛即时可视化”的能力,是它至今没有被完全淘汰的重要原因。

2. 构造细节拆解:茎怎么定、叶怎么排、缩放怎么调

2.1 茎的选取:数量级决定一切

茎叶图构造的第一步,也是最关键的一步,是确定“茎”取哪一位。规则说起来简单:茎是数据的高位部分,叶是紧接着的那一位。但实际操作中,取哪一位需要看数据的分布范围。

假设数据范围是 12 到 89,那十位做茎、个位做叶是最自然的,茎从 1 到 8,一共 8 行,清爽。但如果数据范围是 112 到 189,十位做茎就不行了,因为所有数据的十位都是 1 到 8 里的某几个,茎的行数太少,叶子会挤成一团。这时候应该用百位和十位组合做茎(11、12、…、18),个位做叶。再极端一点,如果数据范围是 1.2 到 8.9,那就得用个位做茎、十分位做叶。

这里有个实操中很常见的坑:茎的位数不统一。比如数据里有 5、15、105,如果你机械地“取最高位做茎”,5 的茎是 5、15 的茎是 1、105 的茎是 1,画出来完全乱套。正确做法是先把所有数据对齐到同一数量级,该补零的补零,让茎的位数一致。5 要写成 005,茎取前两位“00”,叶取“5”;15 写成 015,茎“01”,叶“5”;105 茎“10”,叶“5”。这样画出来才是可比的。这个细节很多教程不讲,但你自己动手画一次就会立刻撞上。

2.2 叶的排序:升序是默认,但别盲目排

叶子在每一行里怎么排?标准做法是升序排列,这样每一行的最小值在最左、最大值在最右,配合茎的有序性,整个图从左上到右下就是全局有序的。这个有序性带来一个很实用的副产品:中位数可以直接从图上数出来。总共 n 个数据,从最小的叶子开始数到第 (n+1)/2 个(n 为奇数)或第 n/2 与 n/2+1 个的平均(n 为偶数),不用另外排序计算。

不过有一种情况我会故意不排序:当我想看数据进入的原始顺序时。比如做数据采集质量检查,我想知道后采集的数据是不是系统性偏大或偏小,这时候保持叶子按采集顺序排列,反而能看出时间趋势。这算是茎叶图的一个非主流用法,但确实好用。当然,正式出图给别人的时候还是老老实实升序,不然读者会看得很别扭。

2.3 茎叶缩放:一行不够就拆成多行

当某个茎下面的叶子太多,一行排不下或者挤得看不清时,就需要做茎叶缩放(stem-and-leaf scaling)。常见做法是把一个茎拆成两行或五行。

拆两行的规则是:第一行放叶子 0 到 4,第二行放叶子 5 到 9,两行共用同一个茎值,通常在第一行后面标个标记区分。拆五行的规则更细:分别放叶子 0-1、2-3、4-5、6-7、8-9。拆得越细,形状的分辨率越高,但行数也越多。

什么时候该拆?我的经验判断是:如果某一行叶子超过 15 个,或者整个图的行数少于 5 行,就该考虑拆了。行数太少意味着分辨率不够,形状看不出来;叶子太多意味着单行信息过载。这两个信号出现任何一个,缩放就是必要的。但也要注意别过度拆,拆到五行以上,图会变得很长,反而不好读。一般两行拆分能解决大部分问题。

2.4 一个完整的构造流程示例

拿一组具体数据走一遍。假设有 30 个观测值:

23, 25, 27, 31, 34, 35, 38, 41, 42, 44, 45, 47, 48, 51, 52, 53, 55, 56, 58, 61, 62, 64, 65, 67, 71, 73, 75, 78, 82, 85

第一步定茎:范围 23 到 85,十位做茎,茎从 2 到 8。第二步分叶:把每个数的个位挂到对应茎后面。第三步排序:每行叶子升序。结果:

2 | 3 5 7 3 | 1 4 5 8 4 | 1 2 4 5 7 8 5 | 1 2 3 5 6 8 6 | 1 2 4 5 7 7 | 1 3 5 8 8 | 2 5

看这个图能读出什么?中间 4、5、6 三行叶子最密,说明数据集中在 40 到 69 之间;两端 2 和 8 行叶子稀疏,尾部较轻;整体大致对称,略微右偏(高端拖得稍长一点)。中位数:30 个数据,第 15 和 16 个的平均,从图上数,第 15 个是 51、第 16 个是 52,中位数 51.5。这些信息在三十秒内就能读出来,这就是茎叶图在中小样本上的效率优势。

3. 实操落地:手工绘制与代码实现两条路径

3.1 手工绘制:纸笔场景下的标准动作

手工画茎叶图有一套固定动作,按顺序走不容易出错。

  1. 扫描数据范围,确定最大值和最小值,据此决定茎取哪一位。
  2. 列出所有可能的茎值,从最小茎到最大茎,竖着写一列,后面跟一条竖线。注意即使某个茎下面没有数据,也要保留这一行(留空),因为空缺本身也是分布信息——它告诉你这个区间是断档的。
  3. 逐个读取数据,把叶挂到对应茎后面。这一步不要排序,先挂上去再说,避免边挂边排导致遗漏。
  4. 每行叶子升序排列。排的时候用手指或笔尖点着数,防止漏排。
  5. 检查总数。把所有叶子数一遍,必须等于原始数据个数。这一步是硬性校验,我见过太多人画完发现少了一两个数,回头找半天。

提示:手工绘制时,建议用方格纸或者等宽字体环境(比如代码块、记事本),因为茎叶图的对齐极其重要。比例字体下叶子宽度不一致,图会歪得没法看。

3.2 代码实现:Python 手工构造版

现成的统计库对茎叶图的支持其实不算好,很多库要么没有、要么输出格式不理想。我的做法是自己写一个几十行的构造函数,逻辑透明、格式可控。下面这个版本处理了前面提到的位数对齐和缩放问题:

def stem_leaf(data, stem_digits=1, scale=1): """ data: 数值列表 stem_digits: 茎占的位数(从最高位算起) scale: 1 表示不拆行,2 表示拆两行,5 表示拆五行 """ # 统一数量级:找到最大绝对值,确定需要补几位 max_abs = max(abs(x) for x in data) int_digits = len(str(int(max_abs))) if max_abs >= 1 else 1 # 计算叶的位数 leaf_digits = int_digits - stem_digits factor = 10 ** leaf_digits if leaf_digits > 0 else 1 buckets = {} for x in data: scaled = int(round(x / factor)) if factor > 1 else int(round(x)) stem = scaled // 10 if leaf_digits > 0 else scaled // 10 leaf = abs(scaled) % 10 buckets.setdefault(stem, []).append(leaf) lines = [] for stem in sorted(buckets): leaves = sorted(buckets[stem]) if scale == 1: lines.append(f"{stem:>{stem_digits}} | " + " ".join(map(str, leaves))) elif scale == 2: low = [l for l in leaves if l <= 4] high = [l for l in leaves if l >= 5] if low: lines.append(f"{stem:>{stem_digits}} | " + " ".join(map(str, low))) if high: lines.append(f"{stem:>{stem_digits}} | " + " ".join(map(str, high))) elif scale == 5: groups = [(0,1),(2,3),(4,5),(6,7),(8,9)] for lo, hi in groups: part = [l for l in leaves if lo <= l <= hi] if part: lines.append(f"{stem:>{stem_digits}} | " + " ".join(map(str, part))) return "\n".join(lines) sample = [23,25,27,31,34,35,38,41,42,44,45,47,48, 51,52,53,55,56,58,61,62,64,65,67,71,73,75,78,82,85] print(stem_leaf(sample, stem_digits=1, scale=1))

这段代码的核心逻辑就三步:缩放对齐、分桶、排序输出。factor那一段处理的是小数和不同数量级混排的情况,scale参数控制拆行。实测下来,这个函数在几百个数据以内跑得飞快,输出格式也干净,直接贴进文档就能用。

3.3 代码实现:借助成熟库的快捷路径

如果不想自己写,stemgraphic这个库可以直接出图,支持缩放和美化:

# pip install stemgraphic import stemgraphic data = [23,25,27,31,34,35,38,41,42,44,45,47,48, 51,52,53,55,56,58,61,62,64,65,67,71,73,75,78,82,85] fig, ax = stemgraphic.stem_graphic(data, scale=1)

它的好处是能直接生成带坐标轴的图形版本,适合放进报告;坏处是定制化程度不如自己写函数,输出格式受库的约束。我的建议是:快速探查用库,正式产出用自写函数,各取所需。

3.4 参数选择的经验法则

关于stem_digits和scale这两个参数怎么定,我总结了一个简单的判断表:

数据范围建议茎位数建议缩放理由
0-991(十位)1 或 2行数 10 行以内,叶子密度适中
100-9992(百位+十位)1 或 2保证行数在 10 行左右
0-9(含小数)1(个位)2 或 5行数太少,必须拆行提分辨率
样本量 > 200不建议用茎叶图-改用直方图或密度图

这张表不是金科玉律,但它覆盖了八成以上的日常场景。真正拿不准的时候,先按表画一版,看看行数和叶子密度,再决定要不要调。

4. 常见问题与排查技巧实录

4.1 叶子数对不上原始数据个数

这是最高频的问题,几乎每个手工画茎叶图的人都踩过。原因通常有三个:一是数据里有负数,取叶的时候符号处理错了;二是小数点的位置没对齐,某个数被挂到了错误的茎上;三是纯粹的数漏了。

排查顺序建议这样走:先数总叶子数,和原始数据个数比对;对不上就按茎逐行核对,看哪一行的叶子数和该区间的数据个数不符;锁定行之后,把该区间的原始数据单独列出来,和叶子逐个对。我自己的习惯是画之前先把数据排序,排序后挂叶,挂完再核对,这样漏数一眼就能看出来——排序后相邻的两个数如果茎相同但叶不连续,中间大概率漏了。

4.2 图形严重右偏或左偏,是不是数据有问题

不一定。偏态本身是数据分布的合法形态,收入、房价、响应时间这类数据天然右偏。但如果你预期数据应该对称,画出来却严重偏斜,那就要查两件事:是不是有极端值没处理,以及茎的位数是不是选错了。

极端值的影响很直接,一个 999 混在 20 到 80 的数据里,会把茎的范围硬生生拉到 99,中间一大片空行,图变得又长又稀。这时候要么单独标注极端值,要么先做截尾再画。茎位数选错的影响更隐蔽:如果数据实际范围是 100 到 180,你却用个位做茎,那所有数据的茎都是 0 到 8 里的某几个,叶子全是十位和百位,图会完全失去意义。判断方法很简单:画出来的茎如果只有个位数几行、叶子却密密麻麻,基本就是茎位数选少了。

4.3 双峰被误判成单峰

茎叶图在识别双峰上比直方图有优势,但前提是缩放要够细。如果两个峰靠得很近,不拆行的情况下可能看起来就是一个宽峰。我的经验是:当你在图上看到某一行叶子特别多、而且分布明显不均匀(比如左边密右边疏,或者中间有个明显的空档)时,就该拆行再看一遍。拆成两行或五行之后,原本糊在一起的峰往往会分开。

举个我实际遇到的例子:一组响应时间数据,不拆行时 3 字头那一行有 18 个叶子,看起来是个单峰。拆成两行后,3|0 1 1 2 2 3 3 4 和 3|5 6 7 8 8 9 9 9 明显是两个不同的聚集,前者是正常响应,后者是慢请求,后续排查方向完全不同。这个案例说明,缩放不是可选项,而是诊断深度的调节旋钮。

4.4 常见问题速查表

现象可能原因处理办法
叶子总数少于数据个数漏挂、负数符号处理错排序后逐行核对
图特别长、中间大量空行存在极端值单独标注或截尾
茎只有几行、叶子极密茎位数选少增加茎位数或拆行
形状糊成一团看不出峰缩放不足拆两行或五行
小数数据挂错位置小数点未对齐统一换算成整数再画
负数茎叶混乱符号未单独处理正负分开画两张图

注意:负数处理是茎叶图的一个经典难点。比较稳妥的做法是正数和负数分开画两张图,各自按绝对值构造,然后在图标题里标明正负。混在一张图里处理符号,出错概率极高。

4.5 一个容易被忽略的校验技巧

画完茎叶图后,除了数叶子总数,还有一个快速校验法:用茎叶图反算中位数和极差,和原始数据直接算的结果比对。中位数从图上数,极差看最小茎最小叶和最大茎最大叶。两个数都对得上,基本可以确认图没画错。这个方法比逐个核对快得多,适合数据量中等(50 到 100)的场景。我现在的习惯是画完必做这一步,三十秒的事,能挡掉大部分低级错误。

5. 茎叶图在实际分析中的组合用法

5.1 和数据清洗流程的配合

茎叶图在数据清洗阶段的价值被严重低估了。清洗前先画一张,能快速暴露几类问题:异常值(孤零零挂在最上或最下的叶子)、数据断档(中间空行)、精度不一致(有的叶子是一位有的两位,说明数据来源不统一)。这些问题在表格里翻半天未必看得出来,但茎叶图一画,几秒钟就现形。

我通常的做法是:拿到原始数据先画茎叶图,把可疑区间记下来,再回到原始记录里核对。这比上来就跑描述统计更直观,因为描述统计只给你几个汇总数,而茎叶图给你的是全貌。

5.2 和直方图对照使用

前面说过两者不是替代关系,这里补充一个具体的对照用法:先用茎叶图定形状,再用直方图做呈现。茎叶图帮你判断分布是单峰还是双峰、偏态方向、尾部厚度,这些判断做完之后,你再决定直方图的箱宽怎么设——单峰对称可以设宽一点,双峰就必须设窄一点把两个峰分开。反过来,如果先画直方图,箱宽选错了,你可能根本意识不到有双峰,后面的判断全跟着错。这个顺序上的讲究,是我踩过坑之后才养成的习惯。

5.3 小样本场景下的效率优势

最后说一个很实际的场景:样本量在 20 到 50 之间的时候,茎叶图几乎是效率最高的分布诊断工具。这个量级下,直方图的箱宽怎么设都别扭——设宽了只有三四根柱子,形状出不来;设窄了每根柱子就一两个数,噪声比信号大。而茎叶图在这个量级下刚好每行三五个叶子,形状清晰、数值完整,画起来也就一两分钟。我做过一个粗略的对比,同样一组 40 个数据,从拿到数据到形成“分布是否对称、有无异常、集中区间在哪”这三个判断,茎叶图平均比直方图快将近一半,因为省掉了调箱宽和反复出图的时间。

这个效率优势在探索性分析的早期阶段特别值钱。那时候你对数据一无所知,需要的是快速试错、快速形成假设,而不是精雕细琢的成品图。茎叶图的“糙”在这个阶段反而是优点——它逼着你直接看数,而不是看经过平滑处理的图形,很多细节就是在看数的过程中被发现的。

我个人在实际操作中的体会是,工具没有高低之分,关键是搞清楚它擅长什么、不擅长什么。茎叶图不擅长大数据量、不擅长精美呈现、不擅长多组对比,但在中小样本的快速诊断上,它至今没有对手。把它放进你的工具箱,在合适的时候拿出来用,比盲目追新工具实在得多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询