在上一章中,我们主要介绍了通过fig.add_subplot()
和fig.add_ases()
的方法来增加子图,第二种方法可以较为灵活的实现任意格式的画面布局,而本文将介绍其他的布局方法,使得在实际绘图过程中可以更快得到自己想要的布局。
之前我们通过在Figure上利用add_subplot的方式来添加子图,实际上我们可以用pyplot中的subplots功能来快速创建画板Figure多个子图集,语法如下:
plt.subplots(nrows=1, ncols=1, *, sharex=False, sharey=False, squeeze=True, subplot_kw, gridspec_kw)
这里主要的参数有这么几个:
nrows
: 行数ncols
: 列数sharex
和sharey
: 表示这些子图的xticks和yticks是否是一样的,默认值都是Falsesqueeze
: 表示返回的子图对象的数组格式是否被压缩,即squeeze=True
,对于行数为1或者列数为1,则用一维数组格式(1DArray)返回子图对象,squeeze=False
,哪怕行数为1或者列数为1,仍用二维数组格式(2DArray)返回子图对象。下文会利用实例解释如何用返回的子图对象的数组格式来对子图进行操作。subplot_kw
: 用字典格式将参数传递给add_subplot()
,比如可以传颜色等参数,详见add_subplot()
。gridspec_kw
: 用字典格式将参数传递给mpl.gridspec.GridSpec()
,用于创建子图所摆放的网格。这里的gridspec时matplotlib中另一个重要的画板布局的class,也会在下文介绍。 我们常用这两种方式来利用plt.subplots()
创建画板和子图集:
#方法一:
fig,((ax1,ax2,ax3),(ax4,ax5,ax6),(ax7,ax8,ax9))=plt.subplots(3,3,sharex=True,sharey=True)
#方法二:
fig,axes=plt.subplots(3,3,sharex=True,sharey=True)
显然,这两种方法都能得到画板fig
和子图集,区别在于:我们可以用3*3的tuple来将这些子图集一一命名,如法一,然后直接利用这些名字来操作对应子图;也可以像法二一样,用axes来表示整个子图集,然后可以根据用数组格式的调用,来对操作子图集中的子图。
比如我们要对最中间的那张子图来画一条直线,两种方法的绘图语句如下:
import numpy as np
pos=np.arange(0,5,1)
方法一:
ax5.plot(pos)
方法二:
axes[1,1].plot(pos)
对于方法一,很好理解,对于方法二,这个调用手法本质上就是把axes看成了一个二维矩阵,则axes[1,1]
代表从上往下,从左往右数第二行第二个(坐标从0开始)。这里回头看squeeze
参数,默认值为True,表示如果你做2*1的子图,他将返回一维矩阵,你就不能用axes[0,0]
来取第一个子图,只能用axes[0]
来取。
plt.subplots_adjust()
可以用来调整子图大小,也可以用其中的子图之间的行间距和列间距,如下:
plt.subplots_adjust(wspace = 0.0,hspace = 0.0)
for ax in plt.gcf().get_axes():
ax.tick_params(bottom=False,left=False)
这样可以将子图间的行间距和列间距都调整为0。
如果觉得内部的刻度轴太多余,可以用tick_params()来去除,语法见下文。
如上图所示,我们发现如果共享了x轴刻度和y轴刻度,matplotlib会自动隐藏子图的内部刻度,这个问题困扰了我一段时间,在老版本的matplotlib中,只要将各个子图的左和下两个xticklabel设置为可见就可以了,如下:
for ax in plt.gcf().get_axes():
for label in ax.get_xticklabels() + ax.get_yticklabels():
label.set_visible(True)
plt.gcf().canvas.draw() #有的编辑器需要再运行这个语句来实现绘画
但在新的matplotlib中(我用的是3.6.0版本)就不可以了,最终我通过设置对应的axes的tick_params()解决了这个问题,里面的参数left、right、top、bottom
表示刻度线,而labelleft、labeltop、labelright、labelbottom
则表示刻度标签,不像显示,设置为False即可示例代码如下:
for ax in plt.gcf().get_axes():
ax.tick_params(labelbottom=True, labelleft=True)
这个布局功能也是我在解决subplots() x、y轴共刻度时内部图刻度不可见时找到的新的布局方法,语法如下:
plt.subplot_mosaic(mosaic, sharex=False, sharey=False, subplot_kw, gridspec_kw, empty_sentinel='.', **fig_kw)
大部分语法与subplots一样,除了以下两个:
mosaic
: 可以传入list或者str来进行视觉布局,见下文例子。empty_sentinel
:用来表示此位置上子图为空,默认用’.'表示此位置上子图为空。比如我们想将子图分为3*3 一共9块,第一行第一、二列合并,第一列第二、三行合并,第三列第一、二行合并,第三行第二列不显示,可以用如下两种办法来实现(我们顺便设置了内部子图的刻度与子图间距):
import matplotlib.pyplot as plt
fig,axes=plt.subplot_mosaic([['A','A','B'],
['C','D','B'],
['C','.','E']],sharex=True,sharey=True)
for ax in plt.gcf().get_axes():
ax.tick_params(labelbottom=True, labelleft=True)
plt.subplots_adjust(wspace = 0.25,hspace = 0.25)
import matplotlib.pyplot as plt
fig,axes=plt.subplot_mosaic('''AAB
CDB
C.E''',sharex=True,sharey=True)
for ax in plt.gcf().get_axes():
ax.tick_params(labelbottom=True, labelleft=True)
plt.subplots_adjust(wspace = 0.25,hspace = 0.25)
得到结果:
我们想对其中某些子图操作,只要对对应名字的子图操作就行了:
axes['A'].plot(pos)
axes['E'].scatter(pos,pos)
matplotlib.gridspec.GridSpec()
可以对当前Figure进行格子分割,然后可以用类似数组切片的方式来产生想要的子图,主要语法如下
matplotlib.gridspec.GridSpec(nrows, ncols, figure, left, bottom, right, top, wspace, hspace, width_ratios, height_ratios)
主要参数如下:
nrows
和ncols
: 分别表示行数和列数left, bottom, right, top
: 框定在子图中的范围。例如left=0.2
表示距离左边界空出20%,left 不能大于 right,bottom 不能大于 top。如果没有给出,这些值将用Figure 或 rcParams 中的默认参数表示。wspace
和hspace
: 分别代表行间距和列间距width_ratios
和height_ratios
: 分别表示各行间距的比例和各列间距的比例,参数应为类矩阵对象(array-like object)或者行、列数。注意:如果是类矩阵对象,维度应该与行、列数一致。给出以下例子:
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
plt.figure()
gspec = gridspec.GridSpec(3, 3,left=0.2,width_ratios=[1,4,9],height_ratios=[1,4,9])
top_subplots = plt.subplot(gspec[0, 1:])
side_subplots_1 = plt.subplot(gspec[0, 0])
side_subplots_2 = plt.subplot(gspec[1,0])
side_subplots_3 = plt.subplot(gspec[2,0])
bottom_subplots = plt.subplot(gspec[1:,1:])
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import numpy as np
plt.figure()
gspec = gridspec.GridSpec(3, 3)
#将figure分割成三块
top_histogram = plt.subplot(gspec[0, 1:])
side_histogram = plt.subplot(gspec[1:, 0])
lower_right = plt.subplot(gspec[1:, 1:])
#产生10000条正态分布的数据
Y = np.random.normal(loc=0.0, scale=1.0, size=10000)
#产生10000条随机分布的数据
X = np.random.random(size=10000)
lower_right.scatter(X, Y,s=1) #绘制散点图
top_histogram.hist(X, bins=100) #绘制X直方图
side_histogram.hist(Y, bins=100, orientation='horizontal') #绘制Y直方图
#将上方的子图清空并绘制X数据的直方图
top_histogram.clear()
top_histogram.hist(X, bins=100, density=True)
#将侧方的子图清空并绘制Y数据的直方图
side_histogram.clear()
side_histogram.hist(Y, bins=100, orientation='horizontal', density=True)
#反转侧方直方图的x轴
side_histogram.invert_xaxis()
#改变各个图的x和y坐标范围
for ax in [top_histogram, lower_right]:
ax.set_xlim(0, 1)
for ax in [side_histogram, lower_right]:
ax.set_ylim(-5, 5)
在上一章我们介绍过matplotlib的Artist层中的几个主要的实例及控制方式,可见matplotlib函数式绘图与面向对象绘图基础的1.2和1.3章,这里对两种情况再次总结一下(x和y轴的区别只要改变x和y就可以了,此处仅以x举例,y同理可得):
plt.figure(figsize,dpi,facecolor)
其中figsize表示画板的大小,dpi是每英寸的像素数,facecolor是填充色plt.title()
或者ax.set_title()
(标题可以用Latex格式输入数学符号)plt.xlabel()
或者ax.set_xlabel()
plt.xlim()
或者ax.set_xlim()
在pyplot中可以用plt.xticks(pos,ticklabels)
:其中第一个pos是设置刻度值,可以给定list,第二个参数ticklabels可选填,表示将前面的pos进行重命名,例如原本刻度是[0,1,2],想改成1990,1991,1992就可以用plt.xticks([0,1,2],[1990,1991,1992])
;
在OO(object-oriented)绘图中可以用 ax.set_xticks()
来设置刻度值,然后用ax.set_xticklabels()
来设置刻度的标签
示范例句:
pos=np.arange(0,5,1)
Language=['Python','SQL','Java','C++','JavaScript']
#方法一:
plt.xticks(pos,Language)
#方法二:
ax=fig.add_subplot(111)
ax.set_xticks(pos)
ax.set_xticklabels(Language)
plt.legend(loc)
里面的参数loc代表放置的位置,可以用loc='best'
这样系统就会帮你选定一个最好的位置放置。plt.savefig()
plt.show()
,如果用%matplotlib notebook
则会默认返回一个可以交互的图像,并可以保持更新。plt.gca().spines['top'].set_visible(False)
plt.gca().spines['right'].set_visible(False)
plt.gca().spines['left'].set_visible(False)
plt.gca().spines['bottom'].set_visible(False)
for spine in plt.gca().spines.values():
spine.set_visible(False)
#LC表达式
[plt.gca().spines[loc].set_visible(False) for loc in ['top','right','bottom','left']]
plt.text(x,y,string,fontsize=,va="",ha="",bbox={‘fc’:’’, ‘ec’:’’})
一般的线图可以用plt.plot()
来实现,主要的语法是
plt.plot(x=,y=,ls=,lw=,c=,marker=,s=,markeredgecolor=,markerfacecolor, label=)
主要参数:
x
, y
分别是x轴上和y轴上的数据ls
: linestyle 折线的样式lw
: linewidth 折线的宽度c
: color 折线的颜色maker
: 折线上点的样式makeredgecolor
: 折线上点边界的颜色makerfacecolor
: 折线上点中间填充的颜色label
: 折线的标签,可以在legend中展示 补充1: 事实上,ls和maker可以连在一起用,比如直接给’–o’就是设置着先的样式为–,设置折线上点的样式为’o’
补充2: 两条线图之间还可以用fill_between()
函数来填充,语法如下:Axes.fill_between(x, y1, y2=0, where=None, interpolate=False, step=None, *, data=None, **kwargs)
(pyplot也有,用法相似)主要是确定x轴范围,两条线,还有x轴里哪些点需要(where参数),确定填充颜色。
import numpy as np
import matplotlib.pyplot as plt
linear_data = np.array([1,2,3,4,5,6,7,8])
exponential_data = linear_data**2
eng=['one','two','three','four','five','six','seven','eight']
plt.figure()
plt.plot(linear_data, '--o',exponential_data,'-o')
plt.xlabel('x-label')
plt.ylabel('y-label')
plt.title('linear_data(x) & exponential_data($x^2$)')
plt.xticks(range(8),eng)
plt.gca().fill_between(range(len(linear_data)),
linear_data, exponential_data,
facecolor='azure',
alpha=1)#alpha为透明底,在0到1之间,越低越透明
条形图用plt.bar()
或者plt.barh()
来实现,前者做垂直的条形图,后者是水平的条形图,常用语法如下:
plt.bar(x, height, width=0.8, bottom=None, color=,edge=, align='center')
x
:x轴的值height
:条形高width
:条形宽plt.barh()
中为left)color
:条形填充颜色edge
:条形边框颜色align
:x轴上的对齐方式补充1:如果需要画两个条形图,可以通过给每一个x增加条形图的width来解决。
linear_data = np.array([1,2,3,4,5,6,7,8])
exponential_data = linear_data**2
plt.figure()
xvals = range(len(linear_data))
plt.bar(xvals, linear_data, width = 0.3, color='royalblue',alpha=0.5)
plt.bar(xvals, exponential_data, width = 0.3, bottom=linear_data, color='tomato',alpha=0.5)
new_xvals = []#设置一个新的x坐标时+0.3
for item in xvals:
new_xvals.append(item+0.3)
plt.bar(new_xvals, exponential_data, width = 0.3 ,color='powderblue')
补充2:如果需要画辅助线,可以用ax.axhline()
画水平的辅助线,用ax.axvline()
画垂直的辅助线
np.random.seed(666)
x = np.arange(5)
y = np.random.randn(5)
z = np.random.randn(5)
fig, axes = plt.subplots(1,2,figsize=plt.figaspect(1/2))#使得figure的高是宽的0.5倍
vert_bars = axes[0].bar(x, y, color='lightblue', align='center')
vert_bars = axes[0].bar(x, z, bottom = y,color='tomato', align='center')
horiz_bars = axes[1].barh(x,y, color='lightblue', align='center')
#在水平或者垂直方向上画辅助线
axes[0].axhline(0, color='gray', linewidth=2)
axes[1].axvline(0, color='gray', linewidth=2)
如果我们要查看一个数据集的分布,可以用直方图来实现,用plt.hist()
,其语法如下:plt.hist(x, bins=None, range=None, density=False, weights=None, cumulative=False, bottom=None, histtype='bar', align='mid', orientation='vertical', rwidth=None, log=False, color=None, label=None, stacked=False, *, data=None, **kwargs)
主要参数:
bins
:range
:上下界,超出范围的数据不取density
:取True返回密度曲线weights
:权重,传入与x形状相同的权重数组。默认为1cumulative
:是否计算累计频率bottom
:是否添加基准线align
:对齐形式color
:填充颜色label
:标签补充1:箱子个数如何确定?在Kun He和Glen Meeden的论文Selecting the Number of Bins in a Histogram: A Decision Theoretic Approach中,得到当数据条数为 n n n时,箱子数可取 ( 2 n ) 1 / 3 (2n)^{1/3} (2n)1/3。
import numpy as np
import matplotlib.pyplot as plt
fig, axs = plt.subplots(2, 2, sharex=True)
for n in range(0,4):
sample_size = 10**(n+1)
sample = np.random.normal(loc=0.0, scale=1.0, size=sample_size)
axs[int(n/2),n%2].hist(sample,bins=100)
axs[int(n/2),n%2].set_title('n={}'.format(sample_size))
散点图可以用plt.scatter()
来表示,只要给X轴的数据和Y轴的数据就可以了。
补充1:散点图可以用另一个数据Z来反应大小,设置参数s=Z
即可,例如:
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(123)
x=np.random.randn(100)
y=np.random.randn(100)
z=np.random.randint(1,100,100)
plt.figure()
plt.scatter(x,y,s=z,c=z)
箱线图又称whisker plot,会取四分位点、均值、最大最小值进行绘制,可以用plt.boxplot()
。
import pandas as pd
import matplotlib.pyplot as plt
normal_sample = np.random.normal(loc=0.0, scale=1.0, size=10000)
random_sample = np.random.random(size=10000)
gamma_sample = np.random.gamma(2, size=10000)
df = pd.DataFrame({'normal': normal_sample,
'random': random_sample,
'gamma': gamma_sample})
plt.figure()
plt.boxplot([ df['normal'], df['random'], df['gamma'] ])
补充1:常见的箱线图会带尾,即排除部分数据不取,而如果想要将所有数据都带入,可以设置whis=(0,100)
plt.figure()
plt.boxplot([ df['normal'], df['random'], df['gamma'] ],whis=(0,100))
plt.subplots()
、plt.subplot_mosaic()
和mpl.gridspec.GridSpec()