matplotlib 画折线图时,有一些值异常大的离群点,会把其它曲线压得很密。想把 Y 轴断成两截,将离群点隔离。
normal plotting
原本画的图如下:
- 普通 plot,没用断轴
- 中文设置见 [1]
- 科学表示法见 [2,3]
- 允许 marker 超出边框见 [4]
- 公式 latex 支持见 [5,6]
可以看到,蓝色线第一个值远大于其它数据,使得另两条线的走势不明显。源码如下:
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] 画更方便。效果如下:
代码如下:
- 在
plt.figure()
中用figsize
参数控制比例,对比上面的ax.set_aspect(0.65 / ax.get_data_ratio(), adjustable='box')
。因为图的绝对大小同上面代码画出来的不一样,字号要相应地调整,保证比例跟上面代码画出来的一致(或者调图的大小应该也行,但用个 10 比较容易推出另一个数是 6.5,跟前面 1:0.65 对应)。 xlim
和ylim
在创建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
- matplotlib设置宋体和Times New Roman体
- matplotlib刻度值使用科学记数法
- matplotlib左上角数量级的字体大小
- matplotlib plot显示marker超出边界部分
- matplotlib的text.usetex会影响字体
- matplotlib legend写tex公式且控制字体
- matplotlib的断轴实现原理和代码详解
- Matplotlib - Broken axis example: uneven subplot size
- bendichter/brokenaxes
- bug on clip_on=False #81
- duplicated offset text on using scientific notation #82
- Ylabel colides with tick texts. #35
- BrokenAxes.set_xlabel
- matplotlib.axes.Axes.set_xlabel
- Keeping the “top” and “right” spines #50