Python画截断图

近日,帮女朋友画截断图时,遇到了一些问题,网上很多资料都是互相粘贴,缺少能够解决问题的帖子,经过查看官方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')

即可以得到可用的图:


brokenaxes_simple.png

看着效果好像还不错,但是如果要画柱状图,则如何解决呢?
仿照官方的程序,我们可以写出来以下的程序

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()

得到以下图:


brokenaxes_bar.png

这个图我们可以发现存在一定的问题,它的横坐标不对了,我们的“1”显示不出来了!!!
那怎么解决呢?可以知道我们在bar()函数中使用tick_label=name_list是不可行的了,那我们试下plt.xticks函数看看
首先删除bar函数中的tick_label=name_list,然后在plt.show()前面加上plt.xticksx,name_list),得到下图结果

brokenaxes_bar_2.png

好吧,结果更差,不只存在自带的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()

brokenaxes_bar_3.png

不显示自带的刻度了,但是对应的刻度小短线还存在!怎么办呢?
我们可以考虑手动调整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_1.png

只是,由于我们使用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()

画出来的结果好看多了!

subplot_2.png

不对,还有问题!我们的ylabel不在图片的正中间呀!
在网上查了好久,都说的set_position,但是这个参数很简陋,只有几个位置参数,没法进行详细的定义。最后发现官方的api提供的改变位置的函数set_label_coords
那我们就使用起来吧!
plt.ylabel('y')改为

ax2.set_ylabel("y")
ax2.yaxis.set_label_coords(-0.1,1)

重新画一下图


subplot_3.png

问题解决了!
我们得到了一个完整的图!

参考文献:

  1. 不想开手动挡了
  2. Python 作图实现坐标轴截断
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351