文章目录
前言
  在上一章中,我们主要介绍了通过fig.add_subplot()和fig.add_ases()的方法来增加子图,第二种方法可以较为灵活的实现任意格式的画面布局,而本文将介绍其他的布局方法,使得在实际绘图过程中可以更快得到自己想要的布局。
一、绘图布局
1.1 子图集(plt.subplots())
  之前我们通过在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是否是一样的,默认值都是False
- squeeze: 表示返回的子图对象的数组格式是否被压缩,即- 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)

1.2 马赛克子图(plt.subplot_mosaic())
  这个布局功能也是我在解决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块,第一行第一、二列合并,第一列第二、三行合并,第三列第一、二行合并,第三行第二列不显示,可以用如下两种办法来实现(我们顺便设置了内部子图的刻度与子图间距):
- 用list做mosaic参数创建图像
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)
- 用str换行做mosaic参数创建图像
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)

1.3 格子分割(mpl.gridspec.GridSpec())
  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:])

   结合以上图像,我们就可以很快理解各个参数所对应的意思了。
1.4 合理分割与绘图
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)

二、基本图形与常用统计图形
2.1 绘图基础
在上一章我们介绍过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格式输入数学符号)
- 设置x和y轴的标签: plt.xlabel()或者ax.set_xlabel()
- 设置x和y轴的范围: plt.xlim()或者ax.set_xlim()
- 设置x和y轴刻度值和刻度的标签:
  在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则会默认返回一个可以交互的图像,并可以保持更新。
- 设置子图的边框不可见:
 可以将对应边框的spines的值设置为不可见,共四种:
 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)
 可以适当用LC表达式来简化语言,或用迭代器的方式简化语言,例如:
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’:’’})
 只要设置x的值和y的值,设置文本string,设置字体大小fontsize,va代表verticalalignment,即垂直对齐,ha代表horizontalalignment,即水平对齐,下文将会给出例子
2.2 线图(plt.plot())
  一般的线图可以用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之间,越低越透明

2.3 条形图(plt.bar() & plt.barh())
  条形图用plt.bar()或者plt.barh()来实现,前者做垂直的条形图,后者是水平的条形图,常用语法如下:
 plt.bar(x, height, width=0.8, bottom=None, color=,edge=, align='center')
- x:x轴的值
- height:条形高
- width:条形宽
- bottom:堆叠条形图时底下的条形(在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)

 
2.4 直方图(plt.hist())
  如果我们要查看一个数据集的分布,可以用直方图来实现,用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:
 有两种填写方法,填写数字,则mpl会帮你把数据划分成若干个箱子,也可以填写每个箱子的范围,注意划分的时候时前取后不去,例如写[1,2,3,4],则分成四个箱子,分别是[1,2), [2,3),[3,4],’[‘代表能取到,’('代表不能取到。
- range:上下界,超出范围的数据不取
- density:取True返回密度曲线
- weights:权重,传入与x形状相同的权重数组。默认为1
- cumulative:是否计算累计频率
- 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))

 
2.5 散点图(plt.scatter())
  散点图可以用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)

2.6 箱线图(plt.boxplot())
  箱线图又称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()
- 第二章第一节介绍了怎么对子图的各个实例进行设置
- 第二章后几节介绍了常用图形的绘制
- 接下来将介绍怎么用plt来实现动画和交互









