上一篇教程我们使用Matplotlib在Jupyter Lab中画出了第一张图,这一篇我们先不着急继续画图,我想先带你认识一下Matplotlib中的画图逻辑,同时从计算机的角度去描述一张图。
如何描述一张图
你或许见到的第一个用来画图的工具是微软的办公套件Excel,在里面你可以画出一些折线图、柱状图或者是一些散点图。
Excel
你只需要选择合适的数据就可以画出一张简单的图表,不需要关注图形本身,只需提供数据即可。这种方式十分便捷,但也有一个缺点,当你选择一个要画的图表后,比如折线图,在给定不同的数据后,生成的折线图看起来差不多,但我们又很难去修改他。因为这张折线图里面的好多要素都已经被固定下来,Excel也没有为我们提供修改的方式。试想一下,当我想画出一百种不同的折线图,理论上需要微软为我们提供一百种不同的折线图才能做到这一点。即使微软为你提供了一百种不同的折线图,但如果某一天你还想画一百种不同的散点图呢?这样下去只会使得Excel被塞满了不常使用的图表。
让我们来分析一下这个问题,之所以需要不停地增加图表来满足我们的需求,是因为每一个图表可以调节的地方太少,如果可以对一个图表进行调整一些参数,就可以得到另一个不同的图表,那么这个问题就迎刃而解。
图形语法
你可能听说或使用过R语言中的ggplot,一个用来画图的工具。ggplot的理论基础源自一本数据可视化领域中非常著名的书籍:《图形语法》(《The Grammar of Graphics》)。在这本书中,作者提出将一张图拆解为不同的部分,比如一张图拆解为坐标轴、图例、图形元素、标题等等。相当于是对每一张图都拆解为这些部分,当我想改变一张图表的坐标轴的时候,我只需要改变坐标轴,其他的不需要进行修改,因为这些都是相互独立的。将这种组合的思想应用到画图上,就可以创造出千千万万张不同的图形。
Matplotlib有着相似的思想,他将一张图片划分为不同的层级,每个层级中含有不同的元素,通过调节每个元素的参数,来画出不同的图。在Matplotlib中分为四个层级,分别是Figure、Axes、Axis和Artist。他们的关系类似于下面这张图。
Matplotlib图层结构
我们所看到的每一张图都是一个Figure,这是Matplotlib的画板,在画板之上,可能会画着很多幅画,这便是Axes,在每一个Axes中会包含Axis,也就是这张画的坐标轴信息,在画之上,有着很多其他的元素,比如画上面的文字、图形、以及图例等等,这些被称为Artist。你可以这样简单的理解Matplotlib中的层级关系,但有一点需要你知道,其实在Matplotlib中所有看的见的东西都可以被称作Artist,甚至是Figure、Axes和Axis都可以算作Artist。但因为他们相对固定,没有画上的内容变化多。尽管是Artist,但被赋予了特殊的意义,也就有了专属的名字。
Matplotlib中的图层结构
Matplotlib官方对图层结构也有一张十分醒目的图,在这张图上,清晰地标注了一张图所有的元素。
- 画板 figure
- 画 axes
- 坐标轴 axis
- 图例 legend(Artist)
- 标记 marker(Artist)
- 文字 Text(Artist)
- ....
所以了解了上面,我们后知后觉到,Matplotlib修改图像的大小需要我们去修改Figure这一层的内容,修改坐标轴需要去修改Axis这一层的内容,画什么内容需要去修改Axes这一层的东西,而修改所画的内容则需要修改Artist这一层的内容。知道了这些,我们在后面的画图中,就知道了应该去哪里去修改一张图的某一块区域的参数,而不是乱改一通。
前面说了那么多,只是想让你从整体上对Matplotlib中的画图逻辑有一个大概的了解,在后面的学习中,只需要熟悉如何在matplotlib中控制每一个元素即可。画图是一个数据和逻辑交融的过程,图上的每一个元素都是由数据点和控制数据点的逻辑来组成。
命令式和面向对象式画图方式
命令式
在matplotlib中有两种画图的方式,一种是使用类似于matlab中的直接通过一行命令进行画图,这种方式适合对图没有太高的要求,需要快速看到图像结果。
设置figure
在命令式的下,可以使用下面的方式进行设置一个figure
fig = plt.figure(figsize=(3.5, 2.5))
创建Axes
import numpy as np import matplotlib.pyplot as plt import matplotlib as mpl x = np.linspace(1, 100, 50) plt.plot(x , x*x) plt.plot(x , x*x *x)
从我们之前的角度分析一下这张图,可以发现,画板(figure)只有一个,画(axes)也只有一张,所以这种方式适合将一组或多组数据画到同一个图上。如果需要在同一个画板上画多个图,一般会使用下面的方法。
当需要一个axes的时候,使用subplot去创建一个。这样就可以在同一个figure创建多个axes。传递给subplot的参数可以是以下的三种方式之一:
- 使用一个三位数字,比如221,表示2行2列第1个图
- 传递三个数字,比如(2, 2, 2),表示2行2列的第2个图
- 传递两个数字和一个元组,比如 (2, 2, (3, 4)),表示2行2列,但这个图占了原本第3个和第4个图的长度
plt.subplot(221) plt.plot(x, x*x) plt.subplot(2, 2, 2) plt.plot(x, x*x*x) plt.subplot(2,2,(3,4)) plt.plot(x, x + 2)
面向对象式
python是一门面向对象的语言,使用面向对象的方式是一个值得推荐的方式。在matplotlib中,使用面向对象的方式创建figure和axes。
和命令式不同,面向对象式的figure和axes的创建是同时进行的,可以设置行数和列数,同时还可以设置figure的大小。
fig1, axs = plt.subplots(2, 2, figsize=(3.5, 2.5))
当需要创建和命令时中横跨多个axes的图时,需要将原本的axes删除,然后通过GridSpec来指定一个新的axes。GridSpec在创建时,要和原来figure中的axes数量相同,通过add_subplot将一个新的axes添加到figure上。
from matplotlib.gridspec import GridSpec gs = GridSpec(2,2) fig2, axs2 = plt.subplots(2, 2, figsize=(3.5, 2.5)) axs2[1, 0].remove() axs2[1, 1].remove() # gs是一个axes numpy数组:[[1, 2], [3, 4]] # gs[1, :] 表示选择第二个元素的全部子元素,即 [3, 4] # gs本质不是axes,只是用作描述axes的位置信息 # 在这里表示新添加的axes占原来第3、4个axes的位置 fig2.add_subplot(gs[1,:])
以上就是matplotlib中最基本的figure和axes的创建方法,你可能发现了上面创建出来的axes好像会有一点“挤”,是因为我们没有设置matplotlib的布局模式。使用下面的方式来进行设置
fig1, axs = plt.subplots(2, 2, figsize=(3.5, 2.5)) fig1.tight_layout()
本期到此结束,下期我们将在Axes层面做一些操作。