近日,帮女朋友画截断图时,遇到了一些问题,网上很多资料都是互相粘贴,缺少能够解决问题的帖子,经过查看官方api最终解决了问题。
在此记录一下,也希望能够帮助其他有需要的人。
brokenaxes
这个是最方便的一种解决办法,官网地址为Pypi,官网的教程比较简洁
import matplotlib.pyplot as plt
from brokenaxes import brokenaxes
import numpy as np
fig = plt.figure(figsize=(5,2))
bax = brokenaxes(xlims=((0, .1), (.4, .7)), ylims=((-1, .7), (.79, 1)), hspace=.05)
x = np.linspace(0, 1, 100)
bax.plot(x, np.sin(10 * x), label='sin')
bax.plot(x, np.cos(10 * x), label='cos')
bax.legend(loc=3)
bax.set_xlabel('time')
bax.set_ylabel('value')
即可以得到可用的图:
看着效果好像还不错,但是如果要画柱状图,则如何解决呢?
仿照官方的程序,我们可以写出来以下的程序
import matplotlib.pyplot as plt
import numpy as np
from brokenaxes import brokenaxes
name_list = [ '1', '6', '30', '60']
num_list_scene1 = [1050, 1055, 1060, 1065]
num_list_scene2 = [5, 15, 25, 35]
num_list_scene3 = [10, 10, 10, 10]
num_list_scene4 = [5, 30, 60, 80]
x = list(range(len(num_list_scene1)))
total_width, n = 0.9, 6
width = total_width / n
bax = brokenaxes(ylims=((0, 110), (1050, 1120)), hspace=.03, despine=False)
bax.bar(x, num_list_scene1, width=width, label='scene1', fc='c')
for j in range(len(x)):
x[j] = x[j] + width
bax.bar(x, num_list_scene2, width=width, label='scene2', fc='#A52A2A')
for j in range(len(x)):
x[j] = x[j] + width
bax.bar(x, num_list_scene3, width=width, label='scene3', fc='b', )
for j in range(len(x)):
x[j] = x[j] + width
bax.bar(x, num_list_scene4, width=width, label='scene4',tick_label=name_list, fc='#D2691E')
bax.set_xlabel('x')
bax.set_ylabel('y')
bax.legend(loc = 1)
plt.show()
得到以下图:
这个图我们可以发现存在一定的问题,它的横坐标不对了,我们的“1”显示不出来了!!!
那怎么解决呢?可以知道我们在bar()函数中使用tick_label=name_list
是不可行的了,那我们试下plt.xticks
函数看看
首先删除bar函数中的tick_label=name_list
,然后在plt.show()
前面加上plt.xticksx,name_list)
,得到下图结果
好吧,结果更差,不只存在自带的0.0,1.0的刻度,我们想要的刻度还不在正确的位置!
我们通过对比这两个图,可以发现一个很trick的解决办法!
第一个图,没有自带的刻度,但是少了一个我们想要的刻度;第二个图,存在自带的刻度,但是我们想要的刻度全都有!
我们能不能考虑结合下呢?
我们尝试在bar中加上一个空的刻度,然后再显示出来
import matplotlib.pyplot as plt
import numpy as np
from brokenaxes import brokenaxes
name_list = [ '1', '6', '30', '60']
fake_name_list = [ '', '', '', '']
num_list_scene1 = [1050, 1055, 1060, 1065]
num_list_scene2 = [5, 15, 25, 35]
num_list_scene3 = [10, 10, 10, 10]
num_list_scene4 = [5, 30, 60, 80]
x = list(range(len(num_list_scene1)))
total_width, n = 0.9, 6
width = total_width / n
bax = brokenaxes(ylims=((0, 110), (1050, 1120)), hspace=.03, despine=False)
bax.bar(x, num_list_scene1, width=width, label='scene1', fc='c')
for j in range(len(x)):
x[j] = x[j] + width
bax.bar(x, num_list_scene2, width=width, label='scene2', fc='#A52A2A')
for j in range(len(x)):
x[j] = x[j] + width
bax.bar(x, num_list_scene3, width=width, label='scene3', fc='b', )
for j in range(len(x)):
x[j] = x[j] + width
bax.bar(x, num_list_scene4, width=width, label='scene4', tick_label=fake_name_list, fc='#D2691E')
bax.set_xlabel('x')
bax.set_ylabel('y')
bax.legend(loc = 1)
plt.xticks(x,name_list)
plt.show()
不显示自带的刻度了,但是对应的刻度小短线还存在!怎么办呢?
我们可以考虑手动调整
plt.xticks(x,name_list)
中x
的值,使得这两个短线重叠,问题就可以解决了!只是我们这只有四个短线,如果我们要画很多个值,找让短线重叠的
x
值,就会做很多枯燥而又没意义的工作!
因此,这个方案在一些简单的图上还可以应用,复杂的场景就难以解决了!
subplot
matplotlib具有很强的自定义能力,我们可以考虑通过画两个子图,然后拼接成一个,来生成我们的截断图!
import matplotlib.pyplot as plt
name_list = [ '1', '6', '30', '60']
num_list_scene1 = [1050, 1055, 1060, 1065]
num_list_scene2 = [5, 15, 25, 35]
num_list_scene3 = [10, 10, 10, 10]
num_list_scene4 = [5, 30, 60, 80]
f, (ax, ax2) = plt.subplots(2, 1, sharex=True)
x = list(range(len(num_list_scene1)))
total_width, n = 0.9, 6
width = total_width / n
ax.bar(x, num_list_scene1, width=width, label='scene1', fc='c')
for j in range(len(x)):
x[j] = x[j] + width
ax.bar(x, num_list_scene2, width=width, label='scene2', fc='#A52A2A')
for j in range(len(x)):
x[j] = x[j] + width
ax.bar(x, num_list_scene3, width=width, label='scene3', fc='b', align = 'center')
for j in range(len(x)):
x[j] = x[j] + width
ax.bar(x, num_list_scene4, width=width, label='scene4', fc='#D2691E')
x = list(range(len(num_list_scene1)))
ax2.bar(x, num_list_scene1, width=width, label='scene1', fc='c')
for j in range(len(x)):
x[j] = x[j] + width
ax2.bar(x, num_list_scene2, width=width, label='scene2', fc='#A52A2A')
for j in range(len(x)):
x[j] = x[j] + width
ax2.bar(x, num_list_scene3, width=width, label='scene3', fc='b')
for j in range(len(x)):
x[j] = x[j] + width
ax2.bar(x, num_list_scene4, width=width, label='scene4', tick_label=name_list, fc='#D2691E')
ax.set_ylim(1050, 1120) # outliers only
ax2.set_ylim(0, 110) # most of the data
ax.spines['bottom'].set_visible(False)
ax2.spines['top'].set_visible(False)
ax.xaxis.tick_top()
ax.tick_params(labeltop='off') # don't put tick labels at the top
ax2.xaxis.tick_bottom()
d = .015
kwargs = dict(transform=ax.transAxes, color='k', clip_on=False)
ax.plot((-d, +d), (-d, +d), **kwargs) # top-left diagonal
ax.plot((1 - d, 1 + d), (-d, +d), **kwargs) # top-right diagonal
kwargs.update(transform=ax2.transAxes) # switch to the bottom axes
ax2.plot((-d, +d), (1 - d, 1 + d), **kwargs) # bottom-left diagonal
ax2.plot((1 - d, 1 + d), (1 - d, 1 + d), **kwargs) # bottom-right diagonal
plt.subplots_adjust(hspace=0.1)
plt.xlabel('x')
plt.ylabel('y')
plt.show()
看着结果似乎还不错,坐标的刻度也显示正常啦
只是,由于我们使用subplot画出来的两个图默认大小是一样的,而前文使用brokenaxes画出来的截断上下的长度并不一样。我们这样画出来的图不好看,还需要调整下!
考虑到matplotlib可以使用grid来画图,我们尝试用它来调整下上下两图的比例!
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
name_list = [ '1', '6', '30', '60']
num_list_scene1 = [1050, 1055, 1060, 1065]
num_list_scene2 = [5, 15, 25, 35]
num_list_scene3 = [10, 10, 10, 10]
num_list_scene4 = [5, 30, 60, 80]
a1,a2=2,3
gs = gridspec.GridSpec(2, 1,height_ratios=[a1,a2],hspace=0.1)
ax = plt.subplot(gs[0,0:])
ax2 = plt.subplot(gs[1,0:], sharex=ax)
x = list(range(len(num_list_scene1)))
total_width, n = 0.9, 6
width = total_width / n
ax.bar(x, num_list_scene1, width=width, label='scene1', fc='c')
for j in range(len(x)):
x[j] = x[j] + width
ax.bar(x, num_list_scene2, width=width, label='scene2', fc='#A52A2A')
for j in range(len(x)):
x[j] = x[j] + width
ax.bar(x, num_list_scene3, width=width, label='scene3', fc='b', align = 'center')
for j in range(len(x)):
x[j] = x[j] + width
ax.bar(x, num_list_scene4, width=width, label='scene4', fc='#D2691E')
x = list(range(len(num_list_scene1)))
ax2.bar(x, num_list_scene1, width=width, label='scene1', fc='c')
for j in range(len(x)):
x[j] = x[j] + width
ax2.bar(x, num_list_scene2, width=width, label='scene2', fc='#A52A2A')
for j in range(len(x)):
x[j] = x[j] + width
ax2.bar(x, num_list_scene3, width=width, label='scene3', fc='b')
for j in range(len(x)):
x[j] = x[j] + width
ax2.bar(x, num_list_scene4, width=width, label='scene4', tick_label=name_list, fc='#D2691E')
ax.set_ylim(1050, 1120) # outliers only
ax2.set_ylim(0, 110) # most of the data
ax.spines['bottom'].set_visible(False)
ax2.spines['top'].set_visible(False)
ax.xaxis.tick_top()
ax.tick_params(labeltop='off') # don't put tick labels at the top
ax2.xaxis.tick_bottom()
d = .015
oa1,oa2=(a1+a2)/a1,(a1+a2)/a2
kwargs = dict(transform=ax.transAxes, color='k', clip_on=False)
ax.plot((-d, +d), (-d*oa1, +d*oa1), **kwargs) # top-left diagonal
ax.plot((1 - d, 1 + d), (-d*oa1, +d*oa1), **kwargs) # top-right diagonal
kwargs.update(transform=ax2.transAxes) # switch to the bottom axes
ax2.plot((-d, +d), (1 - d*oa2, 1 + d*oa2), **kwargs) # bottom-left diagonal
ax2.plot((1 - d, 1 + d), (1 - d*oa2, 1 + d*oa2), **kwargs) # bottom-right diagonal
plt.xlabel('x')
plt.ylabel('y')
plt.show()
画出来的结果好看多了!
不对,还有问题!我们的ylabel不在图片的正中间呀!
在网上查了好久,都说的
set_position
,但是这个参数很简陋,只有几个位置参数,没法进行详细的定义。最后发现官方的api提供的改变位置的函数set_label_coords
那我们就使用起来吧!
将
plt.ylabel('y')
改为
ax2.set_ylabel("y")
ax2.yaxis.set_label_coords(-0.1,1)
重新画一下图
问题解决了!
我们得到了一个完整的图!
参考文献: