第5章

Python绘图




数学中,数之间的联系经常需要借助图形的生动性和直观性来阐明。在本章中,将介绍一种有效的数据表示方法: 使用Matplotlib为数据绘制图形。Matplotlib是一个用于在Python中创建静态、动画和交互式视图的综合库。它能让使用者很轻松地将数据图形化,并且提供多样化的输出格式。
5.1安装Matplotlib
Anaconda集成了NumPy,而Matplotlib是NumPy的一个库。如果安装了Anaconda则无须单独安装Matplotlib。
如果需要安装Matplotlib及其依赖项,在macOS、Windows和Linux发行版上,可以使用以下命令安装Wheel包: 
(1) python m pip install U pip。
(2) python m pip install U matplotlib。
对于Linux发布版,可以使用如下命令安装第三方发布版: 
(1) Debian / Ubuntu: sudo aptget install python3matplotlib。
(2) Fedora: sudo dnf install python3matplotlib。
(3) Red Hat: sudo yum install python3matplotlib。
(4) Arch: sudo pacmanS pythonmatplotlib。
5.2绘制简单图形
我们使用Python来绘制的第一张图形是连接(2, 3)、(6, 9)两点的线段。参看以下代码: 


In [1]: import matplotlib.pyplot as plt

x_axis = [2, 3]

y_axis = [6, 9]

plt.plot(x_axis, y_axis)

plt.show()

在这段代码中,首先导入 pyplot绘图接口,并为其起了一个别名plt,这样后续使用时可以减少代码的输入量。有的程序中使用from pylab import *,这样在后续的代码中可以直接使用plot和show。这种写法是为了照顾MATLAB用户的使用习惯,本书不推荐使用这种写法,特别是在大型工程中,因为这样做可能会污染工程的命名空间。



注意: 不要直接导入pylab。








接下来的两行定义了两个列表,分别表示X坐标轴和Y坐标轴上两点的坐标值。函数plot()在当前图形的当前平面直角坐标系内,使用线条、标记或两者的结合标识出x和y的映射关系。函数plot()接收可变长度的入参,X轴上的坐标列表是可选输入,Y轴上的坐标列表是必须的。如果当前图形和当前平面直角坐标系不存在,则函数 plot()会首先创建它们,然后创建并返回一个Line2D对象。更准确地说,返回一个包含多个Line2D对象的列表。最后一行的函数show()使用Matplotlib的默认配置将图形显示出来。
如果使用Notebook,则可以在绘图之前包括命令%matplotlib inline或%matplotlib Notebook,以使绘图自动显示在Notebook内部。如果代码通过IPython控制台运行,则图形可能会自动出现在控制台中。在Spyder中,绘图结果默认在右上角的Plots窗口显示。无论哪种方式,始终使用函数show()以显示绘图的做法都是一种好的习惯。
以上代码的运行结果如图 51所示。


图51(2, 6)、(3, 9)两点的连线


默认情况下,绘制的结果仅在Plots窗口显示,如果希望它同时能够在IPyhton Console内显示,需在option栏内取消对Mute inline plotting的选择。
函数plot()有多个关键字参数,其中关键字参数marker用于设置各点在图中的显示方式。增加该参数后,函数plot()绘图时会将列表中的点在连线上标注出来,代码如下: 


In [2]: x_axis = [2, 4, 6]

  y_axis = [6, 12, 18]

  plt.plot(x_axis, y_axis, marker='o')

  plt.show()

由于调用plot()时增加了关键字参数marker(关于关键字参数,详情可参见3.5.2节),参数列表中的每个点均被标识了出来,如图52所示。


图52三点被标识的连线


以下代码中缺失了关键字marker: 


In [3]: x_axis = [2, 4, 6]

  y_axis = [6, 12, 18]

  plt.plot(x_axis, y_axis, 'o')

  plt.show()

此时输出的结果如图53所示,绘图结果是散点图。


图53散点图


Matplotlib中可供选择的简单的参数marker选项如表51所示,可尝试修改示例代码中参数marker的值。


表51Matplotlib中的参数marker



marker显示符号说明

'.'·点

','.像素

'o'●实心圆

'v'倒三角

'^'▲正三角

'<'左三角

'>'右三角

'1'

'2'

'3'

'4'

'8'八边形

's'■正方形1

'p'五边形

'P'十字

'*'星形

'h'六边形1

'H'六边形2

'+'+加号

'x'×乘号

'X'叉号

'D'◆正方形2

'd'菱形

'|'竖线

'_'横线

针对同样的一组数据,Matplotlib还支持柱状图和散点图的绘制,代码如下: 


In [4]: import matplotlib.pyplot as plt

  x_axis = [2, 4, 6]

  y_axis = [6, 12, 18]



  #绘制散点图

  plt.subplot(121)

  plt.scatter(x_axis, y_axis)



  #绘制柱状图

  plt.subplot(122)

  plt.bar(x_axis, y_axis)

函数scatter()用于绘制散点图,函数bar()用于绘制柱状图。函数subplot()用于创建子图,关于子图详见5.5.3节。以上代码的运行结果如图 54所示。


图54散点图和柱状图


关键字参数color(代码中为c)可以指定线条的颜色。颜色最简单的一种表示方式是使用字符,可选字符是集合{'b','g','r','c','m','y','k','w'}中元素之一,它们分别代表蓝色、绿色、红色、青色、洋红色、黄色、黑色和白色。
以下代码将线条的颜色指定为黑色: 


In [5]: x_axis = [2, 4, 6]

  y_axis = [6, 12, 18]

  plt.plot(x_axis, y_axis, c='k')

  plt.show()

关键字参数linestyle(代码中为ls)用来指定线条的类型。有2种输入方式,其中最简便的是使用预定义的格式字符,可选类型字符如表 52所示。


表52线条类型参数



参数描述

'' or 'solid'实线

'' or 'dashed'虚线

'.' or 'dashdot'点画线

':' or 'dotted'点虚线

'None' or 'or'无线条

以下代码以黑色点画线的形式输出: 


In [6]: x_axis = [2, 4, 6]

  y_axis = [6, 12, 18]

  plt.plot(x_axis, y_axis, c='k', ls='-.')

  plt.show()

代码执行结果如图55所示。


图55黑色点画线图


5.3北京、上海和广州三地的平均温度
表53是北京、上海、广州三地的平均温度(数据来源: www.weatherbase.com)。 


表53北京、上海、广州三地的平均温度



平均温度单位: ℃

全年1月2月3月4月5月6月7月8月9月10月11月12月

北京12-306132024262520135-5

上海1645815202328272318126

广州22141518222627282827242015

平均最高温度单位: ℃

全年1月2月3月4月5月6月7月8月9月10月11月12月

北京1713111925293029251892

上海197811182327313026221610

广州25171720252830323231272320

相比数据,图形能够更加直观地对比三地之间的气温。同时,我们也可以学习如何在同一个坐标系中绘制多条曲线。本例需同时绘制三条折线,它们之间的区别为各点的Y坐标,因此,这个程序需要有4个列表: X坐标、北京平均温度、上海平均温度、广州平均温度,代码如下: 


In [7]: month = list(range(1, 13))

  beijing = [-3, 0, 6, 13, 20, 24, 26, 25, 20, 13, 5, -5]

  shangh = [4, 5, 8, 15, 20, 23, 28, 27, 23, 18, 12, 6]

  guangz = [14, 15, 18, 22, 26, 27, 28, 28, 27, 24, 20, 15]

  plt.plot(month, beijing, month, shangh, month, guangz)

  plt.legend(['Beijing', 'Shanghai', 'Guangzhou'])

  plt.show()

代码运行后的结果如图56所示。


图56北京、上海、广州三地的月平均温度曲线


在这段代码中,函数plot()的入参是多个坐标值的列表。此时,它们会两两一组,前一个被视为X坐标,后一个被视为Y坐标。如果列表是奇数,则最后一组只有Y坐标,其X坐标使用默认值(从0开始,步长为1)。可以删除示例程序中第3个month,对比一下输出。函数legend()增加示例图框以标识图上的每幅图形,输入的标签列表需要和函数plot()中的坐标列表相匹配。还可以为函数legend()指定第2个参数loc,以指定示例框的位置,例如lower center、center left和upper left。参数为best时,Matplotlib会自行选择最佳位置以确保图例的位置不会干扰图形。
5.4绘制函数图形
函数图形是函数的一种重要表达方式,它可以帮助我们理解函数的意义。本节将介绍如何绘制函数图形。
以下代码绘制正弦函数的曲线: 


In [8]: import matplotlib.pyplot as plt

  from math import sin, pi



  x = []

  y = []

  for i in range(201):

x_point = 0.01*i

x.append(x_point)

y.append(sin(pi*x_point)*2)

  plt.plot(x, y, linewidth=1, marker='.',

markerfacecolor='k', markersize=8)

  plt.show()

与5.3节例子不同的地方是,绘制函数图形时所需的数据是在代码运行的过程中生成的。另外,这段代码在调用函数plot()时,增加了4个参数: linewidth用于设置连线的宽度; marker用于设置各点的标识符样式; markerfacecolor用于设置标识符的颜色; markersize用于设置标识符的大小。代码运行后的效果如图57所示。


图57正弦函数曲线


通过第8段代码可以看到,绘制函数图形的通用步骤如下: 
(1) 首先,创建两个空列表。
(2) 然后,利用循环迭代将x和y的数值添加到列表中。注意两个表中的数据一定要满足函数的映射关系,否则不能正确绘制函数的图形。
(3) 最后,使用绘图函数创建绘图对象。
接下来我们实现一段比较通用的代码,并为式(51)绘制函数图形。
f(x)=e-xsin(2πx)(51)
代码如下: 


In [9]: import matplotlib.pyplot as plt

  from math import pi, sin, exp



  #修改函数定义,可以绘制不同函数的曲线

  func = lambda x: exp(-x)*sin(2*pi*x)



  def plot_func(f, start=0, stop=100, step=1, lab=None):

  """

  绘制函数图形



  参数

  ----

  f: 函数

 待绘制的函数

  start: float, 可选项

 自变量的起始值,默认为0。

  stop: float, 可选项

 自变量的终止值,默认为100。

  step: float, 可选项

 自变量递增值,默认为1。

  lab: str, 可选项

 函数说明字符串。



  返回值

  -------

  无。


  """

  l = 'Plot for func ' + lab




  #生成x和y的数值列表

  x = []

  y = []

  i = start

  while (i < stop):

  x.append(i)

  y.append(f(i))

  i += step



  #绘图

  plt.plot(x, y)

  plt.xlabel(r'$x$')

  plt.ylabel(r'$y$')

  if (lab == None):

plt.title('Plot for simple function')

  else:

plt.title(l)

  plt.show()



  plot_func(func, 0, 4, 0.01, r'$\exp^{-x}\sin(2\pi{x})$')

这里自定义的函数plot_func()可以绘制任意一元函数的图形。函数的5个入参分别是: 待绘制函数、自变量起始值、自变量终止值、自变量递增值和图形标题。在绘图时,函数title()为绘制的函数图形添加标题。函数xlabel()和函数ylabel()分别为X轴和Y轴添加说明标签。代码中与标签相关的几个字符串的形式看起来比较特别,X轴的标签是x,Y轴标签是y,调用函数plot_func()传入的函数说明字符串是r'$\exp^{-x}\sin(2\pi{x})$'。这些是LaTeX格式的字符串,LaTeX格式是专业级数学的标准排版方法。字符r不是LaTeX格式的一部分,它是一个Python符号,用来标明其后的字符串是一个原始字符串,这样反斜杠\之后的字符将不被Python转义。Matplotlib有一个内置的LaTeX表达式解析器和布局引擎,并提供自己的数学字体。以上这些原始字符串将由Matplotlib对其进行转义和设置输出字体。
示例代码中,数学函数由Lambda表达式定义。也可以修改这个表达式,尝试着绘制别的函数图形。式(51)的部分图形如图58所示。


图58示例函数的图形


5.5Matplotlib对象层次结构
Matplotlib的一个重要概念是它的对象层次结构,“层次结构”意味着在每个绘图之下都有一个类似树的Matplotlib对象结构。认识Matplotlib的对象层次结构,并使用面向对象的方法,可以更好地控制和自定义绘图。
图59很好地说明了Matplotlib的对象层次结构。该图由Matplotlib生成,源代码可以在Matplotlib官网搜索关键字Anatomy of a figure获得。图59使用的代码出自https://matplotlib.org/3.3.2/gallery/showcase/anatomy.html?highlight=figure%20anatomy。
图形(Figure)对象是最外层容器,该对象可以包含多个子图(Axes)对象。不同的子图占据图形中不同的区域,它们可能会有交集也可能相互分离。每个子图对象内则包含多个较小的对象,如: 刻度标记、线条、图例和文本框。为了表述方便,对只含有一个子图的图形,本书会忽略子图对象和图形对象之间的区别,需读者注意。
来自Pyplot 的绝大多数函数,例如 plot (),要么隐式地引用当前图形和当前子图,要么在不存在的情况下创建它们。当一张图形或者子图被创建后,我们可以使用相应的配置函数修改对象树上每个对象的属性。我们已经使用了函数title()、 xlabel()、ylabel()和legend()来修改子图内特定对象的属性。如图59所示,子图内的每条线条也是一个独立的Line2D对象。


图59Matplotlib的对象层次


5.5.1Line2D对象
函数plot()返回的是一个列表,列表的成员为Line2D对象。我们可以显式地将列表中的成员赋值给独立的变量,然后使用函数setp()修改每个变量的默认属性。函数setp()的第一个入参是被修改的对象,随后是所修改的属性列表。修改后的代码如下: 


In [10]: import matplotlib.pyplot as plt



month = list(range(1, 13))

beijing = [-3, 0, 6, 13, 20, 24, 26, 25, 20, 13, 5, -5]

shangh = [4, 5, 8, 15, 20, 23, 28, 27, 23, 18, 12, 6]

guangz = [14, 15, 18, 22, 26, 27, 28, 28, 27, 24, 20, 15]

bj, sh, gz = plt.plot(month, beijing, month, shangh, month, guangz)

plt.setp(bj, c='k', ls='-', label='Beijing')

plt.setp(sh, c='r', ls='-.', label='Shanghai')

plt.setp(gz, c='b', ls='--', label='Guangzhou')

plt.legend(loc='best')

plt.title('Average temperature in Beijing, Shanghai & Guangzhou')

plt.xlabel('Month')

plt.ylabel('Degrees Celsius')

plt.show()

当然,也可以使用函数plot()依次创建Line2D对象,这样做的好处是可以在创建时指明对象属性,代码如下: 


In [11]: import matplotlib.pyplot as plt



month = list(range(1, 13))

beijing = [-3, 0, 6, 13, 20, 24, 26, 25, 20, 13, 5, -5]

shangh = [4, 5, 8, 15, 20, 23, 28, 27, 23, 18, 12, 6]

guangz = [14, 15, 18, 22, 26, 27, 28, 28, 27, 24, 20, 15]

plt.plot(month, beijing, c='k', ls='-', label='Beijing')

plt.plot(month, shangh, c='r', ls='-.', label='Shanghai')

plt.plot(month, c='b', ls='--', label='Guangzhou')

plt.legend(loc='best')

plt.title('Average temperature in Beijing, Shanghai & Guangzhou')

plt.xlabel('Month')

plt.ylabel('Degrees Celsius')

plt.show()

函数legend()输入参数loc='best',Python会自动选择图示框的位置。以上两段代码执行后的效果相同,均如图 510所示。
5.5.2添加文本


图510北京、上海和广州月平均温度曲线


细心的读者可能会发现前文的例子中所使用的文字都是英文,这是因为考虑到可能部分读者的默认字体库不支持中文,所以代码中一直未使用中文字符。Matplotlib支持非西文文本输出,前提是当前使用的字体库内有希望输出的文字。如果当前字体库中没有需要输出的字符,则Matplotlib会输出一个方块,如图511所示。


图511输出不支持的字符


支持中文的常用字体库有“宋体”“黑体”和“楷体”,因此在输出中文之前,只需指定需要的字体库。方法之一是修改全局字体库,参见以下代码: 


In [12]: import matplotlib.pyplot as plt



plt.rcParams['font.sans-serif'] = ['SimHei'] #将字体替换为黑体

plt.rcParams['axes.unicode_minus'] = False #解决坐标轴负数的负号显示问题

plt.plot([0, 1], [1, 2])

plt.xlabel("x轴")

plt.ylabel("y轴")

plt.title("标题")

此时,绘制的图形便可支持中文输出,如图512所示。


图512中文标签


也可以为不同的标签设置不同的字体。除去各种标签文本之外,Matplotlib还可以使用函数text()在子图中的任意位置添加文本。使用函数annotate()添加特殊形式的标签,代码如下:
 

In [13]: import matplotlib.pyplot as plt



fig = plt.figure()

ax = fig.add_subplot(111)

ax.axis([0, 10, 0, 10])  #设置x轴和y轴的长度

ax.set_xlabel("黑体x轴", fontproperties="SimHei", size=18) #黑体

ax.set_ylabel("宋体y轴", fontproperties="SimSun", size=18) #宋体

ax.set_title("楷体标题", fontproperties="KaiTi", size=28)  #楷体



#在(2,6)点添加质能方程

ax.text(2, 6, r'$E = mc^2$', fontsize = 15)

ax.text(4, 0.05, ' X轴上的彩色文本', verticalalignment = 'bottom',

color = 'green', fontsize = 15)



#在(2,1)处绘制一个点,并为其增加注释

ax.plot([2], [1], 'o')

ax.annotate('注释', xy = (2, 1), xytext = (3, 4),

arrowprops = dict(facecolor = 'black', shrink = 0.05))

plt.show()

函数text()的前2个参数指明文本在子图坐标系中的位置,第3个参数是文本字符串。除此以外还可以使用一些关键字参数设定文本的其他属性,例如: 字体、大小、颜色等。有时我们需要对图上的一点做特别说明,此时可以使用函数annotate()。该函数在子图上输出一个带指示线的说明文字。参数xy通常是子图上需要说明的点,参数xytext是文本所在位置,第1个参数是说明文字。参数arrowprops是一个dict型对象,它用于描述指示线的各种属性。
上述代码遵循了面向对象的编码原则,首先创建图形,然后创建子图,接下来创建其他的子对象。建议大家在编程实践中一定要遵循这一原则。
以上代码运行效果如图513所示。

  
图513不同字体的标签


5.5.3多个子图(Axes)
在Matplotlib的树状层次结构中,每幅图形对象内可能包含多个子图对象。所有的绘图均是在当前图形的当前子图中完成。如同可以在当前子图内创建多个线条一样,我们也可以在当前图形内创建多个子图。创建多个子图的函数有subplot()、subplots()和subplot2grid(),函数subplot()使用起来比较简单,函数subplots()和subplot2grid()则更加灵活。
1. 函数subplot()
函数subplot()用于在当前图形中添加一个子图。它的入参的形式是(行数,列数,位置)。行数、列数、位置均是一个正整数。函数subplot()将图形视为被均匀分割的坐标网格。第一个网格的序号是1,从左上方开始向右编号,依次递增直至右下角的网格。每个子图占据其中的一些连续网格。参数的具体意义可以参看图514。其中图514(a)是水平分布,图514(b)是垂直分布,图514(c)是n×n的等分阵列(图中n=2)。

  
图514子图分布




图515非对称的子图分布

函数subplot()的第3个入参也可以是一个包含两个正整数的元组。它表示这个子图在图形上包含连续的几个网格,这里连续的网格必须在同一行上。这样将会得到非对称的子图分布。函数参数和效果示意如图515所示。
我们回到北京、上海和广州三地平均温度的例子。现在向图形中增加月平均最高温度的柱状图。此时,图形中需要增加一个新的子图对象以供柱状图使用。该对象位于月平均温度曲线的下方,代码如下: 


In [14]: import matplotlib.pyplot as plt



month = list(range(1, 13))

beijing = [-3, 0, 6, 13, 20, 24, 26, 25, 20, 13, 5, -5]

shangh = [4, 5, 8, 15, 20, 23, 28, 27, 23, 18, 12, 6]

guangz = [14, 15, 18, 22, 26, 27, 28, 28, 27, 24, 20, 15]



#创建12×8的图形对象

plt.figure(figsize=(12, 8))



#创建第一个子图

plt.subplot(211)

bj, sh, gz = plt.plot(month, beijing, month, shangh, month, guangz)

plt.setp(bj, color='k', label='北京')

plt.setp(sh, color='r', ls='-.', label='上海')

plt.setp(gz, color='b', ls=(0,(1,2,4,8)), label='广州')

plt.legend(loc='best')

plt.title('北京、上海和广州的月平均温度')

plt.xlabel('月')

plt.ylabel('摄氏度')



#创建第二个子图

plt.subplot(212)

bj_h = [1, 3, 11, 19, 25, 29, 30, 29, 25, 18, 9, 2]

sh_h = [7, 8, 11, 18, 23, 27, 31, 30, 26, 22, 16, 10]

gz_h = [17, 17, 20, 25, 28, 30, 32, 32, 31, 27, 23, 20]

w = 0.2

bj_x = list(i - w for i in range(1, 13))

sh_x = month

gz_x = list(i + w for i in range(1, 13))



#绘制柱状图

plt.bar(bj_x, bj_h, w, label='北京')

plt.bar(sh_x, sh_h, w, hatch='/', label='上海')

plt.bar(gz_x, gz_h, w, hatch='O', label='广州')

plt.legend(loc='best')

plt.title('北京、上海和广州的月平均最高温度')

plt.xlabel('月')

plt.ylabel('摄氏度')

plt.legend(loc='best')



#重置X轴主刻度

x_major_locator=plt.MultipleLocator(1)

ax=plt.gca()#当前子图为柱状图

print(ax.xaxis.get_major_locator()())

ax.xaxis.set_major_locator(x_major_locator)

plt.tight_layout()

plt.show()

以上代码执行过程有如下几部分: ①创建图形; ②创建子图; ③在子图内创建线条对象; ④修改子图内对象参数。
函数figure()用于创建图形对象,入参figsize指明图形大小。函数 subplot()的3个入参之间的逗号也可以被省略。函数bar()用于柱状图的绘制,它的第1个参数是刻度列表,指明每条柱状图中心在X轴上的位置,它的第2个参数是柱高列表,它的第3个参数是柱宽,宽度值不宜过大,否则柱状图会重叠。由于本例中绘制的是分组柱状图,我们使用了关键字参数label以区别每组中的数据。需特别注意分组柱状图各成员之间的位置关系,否则会出现重叠或间隔。本例中每条柱状图的宽度为0.2,这样每组3个柱的总宽度为0.6,这个值小于1,组与组之间在不同刻度之间便不会发生重叠。另外组内成员位置的计算也需注意,否则会产生组内重叠或者间隙过大的情况。本例中绘制的是垂直柱状图,函数barh()用于绘制水平柱状图。函数barh()的各输入参数与函数bar()的意义类似,差别是前2个入参对应的坐标轴交换了位置。
子图被创建时,其X、Y坐标轴上主刻度的间隔采用系统默认值。当有子对象(如前文示例代码中的Line2D对象或者柱状图对象)在其内被创建后,Matplotlib将会依据被创建对象在X轴和Y轴上的取值范围,设定X、Y坐标轴上主刻度的间隔,但是这种自动产生的刻度间隔可能无法满足我们的需求,因此需要在代码中调整。每个二维的子图内会有X和Y两个轴(Axis)对象,轴类型的成员函数 set_major_locator()可以设置该坐标轴的主刻度(Major Tick)。函数 set_major_locator()的入参是一个Locator类型的对象,在代码中由函数MultipleLocator()产生,函数MultipleLocator()的入参为期望的X轴主刻度间隔。本例中希望X轴上的主刻度以1为间隔,因此代码中的输入值为1。函数 set_minor_locator()用于设置从刻度(Minor Tick)间隔。从刻度不同于主刻度,从刻度上没有数值标签。
函数tight_layout()用于调整子图参数,使子图很好地适合于图形。该函数当前仅考虑了锚定到轴的轴标签、刻度标签、轴标题和示例框。在多数情况下,它能够使包含多个子图的绘图以比较满意的效果呈现出来,但在某些情况下,也可能因绘图过于复杂而无法得到满意的输出效果,此时需要更精细地去调整不同对象的位置参数,这里就不展开了。
2. 函数subplots()
也可以使用函数subplots()创建图形,这个函数的作用是创建一幅图形和一组子图。函数 subplots()经常使用的入参是nrows和ncols,用于指明所创建图形中网格的数量。它们的默认值都是1,也就是说,如果函数 subplots()在调用时没有传入nrow和ncols,则它创建的图形内仅能有一个子图。
现在我们需要在创建的图形中创建竖排的两个子图,因此输入参数中至少需要有 nrows=2。函数 subplots()返回一个图形对象和一个子图数组(NumPy数组)。我们可以将子图数组赋值给一个变量,此时该变量的类型是子图数组,也可以将数组中的子图对象分别赋值给一组变量。参看以下示例代码: 


In [15]: import matplotlib.pyplot as plt



month = list(range(1, 13))

beijing = [-3, 0, 6, 13, 20, 24, 26, 25, 20, 13, 5, -5]

shangh = [4, 5, 8, 15, 20, 23, 28, 27, 23, 18, 12, 6]




guangz = [14, 15, 18, 22, 26, 27, 28, 28, 27, 24, 20, 15]

bj_h = [1, 3, 11, 19, 25, 29, 30, 29, 25, 18, 9, 2]

sh_h = [7, 8, 11, 18, 23, 27, 31, 30, 26, 22, 16, 10]

gz_h = [17, 17, 20, 25, 28, 30, 32, 32, 31, 27, 23, 20]



#创建图形和子图列表

fig, (ax1, ax2) = plt.subplots(nrows=2, ncols=1, figsize=(12, 8))



#获取各子图默认主刻度

print(ax2.xaxis.get_major_locator()())

print(ax2.yaxis.get_major_locator()())



#绘制月平均温度曲线

bj, sh, gz = ax1.plot(month, beijing, month, shangh, month, guangz)

plt.setp(bj, color='k', label='北京')

plt.setp(sh, color='r', ls='-.', label='上海')

plt.setp(gz, color='b', ls=(0,(1,2,4,8)), label='广州')

ax1.legend(loc='best')

ax1.set_title('北京、上海和广州的月平均温度')

ax1.set_xlabel('月')

ax1.set_ylabel('摄氏度')

ax1.set_ylabel('Degrees Celsius')



#绘制月平均最高温度柱状图

w = 0.3

bj_x = list(i - w for i in range(1, 13))

sh_x = month

gz_x = list(i + w for i in range(1, 13))

ax2.bar(bj_x, bj_h, w, label='北京')

ax2.bar(sh_x, sh_h, w, hatch='/', label='上海')

ax2.bar(gz_x, gz_h, w, hatch='O', label='广州')

ax2.set_title('北京、上海和广州的月平均最高温度')

ax2.set_xlabel('月')

ax2.set_ylabel('摄氏度')

ax2.legend(loc='best')



#显示当前X轴和Y轴主刻度

print(ax2.xaxis.get_major_locator()())

print(ax2.yaxis.get_major_locator()())



#重置X轴主刻度

x_major_locator=plt.MultipleLocator(1)

ax2.xaxis.set_major_locator(x_major_locator)

x_major_locator=plt.MultipleLocator(0.1)

ax2.xaxis.set_minor_locator(x_major_locator)

plt.tight_layout()

plt.show()

SubPlots()的参数figsize用于指明绘图的大小。函数get_major_locator ()是Axis 类的成员函数,它可以获得当前轴的主刻度对象。通过调试代码print(ax2.xaxis.get_major_locator()()) 和print(ax2.yaxis.get_major_locator()()),我们可以查看子图被创建后,各坐标轴上主刻度的默认值。(ax1, ax2) 将函数 subplots()返回的子图数组中的每个子图对象分别赋值给两个变量,后续的代码分别对它们进行操作。这种面向对象的方法使得程序结构更加清晰,维护起来也会更加方便。以上两种方法创建并实现的代码运行结果如图 516所示。


图516北京、上海和广州月平均温度和月平均最高温度对比图


3. 函数subplot2grid()
函数subplot2grid()可以跨越多行和多列,因此可以更加灵活地创建子图,它的函数原型如下: 


subplot2grid(shape, location, rowspan, colspan)

参数shape是一个含有两个整数的元组,指明子图占据的栅格阵列大小。参数location也是一个含有两个整数的元组,指明子图在栅格阵列中的起始栅格。参数rowspan表示子图向下跨越的行数。参数colspan表示子图向右跨越的列数。参数rowspan和参数colspan的默认值都是1。以下代码对如何使用该函数进行了说明:


In [16]: import matplotlib.pyplot as plt



def annotate_axes(fig):

"""

本函数修改子图参数并在子图中心位置添加说明文本




"""

for i, ax in enumerate(fig.axes):

 ax.set_xticks([])

 ax.set_yticks([])

 ax.text(0.5, 0.5, "子图%d" % (i+1), 

va="center", ha="center")



fig = plt.figure()

ax1 = plt.subplot2grid((3, 3), (0, 0), colspan=3)

ax2 = plt.subplot2grid((3, 3), (1, 0), colspan=2)

ax3 = plt.subplot2grid((3, 3), (1, 2), rowspan=2)

ax4 = plt.subplot2grid((3, 3), (2, 0))

ax5 = plt.subplot2grid((3, 3), (2, 1))



annotate_axes(fig)



plt.show()

自定义函数annotate_axes()依次修改各子图参数并在其中增添文本。函数subplot2grid()所创建的子图会被添加到图形对象的子图数组axes中。for循环中Python内嵌函数enumerate()被用来迭代这个数组,变量i被用作子图索引,ax在每次循环中是每个子图的副本。子图(Axes)类的成员函数set_xticks()和set_yticks()的入参是一个空列表,因此X轴和Y轴上的刻度均被清除,这样输出的将是一个矩形框。每个子图在创建时,X轴和Y轴默认的最大刻度为1,因此函数text()前2个入参0.5表示文本将出现在子图的正中心。"子图%d" % (i+1)是格式字符串,详见2.2.7节。参数va和ha用来指定文本的对齐方式,代码中设定文本在水平方向和垂直方向均中心对齐。代码中将创建的图形划分成3×3的栅格阵列。第1幅子图从第1行第1个栅格开始,向右横跨3列。第2幅子图从第2行第1个栅格开始,向右横跨2列。第3幅子图从第2行第3个栅格开始,向下横跨2行。第4幅子图从第3行第1个栅格开始,向右横跨1列。第5幅子图从第3行第2个栅格开始,向右横跨1列。这里需要注意的是,函数subplot2grid()的第1个入参表示子图所在图形的栅格阵列的大小,因此所创建的子图应该相同。
以上代码的执行效果如图 517所示。


图517函数subplot2grid()创建子图效果


5.6字典(Dictionary)类型
5.5.2节的示例代码中函数annotate()的入参arrowprops是一个字典型的对象。通过type()可以查看dict(facecolor='black', shrink=0.05)的类型,代码如下: 


In [17]: type(dict(facecolor='black', shrink=0.05))

Out[17]: dict

字典是另一种可变容器类型,且可存储任意类型对象。它可以方便地实现关键字查找。假设我们要根据同学的名字来查询其在某次测验中的成绩,如果用4.2节介绍过的列表实现,则需要两个列表,示例代码如下:


Students = ['Richard','Sam','Tom']

Score = [99, 90, 76]

在这种实现方式中,两个列表中的成员的顺序必须严格匹配。根据姓名查找成绩,首先必须在姓名列表中找到该姓名的位置,然后依此序号索引成绩列表。因此,这种方式效率非常低,而且容易出错。
Python中字典类型因为可以进行关键字查找,所以可以方便利用姓名查找成绩。字典的每个键 key和值value组成一对,它们之间用冒号“:”分隔,每个键值对之间用逗号“,”分隔,整个字典包括在花括号 “{}” 中,格式如下:


d = {key1: value1, key2: value2,… }

字典索引和列表类似,其区别是索引时使用的是关键字而非索引号,格式如下: 


d[keyn]

现在,我们将学生成绩列表修改如下: 


In [18]: scores = {'Richard':99, 'Sam':90, 'Tom':76}

print(scores['Richard'])

99

键应该是唯一的,否则,最后出现的键值对将会替换前面的键值对,代码如下: 


In [19]: scores = {'Richard':99, 'Sam':90, 'Tom':76, 'Sam':60}

print(scores['Sam'])

60

由于关键字'Sam'被多次使用,因此只有最后一组的值被保留下来。
5.7本章小结
本章介绍了Matplotlib的基本使用方法。内容包含: 
(1) 如何使用Matplotlib绘制曲线图、散点图和柱状图。
(2) 如何增添文本标签。
(3) 如何指定字体库。
(4) 如何编写通用函数绘制数学函数曲线。
(5) Matplotlib的对象层次,如何使用面向对象的方法绘制图形。
本章是后续章节的基础,关于Matplotlib绘图的更多的内容会在后续章节中继续深入介绍。
5.8练习
练习1: 
在一个大小为 8×4 的图上绘制函数f(x)=x2的曲线,x在区间[-3, 3]取值,颜色是红色,线的粗细为 2(默认为 1)。
练习2: 
在一个大小为 8×4 的图上绘制函数f(x)=sin2(x-2)e-x2的曲线,x在区间[0, 2]取值,颜色是黑色,线的粗细为 2(默认为 1)。
练习3: 
假设我们扔出去一个球,它的飞行距离由初速度和抛射的角度决定。假设球从水平地面抛出,绘制该球在不同仰角和初速度下的运动轨迹。
编程提示: 
假设球的发射初速度是u,仰角θ。仰角采用角度值,速度单位为km/s,重力加速度g=9.81 m/s2。
球的上升时间可由式(52)算出: 
t=usinθg(52)
球飞行轨迹上的每个点可由式(53)算出: 
x=t′ucosθ,y=usin(θt′)gt′22(53)
其中t′在区间[0, 2t]取值。
练习4: 
单峰映射(Logistic Map)可以构建一个数字序列,它的数学形式如式(54)所示。
xn+1=rxn(1-xn)(54)
其中xn是介于0和1之间的数,r是正数。
现在完成以下编程任务: 

(1) 选定x0和r,计算数列中前N个数值。
(2) 取x0=0.5,计算在r=1.5和r=3.5时数列的前2000个数。使用最后100个数据绘图,对比分析两种情况下图形数据的趋势。
(3) 取x0=0.5,r从1开始递增至4,每次递增值为0.01。计算r取不同值时,数列的前2000个数值,并绘制它们。X轴对应r,Y轴对应数列中的值。