matplotlib用brokenaxes画断轴(broken axis)

matplotlib 画折线图时,有一些值异常大的离群点,会把其它曲线压得很密。想把 Y 轴断成两截,将离群点隔离。

normal plotting

原本画的图如下:

  • 普通 plot,没用断轴
  • 中文设置见 [1]
  • 科学表示法见 [2,3]
  • 允许 marker 超出边框见 [4]
  • 公式 latex 支持见 [5,6]

normal

可以看到,蓝色线第一个值远大于其它数据,使得另两条线的走势不明显。源码如下:

import matplotlib
font_config = {
    "font.family": 'serif', # 衬线字体
    "font.size": 12, # 相当于小四大小
    "font.serif": ['SimSun'], # 宋体
    "mathtext.fontset": 'stix',
    'axes.unicode_minus': False # 处理负号,即-号
}
matplotlib.rcParams.update(font_config)
matplotlib.rcParams['mathtext.default'] = 'regular'
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
from matplotlib.pyplot import MultipleLocator
from brokenaxes import brokenaxes
import numpy as np


MARKER = "os^"
LABEL = ["Tom", "Jerry", "Spike"]
BIT = 32
X = np.arange(BIT + 1)
Y = [
    np.array([80000] + [0] * BIT, dtype=np.float32),
    np.array([
          8.7756,   42.7066,   95.9444,  176.1248,  317.6762,  519.3036,
        750.4818,  994.5044, 1271.069 , 1556.7432, 1815.9172, 2069.5258,
       2304.2496, 2502.4506, 2672.5138, 2824.4458, 2974.3928, 3131.4864,
       3312.285 , 3546.2226, 3854.402 , 4210.833 , 4613.9742, 5041.3194,
       5348.7834, 5372.729 , 5148.2434, 4680.7672, 3703.1876, 2588.9898,
       1604.4842,  715.7446,  229.723
    ]),
    np.array([
        3.2206080e+02, 1.2220866e+03, 2.4709372e+03, 3.7537480e+03,
        4.8803058e+03, 5.7186174e+03, 6.1935612e+03, 6.3408946e+03,
        6.2374614e+03, 5.9762898e+03, 5.6437108e+03, 5.2912106e+03,
        4.9258044e+03, 4.5464280e+03, 4.1430268e+03, 3.6587404e+03,
        3.0297596e+03, 2.2955004e+03, 1.5692284e+03, 9.2766680e+02,
        4.5780320e+02, 2.0981440e+02, 1.0106980e+02, 5.0046200e+01,
        2.3046000e+01, 8.0620000e+00, 2.3904000e+00, 6.9840000e-01,
        2.9800000e-02, 8.0000000e-04, 0.0000000e+00, 0.0000000e+00,
        0.0000000e+00
    ]),
]


fig = plt.figure()
ax = plt.gca()

for y, _label, _marker in zip(Y, LABEL, MARKER):
    plt.plot(X, y, label=_label, marker=_marker, clip_on=False)

plt.xlabel("距离", fontsize=20)
plt.ylabel("实例数", fontsize=20)

plt.title(r"$\times\_\times$", fontsize=20)

plt.xlim((0, BIT))

ax.xaxis.set_ticks_position('bottom')
ax.yaxis.set_ticks_position('left')
ax.spines['bottom'].set_position(('data', 0))
ax.spines['left'].set_position(('data', 0))

ax.set_aspect(0.65 / ax.get_data_ratio(), adjustable='box')

ax.ticklabel_format(style='sci', scilimits=(-1,2), axis='y')
ax.xaxis.get_offset_text().set(size=20)
ax.yaxis.get_offset_text().set(size=20)

for tick in ax.xaxis.get_major_ticks():
    tick.label.set_fontsize(20)
for tick in ax.yaxis.get_major_ticks():
    tick.label.set_fontsize(20)

grid_margin = MultipleLocator(BIT // 8)
ax.xaxis.set_major_locator(grid_margin)

plt.grid()
plt.legend(fontsize=15)
plt.tight_layout()

fig.savefig("test.png", bbox_inches='tight', pad_inches=0.05)
plt.close(fig)

broken axis

现把 Y 轴断成两段,把蓝线第一点单独划在一格里画,使得另两条线能拉宽一点。用 matplotlib 画断轴的例子见 [7,8],不过用 brokenaxes[9] 画更方便。效果如下:
broken
代码如下:

  • plt.figure() 中用 figsize 参数控制比例,对比上面的 ax.set_aspect(0.65 / ax.get_data_ratio(), adjustable='box')。因为图的绝对大小同上面代码画出来的不一样,字号要相应地调整,保证比例跟上面代码画出来的一致(或者调图的大小应该也行,但用个 10 比较容易推出另一个数是 6.5,跟前面 1:0.65 对应)。
  • xlimylim 在创建 bax 变量时指定,都可以是列表,控制断轴的位置,如这里 ylim 有两段,就分别对应图中两段 Y 轴的范围。
  • despine=False 保留右边、上面的边,见 [15]。
  • plot 的时候能用 clip_on=False,否则会崩,见 [10]。
  • 用科学计数法需要手动蔽掉下半图的指数(offer text),见 [11],即代码中的 bax.axs[1].get_yaxis().get_offset_text().set_visible(False) 一句。注意:如果要用 bax.grid() 画格仔,一定要在这句话之调用,否则下半图的指数的屏蔽会失效!
  • x 轴和 y 轴的 label 可能会跟 tick 重合,需要在 bax.set_*label 时用 labelpad 参数手动调整位置,见 [12,13,14]。set_label_coords 的调节效。
import matplotlib
font_config = {
    "font.family": 'serif', # 衬线字体
    "font.size": 12, # 相当于小四大小
    "font.serif": ['SimSun'], # 宋体
    "mathtext.fontset": 'stix',
    'axes.unicode_minus': False # 处理负号,即-号
}
matplotlib.rcParams.update(font_config)
matplotlib.rcParams['mathtext.default'] = 'regular'
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
from matplotlib.pyplot import MultipleLocator
from brokenaxes import brokenaxes
import numpy as np


MARKER = "os^"
LABEL = ["Tom", "Jerry", "Spike"]
BIT = 32
X = np.arange(BIT + 1)
Y = [
    np.array([80000] + [0] * BIT, dtype=np.float32),
    np.array([
          8.7756,   42.7066,   95.9444,  176.1248,  317.6762,  519.3036,
        750.4818,  994.5044, 1271.069 , 1556.7432, 1815.9172, 2069.5258,
       2304.2496, 2502.4506, 2672.5138, 2824.4458, 2974.3928, 3131.4864,
       3312.285 , 3546.2226, 3854.402 , 4210.833 , 4613.9742, 5041.3194,
       5348.7834, 5372.729 , 5148.2434, 4680.7672, 3703.1876, 2588.9898,
       1604.4842,  715.7446,  229.723
    ]),
    np.array([
        3.2206080e+02, 1.2220866e+03, 2.4709372e+03, 3.7537480e+03,
        4.8803058e+03, 5.7186174e+03, 6.1935612e+03, 6.3408946e+03,
        6.2374614e+03, 5.9762898e+03, 5.6437108e+03, 5.2912106e+03,
        4.9258044e+03, 4.5464280e+03, 4.1430268e+03, 3.6587404e+03,
        3.0297596e+03, 2.2955004e+03, 1.5692284e+03, 9.2766680e+02,
        4.5780320e+02, 2.0981440e+02, 1.0106980e+02, 5.0046200e+01,
        2.3046000e+01, 8.0620000e+00, 2.3904000e+00, 6.9840000e-01,
        2.9800000e-02, 8.0000000e-04, 0.0000000e+00, 0.0000000e+00,
        0.0000000e+00
    ]),
]


fig = plt.figure(figsize=(10, 6.5))
bax = brokenaxes(xlims=[(0, BIT)], ylims=[(0, 10000), (75000, 81000)], despine=False)

for y, _label, _marker in zip(Y, LABEL, MARKER):
    bax.plot(X, y, label=_label, marker=_marker)#, clip_on=False)  # 不能用 clip_on=False,会崩

bax.set_xlabel("距离", fontsize=30, labelpad=35)
bax.set_ylabel("实例数", fontsize=30, labelpad=60)

bax.set_title(r"$\times\_\times$", fontsize=30)
bax.grid()  # grid 一定要在 offset text 的 set_visible(False) 之前调用!

bax.ticklabel_format(style='sci', scilimits=(-1,2), axis='y')
bax.axs[0].get_yaxis().get_offset_text().set(size=30)
bax.axs[1].get_yaxis().get_offset_text().set_visible(False)  # 蔽掉下半图的指数(offset text)

for tick in bax.axs[1].xaxis.get_major_ticks():
    tick.label.set_fontsize(30)
for i in range(2):
    for tick in bax.axs[i].get_yaxis().get_major_ticks():
        tick.label.set_fontsize(30)

grid_margin = MultipleLocator(BIT // 8)
bax.axs[1].xaxis.set_major_locator(grid_margin)

# bax.grid()  # 如果在 offst text 的 set_visible(False) 之后,下半图的指数又会出现!
bax.legend(fontsize=20)

fig.savefig("test.png", bbox_inches='tight', pad_inches=0.05)
plt.close(fig)

References

  1. matplotlib设置宋体和Times New Roman体
  2. matplotlib刻度值使用科学记数法
  3. matplotlib左上角数量级的字体大小
  4. matplotlib plot显示marker超出边界部分
  5. matplotlib的text.usetex会影响字体
  6. matplotlib legend写tex公式且控制字体
  7. matplotlib的断轴实现原理和代码详解
  8. Matplotlib - Broken axis example: uneven subplot size
  9. bendichter/brokenaxes
  10. bug on clip_on=False #81
  11. duplicated offset text on using scientific notation #82
  12. Ylabel colides with tick texts. #35
  13. BrokenAxes.set_xlabel
  14. matplotlib.axes.Axes.set_xlabel
  15. Keeping the “top” and “right” spines #50
  • 6
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
Matplotlib断轴是指在绘制图表时,当某些变量的数量级远大于其他变量时,为了将它们放在同一张图中,需要对坐标进行截断。Matplotlib没有直接提供这个功能,但可以通过一些技巧实现。其中一种方法是使用mpl_toolkits.axes_grid1库中的make_axes_locatable函数来创建一个新的坐标,并将其放置在原始坐标的位置上,然后在新的坐标上绘制数据。这样就可以实现断轴的效果。 举个例子,如果我们要绘制一组数据,其中有一个变量的数量级远大于其他变量,我们可以使用Matplotlib断轴功能来将其放在同一张图中。具体步骤如下: ```python import matplotlib.pyplot as plt from mpl_toolkits.axes_grid1 import make_axes_locatable # 生成数据 x = [1, 2, 3, 4, 5] y1 = [10, 20, 30, 40, 50] y2 = [1000, 2000, 3000, 4000, 5000] # 创建画布和子图 fig, ax = plt.subplots() # 绘制数据 ax.plot(x, y1) ax.set_ylabel('y1') # 创建新的坐标 divider = make_axes_locatable(ax) ax2 = divider.append_axes("bottom", size="30%", pad=0.1) # 绘制数据 ax2.plot(x, y2) ax2.set_ylabel('y2') # 设置坐标范围 ax.set_ylim(0, 60) ax2.set_ylim(900, 5500) # 显示图形 plt.show() ``` 在这个例子中,我们使用make_axes_locatable函数创建了一个新的坐标,并将其放置在原始坐标的下方。然后在新的坐标上绘制了另一组数据。最后,我们通过设置坐标的范围来实现断轴的效果。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值