Matplotlib 是 Python 中最基本的可视化工具。类比一下人类和 Matplotlib 画图过程,人类画图需要三个步骤:
- 找画板
- 用调色板
- 画画
Matplotlib 模拟了类似过程,也分三步
- FigureCanvas
- Renderer
- Artist
上面是 Matplotlib 里的三层 API:
- FigureCanvas 帮你确定画图的地方
- Renderer 帮你把想画的东西展示在屏幕上
- Artist 帮你用 Renderer 在 Canvas 上画图
一般用户只需用 Artist 就能自由的在电脑上画图了。
import matplotlib as mpl
import matplotlib.pyplot as plt
%matplotlib inline
注:%matplotlib inline 就是在 Jupyter notebook 里面内嵌画图的
# 可以将自己喜欢的颜色代码定义出来,然后再使用。
r_hex = '#dc2624' # red, RGB = 220,38,36
dt_hex = '#2b4750' # dark teal, RGB = 43,71,80
tl_hex = '#45a0a2' # teal, RGB = 69,160,162
r1_hex = '#e87a59' # red, RGB = 232,122,89
tl1_hex = '#7dcaa9' # teal, RGB = 125,202,169
g_hex = '#649E7D' # green, RGB = 100,158,125
o_hex = '#dc8018' # orange, RGB = 220,128,24
tn_hex = '#C89F91' # tan, RGB = 200,159,145
g50_hex = '#6c6d6c' # grey-50, RGB = 108,109,108
bg_hex = '#4f6268' # blue grey, RGB = 79,98,104
g25_hex = '#c7cccf' # grey-25, RGB = 199,204,207
1 Matplotlib基础介绍
1.1 概览
Matplotlib 包含两类元素:
- 基础 (primitives) 类:线 (line), 点 (marker), 文字 (text), 图例 (legend), 网格 (grid), 标题 (title), 图片 (image) 等。
- 容器 (containers) 类:图 (figure), 坐标系 (axes), 坐标轴 (axis) 和刻度 (tick)
基础类元素是我们想画出的标准对象,而容器类元素是基础类元素的寄居处,它们也有层级结构。
图 → 坐标系 → 坐标轴 → 刻度
由上图看出:
- 图包含着坐标系 (多个)
- 坐标系由坐标轴 组成 (横轴 xAxis 和纵轴 yAxis)
- 坐标轴 上面有刻度 (主刻度 MajorTicks 和副刻度 MinorTicks)
Python里面“万物皆对象”,坐标系、坐标轴和刻度都是对象。
fig = plt.figure()
ax = fig.add_subplot(1,1,1)
plt.show()
xax = ax.xaxis
yax = ax.yaxis
print( 'fig.axes:', fig.axes, '\n')
print( 'ax.xaxis:', xax )
print( 'ax.yaxis:', yax, '\n' )
print( 'ax.xaxis.majorTicks:', xax.majorTicks, '\n' )
print( 'ax.yaxis.majorTicks:', yax.majorTicks, '\n')
print( 'ax.xaxis.minorTicks:', xax.minorTicks )
print( 'ax.yaxis.minorTicks:', yax.minorTicks )
fig.axes: [<AxesSubplot:>]
ax.xaxis: XAxis(54.0,36.0)
ax.yaxis: YAxis(54.0,36.0)
ax.xaxis.majorTicks: [<matplotlib.axis.XTick object at 0x00000214C05AC240>, <matplotlib.axis.XTick object at 0x00000214C0599DA0>, <matplotlib.axis.XTick object at 0x00000214C05D6358>, <matplotlib.axis.XTick object at 0x00000214C05D6780>, <matplotlib.axis.XTick object at 0x00000214C05D6C18>, <matplotlib.axis.XTick object at 0x00000214C05D6E10>]
ax.yaxis.majorTicks: [<matplotlib.axis.YTick object at 0x00000214C05ACA58>, <matplotlib.axis.YTick object at 0x00000214C05AC668>, <matplotlib.axis.YTick object at 0x00000214C05E8358>, <matplotlib.axis.YTick object at 0x00000214C05E8780>, <matplotlib.axis.YTick object at 0x00000214C05E8C18>, <matplotlib.axis.YTick object at 0x00000214C05F00F0>]
ax.xaxis.minorTicks: [<matplotlib.axis.XTick object at 0x00000214C05B7390>]
ax.yaxis.minorTicks: [<matplotlib.axis.YTick object at 0x00000214C05CA390>]
坐标系和坐标轴指向同一个图 (侧面验证了图、坐标系和坐标轴的层级性)。
print( 'axes.figure:', ax.figure )
print( 'xaxis.figure:', xax.figure )
print( 'yaxis.figure:', yax.figure )
axes.figure: Figure(432x288)
xaxis.figure: Figure(432x288)
yaxis.figure: Figure(432x288)
创造完以上四个容器元素后,我们可在上面添加各种基础元素,比如:
- 在坐标轴和刻度上添加标签
- 在坐标系中添加线、点、网格、图例和文字
- 在图中添加图例
1.2 图
# 图是整个层级的顶部,在图中可以添加基本元素「文字」。
plt.figure()
plt.text( 0.5, 0.5, 'Figure', ha='center',
va='center', size=20, alpha=.5 )
plt.xticks([]), plt.yticks([])
plt.show()
用 plt.text() 函数,其参数解释如下:
- 第一、二个参数是指文字所处位置的横轴和纵轴坐标
- 第三个参数字符是指要显示的内容
- ha, va 是横向和纵向位置,指文字与其所处“坐标”之间是左对齐、右对齐或居中对齐
- size 设置字体大小
- alpha 设置字体透明度 (0.5 是半透明)
# 在图中可以添加基本元素「折线」。
plt.figure()
plt.plot( [0,1],[0,1] )
plt.show()
当我们每次说画东西,看起来是在图 (Figure) 里面进行的,实际上是在坐标系 (Axes) 里面进行的。一幅图中可以有多个坐标系,因此在坐标系里画东西更方便 (有些设置使用起来也更灵活)。
1.3 坐标系与子图
一幅图 (Figure) 中可以有多个坐标系 (Axes),那不是说一幅图中有多幅子图 (Subplot),因此坐标系和子图是不是同样的概念?
在绝大多数情况下是的,两者有一点细微差别:
- 子图在母图中的网格结构一定是规则的
- 坐标系在母图中的网格结构可以是不规则的
子图
把图想成矩阵,那么子图就是矩阵中的元素,因此可像定义矩阵那样定义子图 - (行数、列数、第几个子图)。
subplot(rows, columns, i-th plots)
plt.subplot(2, 1, 1)
plt.xticks([])
plt.yticks([])
plt.text(0.5, 0.5, 'subplot(2, 1, 1)', ha='center', va='center', size=20, alpha=0.5)
plt.subplot(2, 1, 2)
plt.xticks([])
plt.yticks([])
plt.text(0.5, 0.5, 'subplot(2, 1, 2)', ha='center', va='center', size=20, alpha=0.5)
Text(0.5, 0.5, 'subplot(2, 1, 2)')
这两个子图类似于一个列向量
- subplot(2,1,1) 是第一幅
- subplot(2,1,2) 是第二幅
plt.subplot(1, 2, 1)
plt.xticks([])
plt.yticks([])
plt.text(0.5, 0.5, 'subplot(1, 2, 1)', ha='center', va='center', size=20, alpha=0.5)
plt.subplot(1, 2, 2)
plt.xticks([])
plt.yticks([])
plt.text(0.5, 0.5, 'subplot(1, 2, 2)', ha='center', va='center', size=20, alpha=0.5)
Text(0.5, 0.5, 'subplot(1, 2, 2)')
这两个子图类似于一个行向量
- subplot(1,2,1) 是第一幅
- subplot(1,2,2) 是第二幅
fig, axes = plt.subplots(nrows=2, ncols=2)
for i, ax in enumerate(axes.flat):
ax.set(xticks=[], yticks=[])
s = 'subplot(2, 2,' + str(i) + ')'
ax.text(0.5, 0.5, s, ha='center', va='center', size=20, alpha=0.5)
plt.show()
这次我们用过坐标系来生成子图 (子图是坐标系的特例嘛),第 1 行
fig, axes = plt.subplots(nrows=2, ncols=2)
得到的 axes 是一个 2×2 的对象。在第 3 行的 for 循环中用 axes.flat 将其打平,然后在每个 ax 上生成子图。
坐标系
坐标系比子图更通用,有两种生成方式
- 用 gridspec 包加上 subplot()
- 用 plt.axes()
不规则网格
import matplotlib.gridspec as gridspec
G = gridspec.GridSpec(3, 3) # 将整幅图分成 3×3 份赋值给 G
ax1 = plt.subplot(G[0, :])
plt.xticks([]), plt.yticks([])
plt.text(0.5, 0.5, 'Axes 1', ha='center', va='center', size=20, alpha=0.5)
ax2 = plt.subplot(G[1,:-1])
plt.xticks([]), plt.yticks([])
plt.text(0.5, 0.5, 'Axes 2', ha='center', va='center', size=20, alpha=0.5)
ax3 = plt.subplot(G[1:,-1])
plt.xticks([]), plt.yticks([])
plt.text(0.5, 0.5, 'Axes 3', ha='center', va='center', size=20, alpha=0.5)
ax4 = plt.subplot(G[-1, 0])
plt.xticks([]), plt.yticks([])
plt.text(0.5, 0.5, 'Axes 4', ha='center', va='center', size=20, alpha=0.5)
ax5 = plt.subplot(G[-1,1])
plt.xticks([]), plt.yticks([])
plt.text(0.5, 0.5, 'Axes 5', ha='center', va='center', size=20, alpha=0.5)
plt.show()
plt.subplot(G[]) 函数生成五个坐标系。G[] 里面的切片和 Numpy 数组用法一样:
- G[0, :] = 图的第一行 (Axes 1)
- G[1, :-1] = 图的第二行,第一二列 (Axes 2)
- G[1:, -1] = 图的第二三行,第三列 (Axes 3)
- G[-1, 0] = 图的第三行,第一列 (Axes 4)
- G[-1, 1] = 图的第三行,第二列 (Axes 5)
大图套小图
plt.axes([0.1, 0.1, 0.8, 0.8])
plt.xticks([]), plt.yticks([])
plt.text(0.6, 0.6, 'axes([0.1,0.1,0.8,0.8])', ha='center', va='center', size=20, alpha=0.5)
plt.axes([0.2, 0.2, 0.3, 0.3])
plt.xticks([]), plt.yticks([])
plt.text(0.5, 0.5, 'axes([0.2,0.2,0.3,0.3])', ha='center', va='center', size=10, alpha=0.5)
plt.show()
plt.axes([l,b,w,h]) 函数,其中 [l, b, w, h] 可以定义坐标系
- l 代表坐标系左边到 Figure 左边的水平距离
- b 代表坐标系底边到 Figure 底边的垂直距离
- w 代表坐标系的宽度
- h 代表坐标系的高度
如果 l, b, w, h 都小于 1,那它们是标准化 (normalized) 后的距离。比如 Figure 底边长度为 10, 坐标系底边到它的垂直距离是 2,那么 b = 2/10 = 0.2。
生成坐标系的2种方式
# 1.同时生成图和坐标系
fig, ax = plt.subplots()
plt.xticks([]), plt.yticks([])
s = 'Style 1\n\nfig,ax=plt.subplots()\nax,plot()'
ax.text(0.5, 0.5, s, ha='center', va='center',size=20,alpha=0.5)
plt.show()
# 2.先生成图,再添加坐标系
fig = plt.figure()
ax = fig.add_subplot(1,1,1)
ax.set(xticks=[],yticks=[])
s = 'Style 2\n\nfig=plt.figure()\nax=fig.add_subplot()\nax.plot()'
ax.text(0.5,0.5,s,ha='center',va='center',size=20,alpha=0.5)
plt.show()
1.4 坐标轴
一个坐标系 (Axes),通常是二维,有两条坐标轴 (Axis):
- 横轴:XAxis
- 纵轴:YAxis
每个坐标轴都包含两个元素
- 容器类元素「刻度」,该对象里还包含刻度本身和刻度标签
- 基础类元素「标签」,该对象包含的是坐标轴标签
「刻度」和「标签」都是对象。
r_hex
'#dc2624'
fig, ax = plt.subplots()
ax.set_xlabel('Label on x-axis')
ax.set_ylabel('Label on y-axis')
for label in ax.xaxis.get_ticklabels():
# label is a text instance
# 标签是一个文本对象
label.set_color(dt_hex)
label.set_rotation(45)
label.set_fontsize(20)
for line in ax.yaxis.get_ticklines():
# line is a line2D instance
# 刻度是一个二维线段对象
line.set_markersize(20)
line.set_markeredgewidth(3)
plt.show()
第 2 和 3 行打印出 x 轴和 y 轴的标签。
第 5 到 9 行处理「刻度」对象里的刻度标签,将它颜色设定为深青色,字体大小为 20,旋转度 45 度。
第 11 到 15 行处理「标签」对象的刻度本身 (即一条短线),标记长度和宽度为 20 和 3。
1.5 刻度
刻度 (Tick) 的核心内容就是
- 一条短线 (刻度本身)
- 一串字符 (刻度标签)
首先定义一个 setup(ax) 函数,主要功能有
- 去除左纵轴 (y 轴)、右纵轴和上横轴
- 去除 y 轴上的刻度
- 将 x 轴上的刻度位置定在轴底
- 设置主刻度和副刻度的长度和宽度
- 设置 x 轴和 y 轴的边界
- 将图中 patch 设成完全透明
将上面效果全部合并,这个 setup(ax) 就是把坐标系里所有元素都去掉,只留 x 轴来添加各种刻度。
import matplotlib.ticker as ticker
def setup(ax):
ax.spines['right'].set_color('none') # 去除左纵轴 (y 轴)
ax.spines['left'].set_color('none') # 去除右纵轴
ax.spines['top'].set_color('none') # 去除上横轴
ax.yaxis.set_major_locator(ticker.NullLocator()) # 去除y轴上的刻度
ax.xaxis.set_ticks_position('bottom') # 把x轴上的刻度位置定在轴底
ax.tick