ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

Matplotlib断轴实现:量级悬殊数据可视化的一图双坐标解法

Matplotlib断轴实现:量级悬殊数据可视化的一图双坐标解法 做数据可视化做过几年的人几乎都撞上过同一个画面一堆数据大部分集中在0到1之间个别几个点直接冲上50甚至更高用matplotlib一画小值部分全被压成一条贴着横轴的线条什么波动都看不出来。我第一次被这问题卡住是给某次运行监测项目画时间序列绝大多数采样点都在0附近小幅震荡偶发几个尖峰跳到几十。线性坐标下那段时间的细节完全糊成一片根本没法向别人解释虽然看起来是平的其实里面很有故事。当时第一反应就是上对数轴但业务方明确说不接受log刻度他们想看到真实量级差。于是我去翻了matplotlib的资料发现官方文档里有一张经典的断轴示例图专门干这件事把坐标轴切掉中间的一段让上下两部分数据各自拥有合适的显示范围同时保持x轴连续。这就是今天要说的matplotlib断轴实现。这篇文章不讲空洞概念直接从原理到底层实现再到可变现的完整代码和实战中容易踩的坑希望给同样被量级悬殊数据折磨的人一点参考。1. 什么时候必须用断轴一条贴地线的自救现场1.1 线性坐标的天然局限你看到的不是数据是最大值的身影matplotlib默认的线性坐标轴做的事情非常简单把所有数据点的最小值和最大值映射到坐标轴的前后两端中间按照等差间隔画刻度。问题就出在等差这两个字上。假设绘图区域的高度是500像素数据范围是0到50那么0到1这个区间只占500的1/50也就是10像素勉强还能看出一点轮廓。但如果数据范围是0到50000到1的区间就只能占到0.1像素比一个像素还细。这个时候所有的小值点都被压缩在同一条水平线上严格来说它们确实画出来了但肉眼根本无法分辨。这不是绘图代码的问题是线性映射的数学特性决定的它给每个数值分配相同的单位像素而不关心这个数值在你的业务场景里是否重要。当最大值和最小值的数量级差别超过两个量级时小值区域的细节就不可避免地被大值吞噬。1.2 先试过的那些替代方案为什么最后都绕回断轴遇到量级悬殊的数据大多数人会按顺序尝试下面几种方案对数坐标log轴网上呼声最高把指数级的增长变成线性。但log轴在业务汇报里经常被挑战因为非技术背景的观众对刻度之间差了10倍这件事没有直觉更麻烦的是数据里一旦有0或负值log轴直接报错或丢点需要额外平移和裁剪。截断大值窗口只把坐标范围定在0到1让超出的部分跑到图上沿外面。这个操作简单但也把那些尖峰到底有多高这个核心问题抹掉了观众只会看到几根线冲出画布并不知道是50还是5000。双Y轴或分组子图适合两类数据但这里只有一条曲线硬拆成两组又破坏同一序列的连续性。归一化处理后画把数据变换到0到1区间再去画图问题是你得在坐标轴标注上做反向换算而且换算后的刻度间隔其实和log一样扭曲了原始量的感受。绕了一圈你会发现真正想要的效果是全局信息保留局部细节放大两者在同一张图里共存。这就是断轴的用武之地。它把坐标轴切成两段各自使用不同范围中间用一个断裂标记告诉观众这段被我裁掉了不表示没有数据只是我不想浪费绘图空间显示它。1.3 什么样的数据适合断轴三个先决条件不是所有贴地线都适合用断轴。根据我踩坑的经验判断标准可以归纳成三条数据呈现明显的分段聚集。比如绝大多数点落在[a, b]少部分落在[c, d]且b远小于c中间是空白区。断轴的本质是让你跳过这段空白如果数据点在整个区间连续分布断轴会把中间一大段有意义的信息隐藏掉。你确认真空区间确实是空白。这是最关键的一条。很多时间序列看似有一段时间没有值实际上只是采样稀疏或者被零值填充直接跳过会丢失这段时间发生了什么。受众能看懂断裂标记。学术论文、正式报告对断轴容忍度低业务看板则相对宽容。你得先确认你的观众不会因为两根斜线就产生误解。满足这三条断轴才是合理的方案否则还是乖乖回log轴。2. 断轴的底层原理两个坐标系拼接成一个连续坐标轴2.1 matplotlib的坐标体系数据坐标、Axes坐标与Figure坐标很多人第一次接触断轴会觉得它是在同一个坐标轴上做点手脚把中间刻度藏起来。其实不是。matplotlib里根本没有删除坐标轴中间一段的原生API。断轴实际做的事是创建两个独立的Axes对象把数据分别画进去然后通过隐藏脊线、调整布局让两个Axes在视觉上看起来像一个连续坐标轴。理解这一点必须弄清楚matplotlib的三层坐标系统数据坐标transData每个Axes用自己的xlim/ylim把数据值映射到绘图框内的位置。不同Axes有不同映射。Axes坐标transAxes以每个Axes绘图区为参照范围是0到1不随数据范围变化。断口的斜线标记就画在这个坐标里。Figure坐标transFigure以整张图页面为参照范围也是0到1用来定位不同Axes之间的相对位置。想象一下断轴的过程你拍了一张非常长的卷轴照片中间一大段是空旷的马路你要给别人展示两头的热闹场景。你不会直接把整张卷轴原样塞进屏幕而是把卷轴从中间剪开把两段有用的部分上下摞在一起中间留一道空隙再在切口处画上锯齿线表示此处剪断。断轴的图就是这张剪开的卷轴。2.2 实现断轴的三个核心步骤第一步创建两个Axes。它们共享同一个数据序列但各自的xlim/ylim不同。第二步隐藏两个Axes相邻一侧的脊线。比如上下断轴时上面的Axes底部脊线和下面的Axes顶部脊线都要隐藏否则两条黑线叠在一起等于告诉观众这是两个独立的图。第三步调整两个Axes的间距。间距不能太大太大会让观众觉得这是两幅图太小的又割裂感太强。通常用subplots_adjust(hspace0.05)这种很小的值。还有一个容易被忽略的细节坐标刻度。断裂侧的刻度线要不要显示显示还是隐藏方向是朝内还是朝外都会极大影响观感。官方示例的做法是上面子图的x轴刻度标签完全隐藏x轴信息统一由下面子图展示下面子图的顶部刻度也一并抹掉避免断裂处出现多余的视觉元素。2.3 坐标变换的拼接本质两个Axes能拼出一个连续的视觉坐标轴依赖的是人眼的格式塔认知——当两个矩形区域上下对齐、边线平直、数据线颜色连续时大脑会自动把它们理解为同一坐标系中被截断的两段。但这里有一个深层的坑两个Axes的数据坐标映射是完全独立的两套比例尺。比如上面的Axes把[4, 6]映射到200像素高度下面的Axes把[0, 1]映射到200像素高度。同样1个单位的数值变化在上面的Axes里占100像素在下面的Axes里只占50像素。体现在图上就是断裂处两侧的折线斜率断崖式变化视觉上被扭曲了。这不是bug而是断轴的固有性质。你选择断轴就等于接受了局部区间被放大这件事。但如果你连两个子图的显示尺寸都不做调整这个扭曲会变得更加不可控。这一点后面在横向断轴的width_ratios部分会再展开。3. 纵向断轴手写实现从数据掩码到坐标轴拼接3.1 先准备一份量级悬殊的示例数据为了把过程讲清楚我用一份模拟数据来做演示一条时间序列横轴0到10纵轴绝大多数时刻在0到1之间小幅震荡但200到260位置附近突然有一段值落在4到6之间。这正好符合断轴的第一条判断标准——数据明显分两堆中间是空白区。import numpy as np import matplotlib.pyplot as plt rng np.random.default_rng(42) x np.linspace(0, 10, 500) y 0.5 * np.sin(x * 1.2) 0.5 rng.normal(0, 0.08, sizex.size) # 人为制造一段高值区域 y[200:260] 5 0.5 * np.sin(x[200:260] * 5) rng.normal(0, 0.1, size60)注意我故意让高值区域在5附近波动低值区域在0到1附近。这样断口可以设置在1到4之间中间确实没有数据点非常适合用断轴展示。3.2 创建两个子图并划分显示范围纵向断轴的核心是把一个纵坐标轴分成两段。我用plt.subplots(2, 1)创建上下两个子图因为两张图要共享横轴的时间信息我把sharexTrue打开。fig, (ax_top, ax_bottom) plt.subplots( 2, 1, figsize(7, 5), sharexTrue, gridspec_kw{height_ratios: [1, 1]} ) # 上子图显示高值区 4 ~ 6下子图显示低值区 0 ~ 1 ax_top.set_ylim(4, 6) ax_bottom.set_ylim(0, 1)height_ratios在这里控制两个子图的高度比例。理想状态下应该按照数据范围的跨度来设置即上子图负责[4,6]跨度2下子图负责[0,1]跨度1所以height_ratios[2, 1]更接近真实比例。这里为了讲解方便先用1:1后续可以自行调整。3.3 数据掩码最容易忽略但非做不可的一步如果你现在直接把ax_top.plot(x, y)和ax_bottom.plot(x, y)都画一遍会得到一个怪异的结果下面子图里y值在5附近的线段会从坐标区底部直升到顶部再被裁掉形成一条穿过整个子图的阴影线上面子图里y值在0到1的线段同样会从坐标区顶部冲下去。matplotlib对超出坐标范围的线段默认做裁剪clip_onTrue但裁剪只能保证线不超出坐标区边框无法阻止从可见区域内的一点连到区域外的一点时在区域边缘留下斜向入/出的痕迹。如果你不想让这些幽灵线段出现必须手动掩码。masked_top np.ma.masked_outside(y, 4, 6) masked_bottom np.ma.masked_outside(y, 0, 1) ax_top.plot(x, masked_top, color#1f77b4, lw1.6) ax_bottom.plot(x, masked_bottom, color#1f77b4, lw1.6)masked_outside会把范围外的所有点标记为无效matplotlib绘制时会自动跳过这些点线段不会跨越掩码区域。这是断轴里最重要的一步很多人废了半天劲拼图最后发现连接处有怪线基本都是漏了掩码。3.4 隐藏脊线与刻度标签完成数据绘制后开始处理拼接。# 隐藏断裂处的脊线 ax_top.spines[bottom].set_visible(False) ax_bottom.spines[top].set_visible(False) # 上子图的底部刻度全部隐藏x轴刻度信息由下子图统一展示 ax_top.tick_params(bottomFalse, labelbottomFalse) # 下子图的顶部刻度也隐藏避免断裂处出现多余刻度 ax_bottom.tick_params(topFalse) # 统一横轴范围 ax_top.set_xlim(x.min(), x.max()) ax_bottom.set_xlim(x.min(), x.max())sharexTrue其实已经让两个子图的x轴范围一致了但手动set_xlim有助于代码可读性也方便后续调整。记着ax_top.tick_params(bottomFalse, labelbottomFalse)这行它同时隐藏了刻度线和刻度标签。3.5 布局微调让两个子图贴得足够近两个子图默认间隙很大一眼看就是两张图。用subplots_adjust把垂直间距压到最低fig.subplots_adjust(hspace0.04)hspace的单位是子图平均高度的比例不是绝对像素。所以图越扁实际间隙越小。我一般从0.02开始调感觉挤了就加感觉散了就减。这一步没有标准答案取决于你的figure尺寸和断轴比例。到这里一个基本的断轴框架已经成型。但还缺最关键的一环——断口处的斜线标记。4. 横向断轴与通用函数封装方向一换坑也换了4.1 横向断轴的完整代码从纵向翻转到横向横向断轴适用于横轴量级悬殊、纵轴共享的场景。典型例子是某段时间序列中前后两段事件密集中间时空窗期很长或者横轴的两个区间跨度相差几十倍。原理完全一致只是把上下换成左右把sharex换成sharey。fig, (ax_left, ax_right) plt.subplots( 1, 2, figsize(8, 4), shareyTrue, gridspec_kw{width_ratios: [3, 1]} ) # 左子图展示 x 在 0 ~ 3 的数据右子图展示 x 在 9 ~ 11 的数据 mask_left (x 0) (x 3) mask_right (x 9) (x 11) ax_left.set_xlim(0, 3) ax_right.set_xlim(9, 11) # 绘制 ax_left.plot(x[mask_left], y[mask_left], color#1f77b4, lw1.6) ax_right.plot(x[mask_right], y[mask_right], color#1f77b4, lw1.6) # 隐藏断裂处脊线 ax_left.spines[right].set_visible(False) ax_right.spines[left].set_visible(False) # 隐藏断裂侧刻度 ax_left.tick_params(rightFalse) ax_right.tick_params(leftFalse, labelleftFalse) fig.subplots_adjust(wspace0.04)这个代码和纵向断轴几乎一一对应但换方向之后有两个全新的坑第一width_ratios如果不按数据范围的实际跨度设置视觉扭曲非常明显。我上面给的示例是[3, 1]因为左边跨度是30到3右边跨度是29到11其实更准确的是[3, 2]。如果右子图数据跨度只有0.5你却给了它和左子图一样的宽度等于把右子图的1个单位拉长了6倍折线斜率被严重拉伸。第二shareyTrue时左右子图共享纵轴范围整条曲线的纵向波动比例在两张图里是一致的所以横向断轴在斜率保真上比纵向断轴更容易做好——只要width_ratios按横轴跨度分配。4.2 数据范围与绘图比例的换算逻辑这里值得单独说一下width_ratios和height_ratios到底该怎么设。原则非常简单比例应当等于两个子图各自负责的数据范围跨度之比。纵向断轴上子图显示[4,6]跨度2下子图显示[0,1]跨度1那么height_ratios[2,1]。横向断轴左子图显示0到3跨度3右子图显示9到11跨度2那么width_ratios[3,2]。为什么要纠结这个比例因为断轴已经人为改变了数据到屏幕的映射如果子图尺寸比例还不按数据跨度走那么曲线的形状就被二次扭曲了。比如同样拉伸了0.1的数值断口两边本来应该看起来是相同倾斜角度结果因为宽度比例失调一侧陡一侧缓观众会误判数据变化的剧烈程度。当然现实项目里为了让断口部分更容易看清偶尔会故意放大某一侧比如右侧数据量少但波动幅度重要就给它额外加宽。这属于有意识的视觉强调不算错误但要在图上用文字标注清楚否则就是误导。4.3 封装成可复用函数断轴不该每次都从头写断轴的代码本身不复杂但每次都从头拼一遍容易忘掩码、忘隐藏脊线。我后来把它收拢成一个函数逻辑是你传进两个区间的数据和坐标范围我帮你把图拼好。def draw_broken_axis( x_upper, y_upper, x_lower, y_lower, ylim_upper, ylim_lower, figsize(7, 5), ratios(2, 1), color#1f77b4, ): fig, (ax_top, ax_bottom) plt.subplots( 2, 1, figsizefigsize, sharexTrue, gridspec_kw{height_ratios: list(ratios)} ) ax_top.set_ylim(*ylim_upper) ax_bottom.set_ylim(*ylim_lower) ax_top.plot(x_upper, y_upper, colorcolor, lw1.6) ax_bottom.plot(x_lower, y_lower, colorcolor, lw1.6) ax_top.spines[bottom].set_visible(False) ax_bottom.spines[top].set_visible(False) ax_top.tick_params(bottomFalse, labelbottomFalse) ax_bottom.tick_params(topFalse) fig.subplots_adjust(hspace0.04) return fig, (ax_top, ax_bottom)封装函数唯一的风险是太黑盒。把x_upper/y_upper/x_lower/y_lower作为参数传进去意味着掩码在调用处就已经完成函数内部不关心数据来源这样反而灵活。如果你想更省事可以让函数内部自动掩码但我觉得断口范围这种东西还是显式传进来更可控。5. 让断口更好看斜线标记、脊线隐藏与视觉欺骗的细节5.1 官方Demo的斜线画法四条短线解决一切断口斜线是这个图最重要的视觉提示。matplotlib官方示例里用plot画了四条非常短的斜线分别放在上子图下边缘两端和下子图上边缘两端。# 斜线长度基于Axes坐标的比例 d 0.015 kwargs dict(transformax_top.transAxes, colork, clip_onFalse, lw0.8) # 上子图下边缘左下斜线 右下斜线 ax_top.plot([-d, d], [-d, d], **kwargs) ax_top.plot([1 - d, 1 d], [-d, d], **kwargs) kwargs.update(transformax_bottom.transAxes) # 下子图上边缘左上斜线 右上斜线 ax_bottom.plot([-d, d], [1 - d, 1 d], **kwargs) ax_bottom.plot([1 - d, 1 d], [1 - d, 1 d], **kwargs)逐行解释一下。transformax_top.transAxes表示传入的坐标点是相对于该子图绘图区归一化后的位置(0,0)是坐标区左下角(1,1)是右上角。第一条ax_top.plot([-d, d], [-d, d])画的是一条从(-d,-d)到(d,d)的线段方向是左上到右下长度大约是0.02 * axes宽度。同理[1-d, 1d]表示x以1为中心向两侧各伸d画的是右上角附近的斜线。这四条斜线组合起来就像一个被掰断的裂口。clip_onFalse至关重要。如果不加斜线一旦碰到坐标区边界就会被裁剪掉。因为我们想让它横跨在坐标区边缘外侧一点点必须关闭裁剪。lw0.8是我个人觉得比较克制的线宽太粗会喧宾夺主。5.2 刻度方向与断口留白的微调经验斜线画好后断轴图的主体完整了。但还有几个细节决定这张图是能用还是好看。第一个是刻度方向。matplotlib的刻度默认朝外在断口处的短刻度线如果朝向断裂内部会和斜线打架。我习惯把非断裂侧的刻度保持默认断裂侧完全隐藏。纵向断轴里上子图底部刻度已经bottomFalse敢干净了下子图顶部刻度也做了topFalse。这样断裂处只有斜线没有别的干扰元素。第二个是断口留白宽度。hspace太小上下两块的坐标区边缘会几乎贴在一起斜线看不清楚hspace太大割裂感明显。按我的经验hspace0.04到0.08是安全区。如果你发现斜线被hspace挤到下一张图的区域说明间距确实太小了。第三个是脊线颜色。很多示例图把断口处的脊线隐藏后其他三条脊线还是默认的黑色。但如果你调整了hspace两块图中间其实有一条很细的白色间隔黑线-白缝-黑线会形成三条线视觉上有点杂。可以在隐藏断裂脊线后把相邻另一侧脊线也保留只是靠hspace让观众感知成断口。我见过有人把子图背景色改成浅灰让图面更一体化但那样斜线颜色就要相应调整不太推荐新手搞。5.3 网格、图例和坐标标签的特殊处理断轴里用网格要非常谨慎。两个子图各自画网格断口处的网格线必然错位观众一眼就能看出这是两张图破坏视觉连续性。如果数据本身需要网格辅助读值我建议只在下面的主子图里ax_bottom.grid(True, ls--, alpha0.3)上面子图不放网格。图例是个更麻烦的事。默认ax.legend()会让图例落在当前子图的坐标区内。断轴图通常两个子图都需要显示图例但放两份图例很累赘。我的办法是只在一个子图上放图例然后指定bbox_to_anchor把它放到图外的公共区域ax_top.legend(locupper left, bbox_to_anchor(0, 1.05))或者干脆不用matplotlib的legend改用fig.text()手动添加一个文本标注配合断轴这个非常规图表反而更干净。6. 实战排查跨越断口连线、坐标联动与对数轴断轴6.1 跨越断口的幽灵连线问题第一个坑也是最常出现的坑断轴图最常见的翻车现场是明明设置了两个子图的坐标范围数据线却像是被什么东西从断口处强行连了一段过去。这个问题的根源我在前面已经反复强调过——没有做掩码。但还有一种更隐蔽的变体掩码做了但掩码的边界值和坐标范围没有对齐。比如上子图ylim是(4, 6)你用masked_outside(y, 4.1, 5.9)那么4到4.1之间的点虽然在显示范围内却被掩码过滤掉了上面子图这一段会出现空白而下子图ylim是(0, 1)你却用masked_outside(y, 0, 1.2)1到1.2之间的点会多画出来在断口边缘形成一小段越过边界的线。排查办法很简单删掉掩码观察哪些线段异常出现再把坐标范围打印出来和掩码范围对齐。两个范围必须完全一致连边界的浮点误差都可能造成1像素宽度的异常线段。6.2 两个Axes的坐标联动缩放交互时的错位静态图片不存在这个问题但如果你要交付的是可交互的notebook图表就会碰到上子图和下子图虽然是sharexTrue创建的但当你用鼠标滚轮在一个子图上缩放后另一个子图的x轴范围不一定会同步更新——具体表现取决于matplotlib版本和交互后端。断轴图最怕的就是两个子图各滚各的因为观众必须一直对比两条x轴有没有对齐。解决办法是在创建子图后手动绑定x轴变化事件让上下两个子图的set_xlim始终保持一致。def sync_xlim(ax_src, ax_dst): def on_xlim_changed(event_ax): if event_ax is ax_src: ax_dst.set_xlim(ax_src.get_xlim()) return on_xlim_changed ax_top.callbacks.connect(xlim_changed, sync_xlim(ax_top, ax_bottom)) ax_bottom.callbacks.connect(xlim_changed, sync_xlim(ax_bottom, ax_top))注意sharexTrue在初始状态下已经共享轴范围这个回调主要用来处理后续交互态的变动。如果你的场景不需要交互那直接忽略即可。纵向断轴的y轴互不相关但横向断轴的shareyTrue对应的是y轴共享同样的问题会发生在两个子图的y方向缩放上处理方法一模一样只是把事件从xlim_changed换成ylim_changed。6.3 对数坐标下断轴比例计算要用对数后的跨度有些特殊情况断轴和对数坐标要一起用。比如低值区域从0.01到1高值区域从100到500中间差了好几个量级但业务方既要看真实的指数形态又想跳过中间的空白区间。操作上以纵向断轴为例做法是在两个子图上都加一行ax_top.set_yscale(log) ax_bottom.set_yscale(log)然后其他逻辑不变。但有两个细节第一数据范围内的0和负值会被log轴丢弃掩码时要先把非正值过滤掉否则matplotlib会警告甚至绘制异常。第二height_ratios的计算不能再按原始数值跨度而要按照对数变换后的跨度。比如上子图显示100到500log10之后是2到2.7跨度0.7下子图显示0.01到1log10之后是-2到0跨度2那么height_ratios应该大概是[0.7, 2]而不是[400, 0.99]。否则图上子图的物理高度和它负责的坐标距离不成比例。另外log轴在断口附近的刻度标签可能自动生成非常奇怪的间隔比如上子图有刻度100、200、300下子图却是0.01、0.1、1两者之间毫无关系这是正常现象你只需要确保每个子图的刻度都在它自己显示的范围内。6.4 多段断裂是否可行理论上三段、四段断裂的做法完全一样创建N个子图依次隐藏相邻脊线画对应位置的斜线。实际项目里我做得比较少因为超过两段之后观众的认知负担会急剧上升——他们需要记住这是个不连续坐标轴而且断口还不止一个。如果确实需要多段建议用循环生成子图而不是手动复制代码。核心逻辑是n_segments 3 fig, axes plt.subplots(n_segments, 1, sharexTrue, gridspec_kw{height_ratios: ratios}) for i, ax in enumerate(axes): ax.set_ylim(ylim_list[i]) # 隐藏除最外侧以外的脊线上子图隐藏bottom下子图隐藏top中间子图两个都隐藏中间子图的上下脊线都隐藏斜线也要画两套。图例、网格、标签的复杂度成倍增加所以我个人建议非必要不推荐。7. 别滥用断轴画得出来也要讲得清楚7.1 断轴在可视化诚信上的争议断轴图在数据可视化圈子里一直有争议。反对者认为它天然带有误导性坐标轴缩放的突变会让观众无法正确感知数据变化的幅度。同一组数据两个区间从10涨到12在断轴图里可能看起来翻了不止一倍因为你的缩放把这一小段放大了。这个批评是成立的。断轴本质上是在告诉观众我要用两套比例尺来画同一张图而多数人对图形的第一直觉仍然是坐标是等距连续的。所以使用断轴的前提是你必须在图上明确标注断裂区间最好加一行文字说明中间数据已省略坐标刻度不连续。我在项目里会习惯在断口附近加一行小字格式通常是ax_top.text(0.5, 1.02, 区间 (1, 4) 已省略, transformfig.transFigure, hacenter)。虽然不能完全消除误导但至少做到了可视化上的诚实。7.2 哪些场景我建议果断放弃断轴下面这些情况我更倾向劝你别用断轴学术论文或正式研究报告。审稿人普遍不认可断轴要么分面展示要么用其他变换。除非期刊明确接受这种形式否则只会增加被challenge的风险。数据在断口区间内有重要信息。你为了视觉清爽把中间一段剪掉可这段里恰恰包含一次业务关键事件那就得不偿失。数据分布是连续的。比如正态分布样的钟形曲线你把中间区域砍掉等于砍掉了数据最主要的形态特征整张图已经没有意义。观众缺乏基础预期管理。如果投放的是一张无人讲解的静态图很多第一次见到断轴的人根本不知道那个锯齿线是什么意思也不会反应到纵轴不连续他们只会觉得这个图很奇怪。7.3 替代方案再审视分面、稀释采样、注释标注在放弃断轴之后还有三条退路值得考虑一个是分面图facet。上下两个子图各自用完整坐标范围但x轴共享中间不加斜线而是用大标题清楚区分。这种做法的优点是完全诚实缺点是视觉连贯性弱。另一个是局部放大图zoom-in inset。画一张全量图再画一个局部放大图作为插入图。matplotlib的inset_axes就能做比断轴更不容易引起误解唯一的问题是全局和局部被分开呈现无法在同一个断裂坐标系里对比位置关系。还有一种是保持线性轴但用颜色或注释强调关键区间。有时候你真正想展示的并不是小值和大值的对比而是某一段时间的操作造成了什么影响那直接把那个区间用色块高亮标注就足够了根本不需要动坐标轴。最后说一句我自己的体会。断轴是一个很有用的工具但它不是一个默认方案。我现在的选择标准很简单先用log轴不行就分面分面还不行再考虑断轴。而且一旦决定用断轴我一定会把断口范围和坐标刻度不连续这两件事在图上写得清清楚楚。图表是给人看的信息准确永远比视觉效果优先。这才是断轴正确的打开方式。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表