Matplotlib子图

子图

**有时候我们需要从多个角度进行数据的比较、分析,因此就需要用到子图。**子图的本质是在一个较大的图形中同时放置一组较小的坐标轴,布局形式可以多种多样,不拘泥于我们在第五集中举的那种网格图的形式。

一般化的子图

我们先进行一般化的子图布局。

首先要创建各个子图的坐标轴,传入一个四元列表参数:[x,y,width,height],用来表示这个子图坐标轴原点的x坐标、y坐标,以及宽和高。值得注意的是,这四个值的取值范围都是[0,1],我们约定整个大图的左下端为原点(0,0),右上端为(1,1)。那么x,y的取值就表示该子图坐标原点的横坐标值和纵坐标值占大图整个长宽的比例。而width和height则表示子图的宽和高占整个大图的宽和高的比例。如果不传入参数则表示选取默认坐标轴,即大图的坐标轴。

import numpy as np
import matplotlib.pyplot as plt

ax1 = plt.axes()  
ax2 = plt.axes([0.5, 0.6, 0.15, 0.25])
plt.show()

img

下一步,我们就要在子图中进行绘图了,每生成一个子图坐标系,plt就表示当前的子图,调用plt.plot就是在当前的子图上进行绘图。

import numpy as np
import matplotlib.pyplot as plt

x = np.linspace(0, 10)
plt.axes([0.1, 0.5, 0.8, 0.4], ylim=(-1.2, 1.2))
plt.grid(True)
plt.plot(np.sin(x))

plt.axes([0.1, 0.1, 0.8, 0.4], ylim=(-1.2, 1.2))
plt.grid(True)
plt.plot(np.cos(x))
plt.show()

img

规则子图

plt.subplot()

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

plt.figure(figsize=(16,10))

# 画第1个图:折线图
x=np.arange(1,100)
plt.subplot(2,2,1)
plt.plot(x,x*x)

# 画第2个图:散点图
plt.subplot(2,2,2)
plt.scatter(np.arange(0,10), np.random.rand(10))

# 画第3个图:饼图
plt.subplot(2,2,3)
plt.pie(x=[15,30,45,10],labels=list('ABCD'),autopct='%.0f',explode=[0,0.05,0,0])

# 画第4个图:条形图
plt.subplot(2,2,4)
plt.bar([20,10,30,25,15],[25,15,35,30,20],color='b')
plt.show()

subplot前面俩参数指定的是一个画板被分割成的行和列,后面一个参数则指的是当前**正在绘制的编号!**这个编号是从1开始,即1,2,3…对应了要画的第一,第二,第三…个图。

位置索引规则

那是个什么编号规则呢?就是 行优先数数规则

image-20210715095841506 image-20210715104629356

结果

image-20210715100037932
  • 补充
    以下三种写法是等效的
# 写法一
plt.subplot(231).plot(x,y)
# 写法二
plt.subplot(231)
plt.plot(x,y)
# 写法三
p = plt.subplot(231)
p.plot(x,y)

plt.subplots_adjust(hspace=纵向间隙, wspace=横向间隙)

subplot方法无法绘制比例自定义的子图,而是只能创建彼此对齐的行列网格子图,如果仅仅是这种需求的话,倒是使用起来非常简便:

import numpy as np
import matplotlib.pyplot as plt

plt.subplots_adjust(hspace=0.3, wspace=0.3)
for i in range(1,7):
    plt.subplot(2,3,i)
    plt.text(0.5,0.5,str((2,3,i)),fontsize=18,ha='center')
plt.show()

img

这个用法非常简单和直观,着重说一下plt.subplots_adjust这个方法,他设置了子图之间的纵、横两方向上的间隙,然后子图中的文本就是他的编号规则。

fig, ax = plt.subplots()

fig,ax=plt.subplots(2,2,figsize=(16,10))

# 画第1个图:折线图
x=np.arange(1,100)
ax[0][0].plot(x,x*x)

# 画第2个图:散点图
ax[0][1].scatter(np.arange(0,10), np.random.rand(10))

# 画第3个图:饼图
ax[1][0].pie(x=[15,30,45,10],labels=list('ABCD'),autopct='%.0f',explode=[0,0.05,0,0])

# 画第4个图:条形图
ax[1][1].bar([20,10,30,25,15],[25,15,35,30,20],color='b')
plt.show()

使用fig, ax = plt.subplots()同样可以达到和刚才的图一样的效果

这个方法更直接。事先先把画板分隔好。

image-20210715101827981

可以看到这个的原理更直接,fig, ax = plt.subplots()语句就将画板分为2x2的形状,其中ax是matplotlib.axes._subplots.AxesSubplot 这个类型的,我们可以理解为这是一个子plot类,我们在这上面操作它把图像画到figure上面去。我们直接根据列表的下标指定画图的位置。最后显示figure即可。

位置索引规则

image-20210715104709419

几个plt.subplot()和fig, ax = plt.subplots()需要注意的不同之处:

  1. 位置索引不同

    plt.subplot()的位置信息是第三个参数,是用一个从1开始的序列(1,2,3…)并按照行优先的顺序表示第几个图

    fig, ax = plt.subplots()中画图是在子图ax上画,需要制定子图的索引,这个索引和np.array的2D数组的索引规则一致,都是按照行列顺序,且都是从0开始,例如第2行第1列的子图是 ax [1] [0]

  2. 调整图的大小的方式不同

    细心的你肯定注意到了plt.subplot()和常规作图改变大小一样,只需要在画图前加上 plt.figure(figsize=(16,10))即可

    但是这种方法对改变fig, ax = plt.subplots()的子图大小无效

    fig, ax = plt.subplots()的改变子图的做法是**fig,ax=plt.subplots(2,2,figsize=(16,10)) **

    即在分割画板时在参数中加上画板的大小

    补充:figsize的单位是英尺,所以多用了就大概知道该用什么样的参数了

plt.subplot( )的位置索引其实没有fig, ax = plt.subplots()清晰

因为如果plt.subplot(3,3,7 )的位置索引是7,还要去数7的位置在哪里

但是fig, ax = plt.subplots(3,3)加上ax[2] [0] 是采用2D数组的索引规则,可以马上定位到这张图就在第3行第一列的位置

所以fig, ax = plt.subplots()用的场景更多一些,一般来说都使用它

子图间坐标轴的共用

有没有一种感觉,就是有时候子图显得非常拥挤,因为每个子图都有自己的一套独立的坐标轴,如果这些子图的坐标轴的取值都是一样的,那我们能否让他们同方向上公用,用以简化图形的描述呢,当然可以。

我们下面介绍子图间坐标轴的共用。

以fig, ax = plt.subplots()为例,因为其用的场合更多

import numpy as np
import matplotlib.pyplot as plt

fig, ax = plt.subplots(2,3,sharex= True,sharey= True)
print(ax)
plt.show()

[[<matplotlib.axes._subplots.AxesSubplot object at 0x0000000005EF4748>
  <matplotlib.axes._subplots.AxesSubplot object at 0x0000000005F23278>
  <matplotlib.axes._subplots.AxesSubplot object at 0x0000000004831B38>]
 [<matplotlib.axes._subplots.AxesSubplot object at 0x0000000004886320>
  <matplotlib.axes._subplots.AxesSubplot object at 0x00000000048C4A90>
  <matplotlib.axes._subplots.AxesSubplot object at 0x00000000049176D8>]]

img

从图中,我们看出,同方向上重复的坐标轴已经省去,画面简洁而清爽。

而要做的仅仅是在fig, ax = plt.subplots()中多加参数sharex= True或sharey= True,表示共享横坐标或共享纵坐标

扩展:

同时我们可以看出plt.subplots的返回值是一个二维数组,内含子图的坐标轴,我们可以进行引用,利用坐标轴对象也可以在当前子图上进行同样的绘图操作。

import numpy as np
import matplotlib.pyplot as plt

fig, ax = plt.subplots(2,3,sharex= True,sharey= True)
for i in range(2):
    for j in range(3):
        ax[i,j].text(0.5,0.5,str((i,j)),fontsize=18,ha='center') # ax[i,j]与之前的常规方法不同,等同于 ax[i][j]
plt.show()

img

子图和整图的标题

plt.suptitle( )可以设置整个图的标题 (supertitle的简写)

下面我们分别以plt.subplot()和 fig, ax = plt.subplots()两种方法为例介绍子图和整图的标题

plt.subplot()

方式一:

plt.figure(figsize=(16,10))
# 设置整个图的标题
plt.suptitle('total figure',fontsize = 16)

# 画第1个图:曲线图
x=np.arange(1,100)
plt.subplot(2,2,1)
plt.plot(x,x*x)
plt.title('figure1')

# 画第2个图:散点图
plt.subplot(2,2,2)
plt.scatter(np.arange(0,10), np.random.rand(10))
plt.title('figure2')

# 画第3个图:饼图
plt.subplot(2,2,3)
plt.pie(x=[15,30,45,10],labels=list('ABCD'),autopct='%.0f',explode=[0,0.05,0,0])
plt.title('figure3')

# 画第4个图:条形图
plt.subplot(2,2,4)
plt.bar([20,10,30,25,15],[25,15,35,30,20],color='b')
plt.title('figure4')
plt.show()
image-20210715110724553

因为plt有直接的title方法,可以直接接着画一个图,用**plt.title()**加一个title

方式二:

# plt.subplot

plt.figure(figsize=(16,10))
# 设置整个图的标题
plt.suptitle('total figure',fontsize = 24)

# 画第2个图:散点图
x=np.arange(1,100)
ax1 = plt.subplot(2,2,1)
ax1.plot(x,x*x)
ax1.set_title('figure1')

# 画第2个图:散点图
ax2 = plt.subplot(2,2,2)
ax2.scatter(np.arange(0,10), np.random.rand(10))
ax2.set_title('figure2')

# 画第3个图:饼图
ax3 = plt.subplot(2,2,3)
ax3.pie(x=[15,30,45,10],labels=list('ABCD'),autopct='%.0f',explode=[0,0.05,0,0])
ax3.set_title('figure3')

# 画第4个图:条形图
ax4 = plt.subplot(2,2,4)
ax4.bar([20,10,30,25,15],[25,15,35,30,20],color='b')
ax4.set_title('figure4')
plt.show()

方式二的结果和方式一一样,区别在于使用了类似于 **fig, ax = plt.subplots()**的表示方式,

因为之前的补充中有提及到plt.subplot()plt.plot( ) 等同于 ax = plt.subplot() ax.plot()

但是要注意的一点是在设置标题时,只有plt才有title这个方法,而子图ax只有set_title这个方法,这个地方一定要注意

这种做法的好处在哪:

其好处是可以在最后在统一给每个子图加上标题,每个子图都有自己的变量命名设置标题,使结构更清晰

而plt的方法只能紧跟着子图设置标题,每个子图都是plt.title( )的方式设置

以下是两种方式改变结构后的对比:

# plt.subplot

plt.figure(figsize=(16,10))

# 设置整个图的标题
plt.suptitle('total figure',fontsize = 24)

# 画第2个图:散点图
x=np.arange(1,100)
ax1 = plt.subplot(2,2,1)
ax1.plot(x,x*x)

# 画第2个图:散点图
ax2 = plt.subplot(2,2,2)
ax2.scatter(np.arange(0,10), np.random.rand(10))

# 画第3个图:饼图
ax3 = plt.subplot(2,2,3)
ax3.pie(x=[15,30,45,10],labels=list('ABCD'),autopct='%.0f',explode=[0,0.05,0,0])

# 画第4个图:条形图
ax4 = plt.subplot(2,2,4)
ax4.bar([20,10,30,25,15],[25,15,35,30,20],color='b')

# 设置小标题
ax1.set_title('figure1')
ax2.set_title('figure2')
ax3.set_title('figure3')
ax4.set_title('figure4')

plt.show()
image-20210715110724553

此时采用plt的方式就会出现问题,它必须对应着每个子图块里设置标题
注意以下为错误示范

# plt.subplot

plt.figure(figsize=(16,10))
# 设置整个图的标题
plt.suptitle('total figure',fontsize = 24)

# 画第2个图:散点图
x=np.arange(1,100)
plt.subplot(2,2,1)
plt.plot(x,x*x)

# 画第2个图:散点图
plt.subplot(2,2,2)
plt.scatter(np.arange(0,10), np.random.rand(10))

# 画第3个图:饼图
plt.subplot(2,2,3)
plt.pie(x=[15,30,45,10],labels=list('ABCD'),autopct='%.0f',explode=[0,0.05,0,0])

# 画第4个图:条形图
plt.subplot(2,2,4)
plt.bar([20,10,30,25,15],[25,15,35,30,20],color='b')

# 设置小标题
plt.title('figure1')
plt.title('figure2')
plt.title('figure3')
plt.title('figure4')
plt.show()

<img src="C:\Users\123\AppData\Roaming\Typora\typora-user-images\image-20210715115453270.png" alt="image-20210715115453270" style="zoom:50%;" />

可以看到在错误示范中只有第四个图有标题figure4,其原因是这四个语句都是plt.title( ),他们四个语句都在第四个子图块中,所以前三个子图肯定没有标题,因为前三个子图块中没有plt.title( )语句

所以它们是一次覆盖的关系,最后一个plt.title('figure4')覆盖掉使最后结果为标题figure4

fig, ax = plt.subplots()

fig,ax=plt.subplots(2,2,figsize=(16,10))
plt.suptitle('total figure',fontsize = 24)

# 画第1个图:折线图
x=np.arange(1,100)
ax[0][0].plot(x,x*x)
ax[0][0].set_title('figure1')
    
# 画第2个图:散点图
ax[0][1].scatter(np.arange(0,10), np.random.rand(10))
ax[0][1].set_title('figure2')

# 画第3个图:饼图
ax[1][0].pie(x=[15,30,45,10],labels=list('ABCD'),autopct='%.0f',explode=[0,0.05,0,0])
ax[1][0].set_title('figure3')

# 画第4个图:条形图
ax[1][1].bar([20,10,30,25,15],[25,15,35,30,20],color='b')
ax[1][1].set_title('figure4')
plt.show()

注意子图ax没有title的方法,要使用set_title

最终的呈现结果和上面一致

非规则子图

如果我们想实现一种不规则的多行多列子图,该怎么办?

subplot2grid

subplot2grid()可以让子区跨越固定的网格布局,前面的子区函数subplot()的用法只擅长于规则的子图

而非规则的子图使用subplot2grid( ),通过使用subplot2grid()函数的 rowspan 和colspan 参数可以让子区跨越固定的网格布局的多个行和列,实现不同的子区布局。

plt.figure(figsize=(10,6))
ax1 = plt.subplot2grid((3,3), (0,0), colspan=3) #col 显示图形占3列
ax1.text(0.5,0.5,'ax1',fontsize=18,ha='center')

ax2 = plt.subplot2grid((3,3), (1,0), colspan=2)  #col 显示图形占2列
ax2.text(0.5,0.5,'ax2',fontsize=18,ha='center')

ax3 = plt.subplot2grid((3,3), (1, 2), rowspan=2)  #row 显示图形占2行
ax3.text(0.5,0.5,'ax3',fontsize=18,ha='center')

ax4 = plt.subplot2grid((3,3), (2, 0))
ax4.text(0.5,0.5,'ax4',fontsize=18,ha='center')

ax5 = plt.subplot2grid((3,3), (2, 1))  
ax5.text(0.5,0.5,'ax5',fontsize=18,ha='center')
plt.suptitle("subplot2grid",fontsize=18)  
image-20210715112257396

我们通过调用函数subplot2grid(shape,loc),将参数shape 所划定的网格布局作为绘图区域,实现在参数loc 位置处绘制图形的目的。

上面的代码中,参数shape (3,3)设置了一个3 行3 列的网格布局,参数loc 表示子图索引(数组规则)的位置

以“ax1 = plt.subplot2grid((3,3), (0,0), colspan=3)”语句为例,参数loc=(0,0)就表示图形以左上角远点的位置作为起点,跨越三列。

相应的,“ax3 = plt.subplot2grid((3,3), (1, 2), rowspan=2)”语句就表示图形以第二行和第三列作为位置起点,跨越两行。

值得注意的是,图形位置的索引是和plt.subplots()一致,都是数组规则的索引。

将各图放入布局

plt.figure(figsize=(20,16))
x=np.arange(1,100)
ax1 = plt.subplot2grid((3,3), (0,0), colspan=3)
ax1.plot(x,x*x)
ax1.set_title('f1',fontsize=18)

ax2 = plt.subplot2grid((3,3), (1,0), colspan=2)  #col 显示图形占2列
ax2.scatter(np.arange(0,10), np.random.rand(10))
ax2.set_title('f2',fontsize=18)

ax3 = plt.subplot2grid((3,3), (1, 2), rowspan=2)  #row 显示图形占2行
ax3.pie(x=[15,30,45,10],labels=list('ABCD'),autopct='%.0f',explode=[0,0.05,0,0])
ax3.set_title('f3',fontsize=18)

ax4 = plt.subplot2grid((3,3), (2, 0))
ax4.bar([20,10,30,25,15],[25,15,35,30,20],color='b')
ax4.set_title('f4',fontsize=18)

ax5 = plt.subplot2grid((3,3), (2, 1))  
ax5.boxplot(x)
ax5.set_title('f5',fontsize=18)

plt.suptitle("subplot2grid",fontsize=24) 
plt.show()

结果呈现

image-20210715113849806

plt.GridSpec()

这是就要利用GridSpec方法了。

import numpy as np
import matplotlib.pyplot as plt

grid = plt.GridSpec(2, 3, wspace=0.5, hspace=0.5)
plt.subplot(grid[0,0])
plt.subplot(grid[0,1:3])
plt.subplot(grid[1,0:2])
plt.subplot(grid[1,2])

plt.show()

img

我们来简单的介绍一下这个图的画法,我们得到一个长×宽为2×3的grid区域,这个grid的原点是左上角,第一行第一列的子图占据grid的第0个长度,第0个宽度;第一行第二列子图占据grid的第0个长度,第1和第2个宽度(因此用分片1:3来表示),其他的以此类推。这样我们就能画出我们自定义位置和大小的子图(不过注意,其长和宽都是grid单位的整数倍)

最后,基于这种画法,我们举一个实际的例子。我们考虑在一个子图中画出二元正态分布的联合分布图,而在另两个子图中分别画出x轴和y轴方向上的边缘分布图。

import numpy as np
import matplotlib.pyplot as plt

mean = [0, 0]
cov = [[1, 1], [1, 4]]
x, y = np.random.multivariate_normal(mean, cov, 3000).T
plt.figure(figsize=(6,6))
grid = plt.GridSpec(4, 4, wspace=0.5, hspace=0.5)

main_ax = plt.subplot(grid[0:3,1:4])
plt.plot(x,y,'ok',markersize=3,alpha=0.2)

y_hist = plt.subplot(grid[0:3,0],xticklabels=[],sharey=main_ax)#和大子图共y轴
plt.hist(y,60,orientation='horizontal',color='gray')#图形水平绘制
y_hist.invert_xaxis()#x轴调换方向

x_hist = plt.subplot(grid[3,1:4],yticklabels=[],sharex=main_ax)#和大子图共x轴
plt.hist(x,60,orientation='vertical', color='gray')#图形垂直绘制
x_hist.invert_yaxis()#y轴调换方向

plt.show()

img

在最大的子图中,我们通过散点图,绘制了联合分布情况,而两个小的子图,通过频次直方图绘制了边缘分布。这种图在统计分析上非常有用。

  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值