05.
如何分区和绘图
大家好,我是小C,上期给大家分享——Python数据可视化—认识坐标系
本期分享内容:Python数据可视化—如何分区和绘图
本期小C邀请的是齐伟(Python大学教材及畅销书作者)为我们分享Python数据可视化。
PYTHON
如何分区和绘图
在图纸上绘图,有时候整张图纸只绘制一幅图,有时候要绘制多幅图。在 Matplotlib 中要实现这种功能,可以使用 plt.subplots,前面已经用过此函数,此处要深入理解它的特点。
首先,要引用 Matplotlib 的模块。
%matplotlib inline
import matplotlib.pyplot as plt
import matplotlib
matplotlib.rcParams['axes.unicode_minus'] = False
1.3.1 分区
在前面编写可视化代码的时候,plt.subplots() 已经出现过了,它返回了一个图像对象和一个坐标系对象。但是,在以往调用的时候,没有向函数提供任何参数。如果像下面代码这样,就不会只返回一个坐标系对象了。
fig, ax = plt.subplots(3, 3, sharex='col', sharey='row')
输出结果:
从输出结果中可知,现在得到了 3 × 3 = 9 个坐标,即在一张图中得到了 9 个坐标系。
ax
#Out:
array([[<matplotlib.axes._subplots.AxesSubplot object at 0x118bb3470>,
<matplotlib.axes._subplots.AxesSubplot object at 0x118ba69b0>,
<matplotlib.axes._subplots.AxesSubplot object at 0x11ac36e48>],
[<matplotlib.axes._subplots.AxesSubplot object at 0x11ac652b0>,
<matplotlib.axes._subplots.AxesSubplot object at 0x11ac8c6d8>,
<matplotlib.axes._subplots.AxesSubplot object at 0x11acb49b0>],
[<matplotlib.axes._subplots.AxesSubplot object at 0x11acdcc88>,
<matplotlib.axes._subplots.AxesSubplot object at 0x11ad0c0b8>,
<matplotlib.axes._subplots.AxesSubplot object at 0x11ad333c8>]],
dtype=object)
变量 ax 其实引用了一个数组对象,其形状是 3 × 3 的,每个元素是一个坐标系对象。于是乎就可以通过类似 ax[1, 2] 的方式得到某一个坐标系对象,然后对该坐标系对象实施有关操作,比如设置属性,或者调用方法,在上一课中有一个绘制脸谱的例子,就是这个道理的具体应用。
作为复习,可以看下面的代码,目的是依次在每个坐标系内标注上行列。
fig, ax = plt.subplots(3, 3, sharex='col', sharey='row')
for i in range(3):
for j in range(3):
ax[i, j].text(0.5, 0.5, str((i, j)), fontsize=18, ha='center')
输出结果:
显然,以上功能的实现,就在于 plt.subplots 函数。为了深入理解此函数,可以先浏览它的完整参数。
plt.subplots(nrows=1, ncols=1, sharex=False, sharey=False, squeeze=True, subplot_kw=None, gridspec_kw=None, **fig_kw)
结合之前展示的 Matplotlib 中函数(方法)的参数,会发现不仅参数个数比较多,而且还通过 **fig_kw、*arg 等方式,许可传入更多的参数。这就说明,它们提供了非常灵活的功能。但是,记忆就麻烦了。而实际上,不用刻意记忆,因为通过官方文档或者在 Jupyter 中使用查看帮助文档的智力,能够查看到对所有参数解释说明。
选择几个常用的参数给予说明:
nrows 和 ncols,必须是整数,分别设置了行(nrows)和列(ncols)的坐标系分区数量;
sharex 和 shapey,布尔值,或者选择 ‘none’、‘all’、‘row’、‘col’ 中的某个字符串,默认是 False。
注意观察上面的代码,已经分别设置了两个参数的值。为了对比,如果不设置,即默认值 False,会是什么效果?
fig, ax = plt.subplots(3, 3)
输出结果
将此处的结果图示和前面的对比,发现此图示中,每个坐标系的坐标轴都标明了横纵坐标的刻度。这就是 sharex 和 sharey 的作用效果。
其他参数,就请读者自己查看帮助文档了。
以上解释了 plt.subplots,它返回两个对象,除了 ax,还有一个 fig,那么如何理解 fig 呢?
其实,fig 是在坐标系之上的对象,称之为“图像”(figure)。创建这个图像对象的基本方式是 fig = plt.figure(),而后所建立的坐标系对象,其实是由图像对象生成的。
fig = plt.figure()
ax1 = fig.add_axes([0.1, 0.1, 0.8, 0.8])
输出结果:
如果还对第1-1课的内容有印象,对于这个结果就不陌生了。在这里,首先创建了图像对象,然后使用图像对象的方法创建了此图像中的一个坐标系对象。
注意,fig.add_axes([0.1, 0.1, 0.8, 0.8]) 中的参数,是按照 [left, bottom, width, height] 的顺序,设置坐标系相对于图像对象的位置以及坐标系对象的长宽(其数值是相对于图像对象长宽的比例,width=0.8 意味着坐标系对象的宽度是图像对象宽度的 80%)。
在理解了上面的绘图原理之后,就可以进一步,做出这样的结果了:
fig = plt.figure()
ax1 = fig.add_axes([0.1, 0.1, 0.8, 0.8])
ax2 = fig.add_axes([0.6, 0.5, 0.2, 0.3])
输出结果:
这就是面向对象思想的魅力。
诚然,图像对象还有很多属性和方法,当以后作图的时候如果用到,再进一步研习。
在同一个图像对象上,设置不同大小的坐标系分区,还可以使用 GridSpec 实现。
from matplotlib.gridspec import GridSpec #①
fig = plt.figure()
fig.suptitle("划分不同大小的坐标系分区") #②
gs = GridSpec(3, 3, figure=fig) #③
ax1 = fig.add_subplot(gs[0, :]) #④
ax2 = fig.add_subplot(gs[1, :-1])
ax3 = fig.add_subplot(gs[1:, -1])
ax4 = fig.add_subplot(gs[-1, 0])
ax5 = fig.add_subplot(gs[-1, -2])
def format_axes(fig):
for i, ax in enumerate(fig.axes): #⑤
ax.text(0.5, 0.5, "ax%d" % (i+1), va="center", ha="center")
ax.tick_params(labelbottom=False, labelleft=False)
format_axes(fig)
输出结果:
在上述示例中,使用 GridSpec 类,实现了最终图示的结果。下面依次解释程序中的某些语句:
① 引入 GridSpec,注意它不在 plt 下面;
② 使用图像对象的方法设置本图像的标题;
③ 建立了 GridSpec 的实例。
下面列出这个类的完整参数:
GridSpec(nrows, ncols, figure=None, left=None, bottom=None, right=None, top=None, wspace=None, hspace=None, width_ratios=None, height_ratios=None)
根据望文生义的原则,基本上能够猜测到这些参数的含义,况且,很多参数跟以往遇到的含义基本一致,实在不行还可以查看帮助文档。因此,此处就不再赘述各个参数的含义了。
gs 实例对象的建立,就相当于在图像对象 fig 的图层上创建了网格(3 × 3 的网格)。
④ 中的 fig.add_subplot 方法旨在创建一个坐标系分区,其效果与 add_axes 方法相仿。注意 add_subplot 的参数,是以 gs 的网格来说明这个坐标系分区所覆盖的网格区域。可以把 gs 对象的网格看做一个二维的数组(3 × 3),gs[0, :] 就是获得数组的切片。可以用下图依次表示各部分切片或者索引得到的结果。
如此,就在所规定的网格范围上建立了相应的坐标系对象,即为 ④ 和后面各句的效果。
⑤ 中的 fig.axes 是通过图像对象的属性 axes 得到该图像中的所有坐标系对象,然后依次调用每个坐标系对象,使用坐标系对象的属性和方法。
通过上述各项操作,已经基本感受到了如何实现坐标系分区的操作,下面就开始在坐标系中绘图。
1.3.2 曲线
在本课中,凡是依据某个函数关系在坐标系中绘制的图像,都泛称为曲线,尽管有的分明是直的。下面要演示的是在同一个坐标系中绘制多条曲线。
import numpy as np
fig, ax = plt.subplots()
x = np.linspace(0, 2*np.pi, 100)
ax.plot(x, np.sin(x), color='blue')
ax.plot(x, np.sin(x-np.pi/3), color='r', linestyle='-.')
ax.plot(x, np.sin(x-2*np.pi/3), color='#A52A2A', linewidth=3)
输出结果:
如果在一个坐标系中绘制一条曲线,只需要调用一次 ax.plot();如果要绘制多条曲线,就调用多次。
下面,要了解的就是 ax.plot 的多种多样的使用方法,上面的程序中,已经分别用不同的参数显示了对曲线的控制。
plot([x], y, [fmt], data=None, **kwargs)
x,y:表示横纵坐标的数据集。
fmt:可以在这里设置很多关于曲线的属性,比如前面程序中设置过线的颜色、线型、宽度等。有一部分可以用缩写的方式表示,比如 color=‘r’。关于颜色及其符号表示、线型及其符号表示,在前几课中已经列出了,请参考。
还有一个参数,上面的示例中没有显示,名为 marker,它能够指定表示坐标(点)的图形。例如:
plt.plot([1,2,3], [3,8,5], marker='o')
输出结果:
这里使用 marker=‘o’ 对坐标点指定了显示图形。marker 还可以取其他值,下表列出了可选值及其含义。
符号 | 说明 |
‘.’ | 实心小点 |
‘,’ | 一个像素,其实就是什么也没显示 |
‘o’ | 实心圆点 |
‘v’ | 一个角指向下的三角形 |
‘^’ | 一个角指向上的三角形 |
‘<’ | 一个角指向左的三角形 |
‘>’ | 一个角指向右的三角形 |
‘1’ | 一个棱指向下的三棱形 |
‘2’ | 一个棱指向上的三棱形 |
‘3’ | 一个棱指向左的三棱形 |
‘4’ | 一个棱指向右的三棱形 |
‘s’ | 正方形 |
‘p’ | 五边形 |
‘*’ | 星形 |
‘h’ | 六边形 |
‘H’ | 六边形 |
‘+’ | 加号 + 形 |
‘x’ | 乘号 × 形 |
‘D’ | 菱形(对角线相等) |
‘d’ | 菱形(水平对角线较短) |
’ | ’ |
‘_’ | 横线符号 |
以下几个参数也是跟上述图形设置有关的:
markersize,设置大小
markerfacecolor,设置颜色
markevery,指定所要设置的坐标点
以下面的程序为例,显示上述设置效果:
plt.plot([3,8,5,2,9],
marker='d',
markerfacecolor='yellow',
markersize=10,
markevery=[1,2,4])
输出结果:
对照图示结果,体会上述几个参数的效果,特别是 markevery,它是按照点的顺序指定坐标点的,如上即为第 1 个、第 2 个和第 4 个点,还要注意,计数是从 0 开始的。
data:用这个参数可以声明数据来源,也就是可以这样作图。
import pandas as pd
df = pd.DataFrame({"col1":[1,2,3,4,5,6], 'col2':[9,3,8,1,5,3], 'col3':[10,8,1,5,7,3]})
plt.plot('col1', 'col2', data=df, marker='D') #⑥
输出结果:
语句 ⑥ plt 的参数中,分别用字符串 ‘col1’ 和 ‘col2’ 表示坐标系中的 x 和 y 轴数据集,而它们是数据集 df 的两个特征。数据来源,则用 data 参数指明(data=df)。这充分显示了 Pandas 和 Matplotlib 的融洽,如果没有理解 Pandas,请参阅《跟老齐学Python:数据分析》,里面有深入浅出地讲解。
而事实上,Pandas 的数据对象也有绘图方法。
df.plot('col1', 'col2')
输出结果:
对于 Pandas 对象的绘图方法,与前面的使用方式基本一样,只不过是某个 Pandas 的方法罢了,这里不重复说明。
如果注意观察上图,发现它比以往的图多了一个东西,右上角的那个。
1.3.3 图例
在可视化的图示中,把类似上图右上角的那个东西,称为图例。比如,在地图中,图例就非常清楚地标明了图中表示首都、城市、公路、铁路等各用什么符号。
使用 Pandas 对象的绘图方法,会自动生成图例,这是非常友好的,再体会一番。
df = pd.DataFrame({"col1":[1,2,3,4,5,6], 'col2':[9,3,8,1,5,3], 'col3':[10,8,1,5,7,3]})
df.plot()
输出结果:
如果不用这种自动的方式,就要使用 plt.legend 生成图例。
a = np.arange(0, 3, .02)
b = np.arange(0, 3, .02)
c = np.exp(a)
d = c[: : -1]
plt.plot(a, c, 'k--', label="Model")
plt.plot(a, d, 'r:', label="Data")
plt.plot(a, c+d, 'b-', label="Total")
plt.legend(loc='upper right')
输出结果:
这次不仅用 plt.legend 函数生成了图例,还给函数提供了参数 loc=‘upper right’,通过它指定了图例的位置。在图像中,图例的位置有如下几种,并且每种位置都有相应的数字表示,在使用的时候,可以用“名称”字符串,也可以用“编号”整数。
名称 | 编号 | 名称 | 编号 |
best | 0 | center left | 6 |
upper right | 1 | center right | 7 |
upper left | 2 | lower center | 8 |
lower left | 3 | upper center | 9 |
lower right | 4 | center | 10 |
right | 5 |
如果 loc=0,Matplotlib 会自动根据情况找到最佳的图例位置。
在刚才的程序中,不知不觉就使用了另外一种绘图方式,没有面向对象,目的是要提醒注意,这种方式也要熟悉哦。
图例在坐标系围成的图像之内,这是通常情况。有些特殊情况下,可能要求图例放在其他位置,甚至排列方式也可以变化(默认都是纵向排列的)。
plt.plot([0,1,2,3,4,5], label="positive")
plt.plot([5,4,3,2,1,0], label="negative")
plt.legend(bbox_to_anchor=(0, 1), loc=3, ncol=2)
输出结果:
因此,有时间还是要了解 plt.legend 的其他参数,更多内容可以阅读这里。
阅读材料的确不少,这都在说明 Matplotlib 博大精深,值得学习。虽然很多阅读材料都是英文的,并且充满了专业术语,刚开始会有些不熟练,只要持之以恒,就不会感到困难了。
1.3.4 移动坐标轴
先看下面的程序,“温故”和“知新”同时具有:
X = np.linspace(-np.pi, np.pi, 256, endpoint=True)
C, S = np.cos(X), np.sin(X)
plt.plot(X, C, color="blue", linewidth=2.5, linestyle="-")
plt.plot(X, S, color="green", linewidth=1.0, linestyle="-.")
plt.xticks([-np.pi, -np.pi/2, 0, np.pi/2, np.pi],
[r'$-\pi$', r'$-\pi/2$', r'$0$', r'$+\pi/2$', r'$+\pi$']) #⑦
plt.yticks([-1, 0, +1], [r'$-1$', r'$0$', r'$+1$'])
plt.tick_params(axis='both', labelsize=18, colors='red')c
输出结果:
语句 ⑦ 貌似用了新的函数 plt.xticks,其实不然,以前用过 ax.set_xticks 方法,两者是在不同应用模式下的同一个功能。只不过在 ⑦ 中,除了规定横坐标的主刻线位置([-np.pi, -np.pi/2, 0, np.pi/2, np.pi])之外,还规定了主刻线的标示显示方式([r’-\pi
−π’, r’-\pi/2
−π/2’, r’0
0’, r’+\pi/2
+π/2’, r’+\pi
+π’])。plt.yticks 与此同样含义。
看着这张图,其实并不能满足需要,因为从数学的习惯而言,我们希望坐标原点应该在图像中间,即 x=0 和 y=0 的交点为坐标原点。
X = np.linspace(-np.pi, np.pi, 256, endpoint=True)
C, S = np.cos(X), np.sin(X)
plt.plot(X, C, color="blue", linewidth=2.5, linestyle="-")
plt.plot(X, S, color="green", linewidth=1.0, linestyle="-.")
plt.xticks([-np.pi, -np.pi/2, 0, np.pi/2, np.pi],
[r'$-\pi$', r'$-\pi/2$', r'$0$', r'$+\pi/2$', r'$+\pi$'])
plt.yticks([-1, 0, +1], [r'$-1$', r'$0$', r'$+1$'])
plt.tick_params(axis='both', labelsize=18, colors='red')
#在前面已有代码基础上增加下面的内容
ax = plt.gca() # ⑧
ax.spines['right'].set_color('none') #⑨
ax.spines['top'].set_color('none')
ax.xaxis.set_ticks_position('bottom') #⑩
ax.spines['bottom'].set_position(('data',0)) #⑪
ax.yaxis.set_ticks_position('left')
ax.spines['left'].set_position(('data',0)) #⑫
输出结果:
对这个结果还是相当满意的。不能满足于此,还要知其所以然。
⑧ 中的 plt.gca 函数的作用是获得当前的坐标系对象。在此语句之前,没有创建坐标系对象,是通过执行 plt 的函数完成各种操作。后面要用坐标系对象的方法和属性完成各种操作了,就要获得当前的坐标系对象。“gca”的含义就是“get current axis”。
⑨ 中的 ax.spines 是坐标系对象四周的边线。
ax.spines
# out
OrderedDict([('left', <matplotlib.spines.Spine at 0x11ff95780>),
('right', <matplotlib.spines.Spine at 0x11ff95ef0>),
('bottom', <matplotlib.spines.Spine at 0x11ff95518>),
('top', <matplotlib.spines.Spine at 0x11f7f7b38>)])
ax.spines 的值是一个类字典对象,通过类似 ax.spines[‘right’] 的样式得到相应的边线对象。⑨ 的含义就是将右边的边线颜色设置为 ‘none’,即没有颜色,不显示了。同理也设置了上面的边线为不显示。
⑩ 中的 ax.xaxis 显然是获得x轴,前面已经明确过了,坐标轴上有刻度(包含刻线和标示)。接下来的 set_ticks_position 方法就是设置表示刻度的位置,对于 x 轴而言,参数可选值为:{‘top’, ‘bottom’, ‘both’, ‘default’, ‘none’}。这里使用了 ‘bottom’,显然是要把刻度置于底端。其实,如果没有 ⑩,那么刻度会位于默认值规定的位置,即刻线在上下边线,标示只在下边线。
⑪ 的作用就是要移动下边线(ax.spines[‘bottom’]),把它作为 x 轴,移动到 y=0 的位置。set_position 用来确定移动的目标位置,注意它的参数,是用元组形式,基本格式为:(position type, amount)。其中 position type 的值有:
‘outward’,字符串,表示要将坐标轴移动到坐标系图形范围之外,ammount 规定移动的像素数;
‘axes’,字符串,将坐标轴移动到 Y 轴指定的位置,amount 的取值范围为 0.0 ~ 1.0;
‘data’,字符串,将坐标轴移动到由坐标指定的位置,amount 是具体的坐标位置。
由此可知 ax.spines[‘bottom’].set_position((‘data’,0)) 的意思就是将下边线移动到 y=0 的位置。而当参数是 (‘data’, 0) 的时候,还可以用 ‘zero’ 替代。于是乎 ⑫ 就这么做了。
如此,实现了坐标轴移动。
小结
本课主要学习了如下内容:
实现图像对象中的分区,subplots、fig.add_axes、GridSpec、fig.add_subplot;
对绘制的曲线进行多种属性配置,plt.plot、ax.plot;
图例的生成和位置控制,ax.legend;
坐标轴位置的设置,ax.spines、ax.spines[‘bottom’].set_position((‘data’,0))。
今日内容有get吗,欢迎各位留言讨论!
下期预告:多样化的图像
以上专栏均来自CSDN GitChat专栏《案例上手Python数据可视化》,作者齐伟,专栏详情可识别下方二维码查看哦!
了解更多详情
可识别下方二维码