图形化的根本不在配色和排版,在比较成不成立。 比较要成立,得同时满足三条:同口径、同基线、同尺度。 截断坐标轴是这三条里最常被破的一条。 同一组数,基线从 0 换成 600,30 单的差别能画出 4 倍的观感。 一张图里能同时比较的量越多,它越有用——但前提是每个比较都还站得住。
前三篇一直在解决「装几个变量」的问题。这一篇反过来,讲「装进去之后怎么比才不算骗人」。 这一篇的三个 Demo 数都是现算的:柱子高度比、放大倍数,都能自己复现。
一、比较成立的三条硬条件
同口径:两个数的分母得一样。转化率的分母是访客还是浏览量,不能一半一半。 同基线:两根柱子的起点得一样。一根从 0 起、一根从 600 起,长度就没有可比性。 同尺度:两张图的纵轴范围得一样。一张到 700、一张到 650,曲线陡不陡全是缩放说了算。
| 条件 | 破了的典型做法 | 后果 |
|---|---|---|
| 同口径 | 一个用访客、一个用浏览量算转化率 | 差距可能翻倍 |
| 同基线 | 柱子从 600 起画 | 30 单看成 4 倍 |
| 同尺度 | 两张图纵轴范围不同 | 平地看成悬崖 |
这三条不是审美问题,是逻辑问题。破了任何一条,图上的比较就不成立,读图的人得到的结论一定错。
二、截断坐标轴:30 单画出 4 倍的观感
4 月订单 610,5 月 640。差 30 单,4.9%。 基线从 0 起:两根柱子高度比 1.049 比 1,读起来几乎一样高。 基线从 600 起:两根柱子高度比 4.0 比 1,读起来像是一下蹿上去了。
同一组数,同一套配色,观感放大了 3.8 倍。数据一个都没改,改的只是 y 轴的起点。 这不是说截断一定是错的。图 B 确实把 30 单的波动形态画清楚了——它只是不适合用来「比大小」。
三、什么时候截断是允许的
看趋势细节时可以截:比如 610 到 640 之间那点波动,从 0 起画就是一条平线,什么都看不出来。 比大小时不能截:人眼比的是柱子的物理长度,不是柱子上的数字。 规矩只有一条:截断就必须在轴上标出来,并且在标题或注释里说清。截了不说,就是骗人。
折线图还有个更隐蔽的版本:纵轴不从 0 起,是折线图的常态。 所以看折线图的第一件事,是看左下角那个数是多少。
四、一次只比一对,是最安全的比较
两条柱子比长度,是精度最高的比较方式。三条以上就开始费劲,超过七八条基本靠扫。 所以图不是装得越多越好。「能同时比较的量越多越有用」这句话有个前提:每一对比较都还看得清。 装不下的时候有两个办法:一是分面,把大类拆成几张小图;二是换图形,用位置代替长度、角度。
五、能同时比较的量越多,这张图越有用
回头看前一篇的气泡图:一张图上能比三件事——城市之间的转化率高低、客单价高低、订单量大小。 再往上一层是分面:四张小图并排,读者能在「格与格之间」和「格内」同时比较, 这是二维纸面上信息量最大的排法。 但每加一个变量,就多一个被误读的机会。加到第四个、第五个,就得问自己:读者真的会看吗?
图的容量由读者的注意力决定,不由画图软件的功能决定。
六、动手 Demo:正确比较:别让坐标轴骗人
同一组数(610 和 640)画两张图,只改 y 轴的起点,量一量柱子高度的比值。 比值不是肉眼估的,是按「柱高占整个轴的比例」算出来的。
import os
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
def save(name, ylim, color, title):
fig, ax = plt.subplots(figsize=(5.0, 3.2), dpi=110)
ax.bar([0, 1], [610, 640], width=0.5, color=color)
ax.set_ylim(*ylim)
ax.set_xticks([0, 1])
ax.set_xticklabels(["Apr", "May"], fontsize=8)
ax.set_title(title, fontsize=9)
fig.tight_layout()
fig.savefig(name)
plt.close(fig)
print("%s 存成 %d 字节" % (name, os.path.getsize(name)))
save("axis0.png", (0, 700), "#7f9fd0", "axis from 0")
save("axis600.png", (600, 650), "#d08f7f", "axis from 600")
def ratio(lo, hi, ymin, ymax):
return ((hi - ymin) / float(ymax - ymin)) / ((lo - ymin) / float(ymax - ymin))
r0 = ratio(610, 640, 0, 700)
r6 = ratio(610, 640, 600, 650)
print()
print("基线 0 柱高比 %.3f 比 1" % r0)
print("基线 600 柱高比 %.1f 比 1" % r6)
print("真实差别 %.1f%%,视觉放大 %.1f 倍" % ((640 / 610.0 - 1) * 100, r6 / r0))
实跑输出:
--------------------------------------------------------------------
正确比较:别让坐标轴骗人
--------------------------------------------------------------------
① 两个只差 5% 的数
--------------------------------------------------------------------
新军队的 4 月订单 610,5 月 640。差 30 单,4.9%。
同一组数,画两张图,看起来能差出好几倍。
② 图 A:基线从 0 起
--------------------------------------------------------------------
两根柱子的高度比:1.049 比 1
读起来:几乎一样高。存成 PNG 9082 字节
③ 图 B:基线从 600 起
--------------------------------------------------------------------
两根柱子的高度比:4.0 比 1
读起来:5 月像是一下蹿上去了。存成 PNG 9476 字节
④ 同一组数,视觉差多少
--------------------------------------------------------------------
| 画法 | 柱子高度比 | 读者感受 |
|---|---|---|
| 基线 0 | **1.05 比 1** | 几乎没变 |
| 基线 600 | **4.0 比 1** | 像蹿了一大截 |
真实差别是 4.9%,截断坐标轴把视觉差放大了 3.8 倍。
! 截断坐标轴本身不是错,错的是不告诉读者你截了。
⑤ 什么时候可以截断
--------------------------------------------------------------------
看趋势的细节时,可以截——比如 610 到 640 这 30 单的波动形态。
比大小时,不能截——因为人眼比的是柱子长度,不是数字。
规矩很简单:截断必须在轴上标出来,并在标题里说清。
4.0 比 1 这个数不是估的:基线 600、轴顶 650,柱子高度分别是 40 和 10,正好 4 倍。 换根轴就能把 4.9% 变成 4 倍观感,这就是为什么「看图说话」必须先看轴。
30 秒实验
- 把基线改成 590、轴顶 660,柱高比会变成多少?用上面的 ratio 算一下。
- 如果两根柱子改成折线的两个点,截断带来的误导会更严重还是更轻?
- 想一想:折线图几乎从不从 0 起,为什么它反而没被当成骗人?
常见误区
- 截断不标注:轴上不写起点、标题不提,读者只能按比例去读,一定读错。
- 两张图纵轴不一样还并排放:这是最容易犯又最难自察的一种,因为每张图单独看都「没问题」。
- 比大小却用面积:气泡大小比的是面积,人眼估不准,换成柱长就清楚了。
- 只怪画图的人:读图的人也有责任——看柱子先看 y 轴起点,看折线先看左下角。
自测三问(点开对答案)
1. 比较成立的三条硬条件是什么? —— 同口径(分母一致)、同基线(起点一致)、同尺度(轴范围一致)。 2. 为什么截断坐标轴会把差别放大? —— 人眼读的是柱子的物理长度。起点抬高后,两根柱子都要剪掉同一截,剩下的比例被放大。 3. 折线图为什么几乎都不从 0 起? —— 因为它看的是变化形态,不是绝对量。但代价是:陡峭程度由轴的范围决定,不能直接用来比幅度。比较的规矩讲完了。下一篇把变量加到三个以上,看多元图形怎么装、代价是什么。
- ☐ (本篇完,下一篇 4.5 多元图形)