Matplotlib 绘制多子图(subplot)进行实验结果分析
本文主要总结于本人近期的科研经历,在最后撰写论文试验分析的时候曾经被导师要求绘制一幅2*8的16子图的超参数分析结果图。所以踩了很多坑,在这里给出简单的实现方式,本文使用的子图以折线图为例,其他的类型的图都可以举一反三。当然为了使结果呈现得更加美观简洁,本文在最后也介绍了一点如何调整刻度的小技巧。
预备工作
前期环境配置
pycharm-2021.2.2, python-3.7, matplotlib-3.2.2, numpy-1.20.2 具体其实只要有这几个版本的包就行了,更高版本推测应该也没事。导包:
import numpy as np
import matplotlib.pyplot as plt
准备数据
个人建议使用np.array来准备数据,这样可以随时确认数据的格式。例如本文就采用如图1所示的格式,来存储准备打印的数据,只不过这里是为了简单实验所以都用了相同的数据,到时候真的要做实验分析图的话,肯定是把真实的实验数据放上。如下所示,为2*4的图准备数据。
demo_data = np.array([82.51, 77.66, 81.92, 83.48, 78.95])
配置图表所需的字体
如果所投的期刊或会议有要求,那么这一步设置是很重要的,要使图标坐标轴上、图例的字体以及显示的字体与论文正文一致起来。注意这里需要以dict() 的类型来定义字体,另外还需要注意单独定义坐标轴上刻度的字体大小。如:
default_font = {'family': 'Times New Roman', 'weight': 'normal', 'size': 14}
tick_font = 12 # 坐标轴上字体大小
构建Figures
设置子图布局器
首先需要定义整个布局框架和子图component,代码如下:
plt.subplots(2, 4, figsize=(12, 4), dpi=100)
其中,第一个参数为子图的行数,第二个参数为列数。第三个参数figsize就如同我们定义单张matplotlib图时一样是表示画布的大小,如代码所示就是宽1600*高400的size,dpi越高则图像被放大后的失真率越小。
调整边距
同时我们需要在这里就对图片的边距进行调整,当然如果这6个值一开始设计的不理想,可以在打印出结果后根据自己的视觉感觉慢慢调整。如下代码:
plt.subplots_adjust(top=0.97, bottom=0.2, left=0.05, right=0.995, hspace=0.6, wspace=0.5)
其中,top值越接近1则子图距离顶部越接近,bottom值越接近于0则子图距离底部约接近,left值越接近于0则子图距离左边框越接近,right值越接近于1则子图距离右边框越接近,而hspace指的是两行子图之间的间距,值越大则两行之间距离越高,同理wspace指的是两列子图之间的距离,值越大距离越大。
定义子图
plt.subplot(241)
Subplot方法中可以这样写,这表示定义当前子图为1号图属于2*4的子图阵列。当然也可以写作:
plt.subplot(2, 4, 1)
加上逗号的好处是可以避免如果你需要定义2行5列,或十张以上子图,如subplot(2510)时,编译会报错的情况,当你打算定义10张以上子图的时候必须加逗号来区分(行数,列数和子图编号)这三个数字了(2, 5, 10),而不能直接写2510。此时,当着行代码被执行的时候就代表目前plt已经将布局器定位到了当前子图下,此时再使用plt进行修改的就是当前子图了。(直到代码执行到下一句plt.subplot(***)的时候,plt才会结束定义当前子图)
定义好当前子图就可以开始定义子图内部的结构了,这里以折线图为例子,还可以换成饼图,柱状图,散点图或是3D图。
axis_x_1 = np.arange(1, 6, dtype=np.float32)
plt.plot(axis_x_1, demo_data, color='darkred', linestyle='-', linewidth=2, marker='o',
markeredgecolor='darkred', markersize='4', markeredgewidth=2)
其中plot函数是主要的绘图函数,它代表着线图的主体,其中第一个参数定义着横轴接受数据的范围,比如我们刚刚定义的输入是5个数值,所以这里定义axis_x为np.arrange(1, 6)表示要接受5个值来展示,而第二个参数代表的就是具体的那五个数的数据了。至于后面的参数是一些修饰性质的,大家可以自行查阅文档来了解使用,无非就是主线条配色、主线条粗细、结点形状、结点线条样式,当然为了方便大家查询,我在Reference部分放置了颜色和形状参照表,并且在本文中8个图里也使用了四种不同的但是常用的颜色和形状表示。(补充,如果需要给图添加图例,可以在其中添加参数label=”label text”来实现)
在定义绘图部分后,要尝试为子图添加坐标轴和横纵坐标描述,如下代码:
plt.xticks(np.arange(1, 6), fontsize=tick_font)
plt.yticks(np.arange(75, 85.1, 2.0), fontsize=tick_font)
plt.xlabel('Values of $K$\n(a) Text here', default_font)
plt.ylabel('Text here ', default_font)
其中横纵坐标轴ticks的区间也建议使用np.arrange来定义,关于横坐标,我建议使用输入数据的公因数来绘制会比较美观,比如我的输入是10个数据,那么我可以选择每两个数据记一个刻度,np.arrange(0, 10.1, 2)(注意:这里写10.1是为了让10显示出来)当然如果你只有五个数据需要显示的话,可以直接像我代码中写的一样写成np.arrange(1, 6)来直接绘制5个刻度。纵坐标如何显示的美观呢,也是同样的法子,首先去观察你的输入数据他们最大值和最小值相差多少,然后将这个差除以5,就是间距应该设置多少。比如,我在代码中举的例子:我的数据最大的是83.48最小的是77.66,这时候我们将np.arrange的下限设为75(略低于最小值),上限设为85.1(略高于最大值,且+0.1为了显示出85这个刻度),间距设置为2,就可以美观的铺设五个刻度,并且尽可能的让我们的图像位于中央。
而xlabel中填写的就是坐标轴刻度的含义了,其中大家可以使用$ text $的方式是中间的text变为斜体,这是很有用的,比如在绘制超参数实验图的时候,往往我们定义的参数在Latex中都是倾斜体,为了一致起来,这里也要这样修改。并且善用\n换行符也可以让图变得更加美观。
保存图像
plt.savefig('demo.eps', bbox_inches='tight')
plt.show()
不要忘记保存和显示绘制好的图像,Latex插入图像建议使用.eps格式,所以我在输出的时候采用的.eps,当然你也可以改成.png或是.jpg都没关系,其中的参数bbox_inches是表示以什么样的边距输出图像,tight是最窄边距,不过这个参数在你自定义设置了边距时效果不明显,当你没有自己设置边距,而画布有定义的比较大的时候,这个参数就很有效果了。同时需要注意的是如果你想一边保存一边在Pycharm里查看即时生成的效果图,那么这个plt.show是要放在保存之后的,不然有时会出错。
完整代码
import numpy as np
import matplotlib.pyplot as plt
demo_data = np.array([82.51, 77.66, 81.92, 83.48, 78.95])
default_font = {'family': 'Times New Roman', 'weight': 'normal', 'size': 14}
tick_font = 12 # 坐标轴上字体大小
plt.subplots(2, 4, figsize=(12, 4), dpi=100)
plt.subplots_adjust(top=0.97, bottom=0.2, left=0.05, right=0.995, hspace=0.6,
wspace=0.5)
# 画第1个图:
plt.subplot(241)
axis_x_1 = np.arange(1, 6, dtype=np.float32)
plt.plot(axis_x_1, demo_data, color='darkred', linestyle='-', linewidth=2, marker='o',
markeredgecolor='darkred', markersize='4', markeredgewidth=2)
plt.xticks(np.arange(1, 6), fontsize=tick_font)
plt.yticks(np.arange(75, 85.1, 2.0), fontsize=tick_font)
plt.xlabel('Values of $K$\n(a) Text here', default_font)
plt.ylabel('Text here ', default_font)
# 画第2个图:
plt.subplot(242)
axis_x_2 = np.arange(1, 6, dtype=np.float32)
plt.plot(axis_x_2, demo_data, color='darkred', linestyle='-', linewidth=2, marker='o',
markeredgecolor='darkred', markersize='4', markeredgewidth=2)
plt.xlabel('Values of $K$\n(a) Text here', default_font)
plt.ylabel('Text here ', default_font)
plt.xticks(np.arange(1, 6), fontsize=tick_font)
plt.yticks(np.arange(75, 85.1, 2.0), fontsize=tick_font)
# 画第3个图:
plt.subplot(243)
axis_x_3 = np.arange(1, 6, dtype=np.float32)
plt.plot(axis_x_3, demo_data, color='navy', linestyle='-', linewidth=2, marker='v',
markeredgecolor='navy', markersize='4', markeredgewidth=2)
plt.xlabel('Values of $K$\n(a) Text here', default_font)
plt.ylabel('Text here ', default_font)
plt.xticks(np.arange(1, 6), fontsize=tick_font)
plt.yticks(np.arange(75, 85.1, 2.0), fontsize=tick_font)
# 画第4个图:
plt.subplot(244)
axis_x_4 = np.arange(1, 6, dtype=np.float32)
plt.plot(axis_x_4, demo_data, color='navy', linestyle='-', linewidth=2, marker='v',
markeredgecolor='navy', markersize='4', markeredgewidth=2)
plt.xlabel('Values of $K$\n(a) Text here', default_font)
plt.ylabel('Text here ', default_font)
plt.xticks(np.arange(1, 6), fontsize=tick_font)
plt.yticks(np.arange(75, 85.1, 2.0), fontsize=tick_font)
# 画第5个图:
plt.subplot(245)
axis_x_5 = np.arange(1, 6, dtype=np.float32)
plt.plot(axis_x_5, demo_data, color='darkorange', label="label text", linestyle='-', linewidth=2, marker='s',
markeredgecolor='darkorange', markersize='4', markeredgewidth=2)
plt.xlabel('Values of $K$\n(a) Text here', default_font)
plt.ylabel('Text here ', default_font)
plt.xticks(np.arange(1, 5.1, 1), fontsize=tick_font)
plt.yticks(np.arange(75, 85.1, 2.0), fontsize=tick_font)
plt.legend(loc='upper left') # 定义图例显示的位置
# 画第6个图:
plt.subplot(246)
axis_x_6 = np.arange(1, 6, dtype=np.float32)
plt.plot(axis_x_6, demo_data, color='darkorange', label="label text", linestyle='-', linewidth=2, marker='s',
markeredgecolor='darkorange', markersize='4', markeredgewidth=2)
plt.xlabel('Values of $K$\n(a) Text here', default_font)
plt.ylabel('Text here ', default_font)
plt.xticks(np.arange(1, 5.1, 1), fontsize=tick_font)
plt.yticks(np.arange(75, 85.1, 2.0), fontsize=tick_font)
plt.legend(loc='upper right')
# 画第7个图:
plt.subplot(247)
axis_x_7 = np.arange(1, 6, dtype=np.float32)
plt.plot(axis_x_7, demo_data, color='darkgreen', linestyle='-', linewidth=2, marker='D',
markeredgecolor='darkgreen', markersize='4', markeredgewidth=2)
plt.xlabel('Values of $K$\n(a) Text here', default_font)
plt.ylabel('Text here ', default_font)
plt.xticks(np.arange(1, 6), fontsize=tick_font)
plt.yticks(np.arange(75, 85.1, 2.0), fontsize=tick_font)
plt.grid(True)
# 画第8个图:
plt.subplot(248)
axis_x_8 = np.arange(1, 6, dtype=np.float32)
plt.plot(axis_x_8, demo_data, color='darkgreen', linestyle='-', linewidth=2, marker='D',
markeredgecolor='darkgreen', markersize='4', markeredgewidth=2)
plt.xlabel('Values of $K$\n(a) Text here', default_font)
plt.ylabel('Text here ', default_font)
plt.xticks(np.arange(1, 6), fontsize=tick_font)
plt.yticks(np.arange(75, 85.1, 2.0), fontsize=tick_font)
plt.grid(True)
plt.savefig('demo.eps', bbox_inches='tight')
plt.show()
效果图
Reference
[1] 表格和图例的参考:https://blog.csdn.net/HangoverLG/article/details/106044304
[2] 颜色和形状参考表:https://blog.csdn.net/The_Time_Runner/article/details/102599891