Python 数学应用(一)(1)https://developer.aliyun.com/article/1506372
方程组
解(线性)方程组是研究矩阵的主要动机之一。这类问题在各种应用中经常出现。我们从写成线性方程组的形式开始
其中n至少为 2,*a[i,j]和b[i]*是已知值,*x[i]*值是我们希望找到的未知值。
在解这样的方程组之前,我们需要将问题转化为矩阵方程。这是通过将系数a[i,j]收集到一个n × n矩阵中,并使用矩阵乘法的性质将这个矩阵与方程组联系起来实现的。因此,让
是包含方程中系数的矩阵。然后,如果我们将x作为未知数(列)向量,包含x[i]值,b作为(列)向量,包含已知值b[i],那么我们可以将方程组重写为单个矩阵方程
现在我们可以使用矩阵技术来解决这个问题。在这种情况下,我们将列向量视为n × 1矩阵,因此前面方程中的乘法是矩阵乘法。为了解决这个矩阵方程,我们使用linalg模块中的solve例程。为了说明这种技术,我们将解决以下方程组作为示例:
这些方程有三个未知值,x[1],x[2]和x[3]。首先,我们创建系数矩阵和向量b。由于我们使用 NumPy 来处理矩阵和向量,我们为矩阵A创建一个二维 NumPy 数组,为b创建一个一维数组:
import numpy as np from numpy import linalg A = np.array([[3, -2, 1], [1, 1, -2], [-3, -2, 1]]) b = np.array([7, -4, 1])
现在,可以使用solve例程找到方程组的解:
linalg.solve(A, b) # array([ 1., -1., 2.])
这确实是方程组的解,可以通过计算A @ x并将结果与b数组进行对比来轻松验证。在这个计算中可能会出现浮点舍入误差。
solve函数需要两个输入,即系数矩阵A和右侧向量b。它使用 LAPACK 例程解决方程组,将矩阵A分解为更简单的矩阵,以快速减少为一个可以通过简单替换解决的更简单的问题。这种解决矩阵方程的技术非常强大和高效,并且不太容易受到浮点舍入误差的影响。例如,可以通过与矩阵A的逆矩阵相乘(在左侧)来计算方程组的解,如果已知逆矩阵。然而,这通常不如使用solve例程好,因为它可能更慢或导致更大的数值误差。
在我们使用的示例中,系数矩阵A是方阵。也就是说,方程的数量与未知值的数量相同。在这种情况下,如果且仅当矩阵A的行列式不为 0 时,方程组有唯一解。在矩阵A的行列式为 0 的情况下,可能会出现两种情况:方程组可能没有解,这种情况下我们称方程组是不一致的;或者可能有无穷多个解。一致和不一致系统之间的区别通常由向量b决定。例如,考虑以下方程组:
左侧的方程组是一致的,有无穷多个解;例如,取x = 1 和y = 1或x = 0和y = 2都是解。右侧的方程组是不一致的,没有解。在上述两种情况下,solve例程将失败,因为系数矩阵是奇异的。
系数矩阵不需要是方阵才能解决方程组。例如,如果方程比未知值多(系数矩阵的行数多于列数)。这样的系统被称为过度规定,只要是一致的,它就会有解。如果方程比未知值少,那么系统被称为不足规定。如果是一致的,不足规定的方程组通常有无穷多个解,因为没有足够的信息来唯一指定所有未知值。不幸的是,即使系统有解,solve例程也无法找到系数矩阵不是方阵的系统的解。
特征值和特征向量
考虑矩阵方程Ax = λx,其中A是一个方阵(n × n),x是一个向量,λ是一个数字。对于这个方程有一个x可以解决的λ被称为特征值,相应的向量x被称为特征向量。特征值和相应的特征向量对编码了关于矩阵A的信息,因此在许多矩阵出现的应用中非常重要。
我们将演示使用以下矩阵计算特征值和特征向量:
我们必须首先将其定义为 NumPy 数组:
import numpy as np from numpy import linalg A = np.array([[3, -1, 4], [-1, 0, -1], [4, -1, 2]])
linalg模块中的eig例程用于找到方阵的特征值和特征向量。这个例程返回一对(v, B),其中v是包含特征值的一维数组,B是其列是相应特征向量的二维数组:
v, B = linalg.eig(A)
只有具有实数条目的矩阵才可能具有复特征值和特征向量。因此,eig例程的返回类型有时将是复数类型,如complex32或complex64。在某些应用中,复特征值具有特殊含义,而在其他情况下,我们只考虑实特征值。
我们可以使用以下序列从eig的输出中提取特征值/特征向量对:
i = 0 # first eigenvalue/eigenvector pair lambda0 = v[i] print(lambda0) # 6.823156164525971 x0 = B[:, i] # ith column of B print(x0) # array([ 0.73271846, -0.20260301, 0.649672352])
eig例程返回的特征向量是归一化的,使得它们的范数(长度)为 1。 (欧几里得范数被定义为数组成员的平方和的平方根。)我们可以通过使用linalg中的norm例程计算向量的范数来检查这一点:
linalg.norm(x0) # 1.0 - eigenvectors are normalized.
最后,我们可以通过计算乘积A @ x0并检查,直到浮点精度,这等于lambda0*x0,来检查这些值确实满足特征值/特征向量对的定义:
lhs = A @ x0 rhs = lambda0*x0 linalg.norm(lhs - rhs) # 2.8435583831733384e-15 - very small.
这里计算的范数表示方程Ax = λx的左侧lhs和右侧rhs之间的“距离”。由于这个距离非常小(小数点后 0 到 14 位),我们可以相当确信它们实际上是相同的。这不为零的事实可能是由于浮点精度误差。
eig例程是围绕低级 LAPACK 例程的包装器,用于计算特征值和特征向量。找到特征值和特征向量的理论过程是首先通过解方程找到特征值
其中I是适当的单位矩阵,以找到值λ。左侧确定的方程是λ的多项式,称为A的特征多项式。然后可以通过解决矩阵方程找到相应的特征向量
其中λ*[j]*是已经找到的特征值之一。实际上,这个过程有些低效,有替代策略可以更有效地计算特征值和特征向量。
特征值和特征向量的一个关键应用是主成分分析,这是一种将大型复杂数据集减少到更好地理解内部结构的关键技术。
我们只能计算方阵的特征值和特征向量;对于非方阵,该定义没有意义。有一种将特征值和特征值推广到非方阵的称为奇异值的方法。
稀疏矩阵
诸如前面讨论的那样的线性方程组在数学中非常常见,特别是在数学计算中。在许多应用中,系数矩阵将非常庞大,有数千行和列,并且可能来自替代来源而不是简单地手动输入。在许多情况下,它还将是稀疏矩阵,其中大多数条目为 0。
如果矩阵的大多数元素为零,则矩阵是稀疏的。要调用矩阵稀疏,需要为零的确切元素数量并不明确定义。稀疏矩阵可以更有效地表示,例如,只需存储非零的索引(i,j)和值a[i,j]。有整个集合的稀疏矩阵算法,可以在矩阵确实足够稀疏的情况下大大提高性能。
稀疏矩阵出现在许多应用程序中,并且通常遵循某种模式。特别是,解决偏微分方程(PDEs)的几种技术涉及解决稀疏矩阵方程(请参阅第三章,微积分和微分方程),与网络相关的矩阵通常是稀疏的。sparse.csgraph模块中包含与网络(图)相关的稀疏矩阵的其他例程。我们将在第五章中进一步讨论这些内容,处理树和网络。
sparse模块包含几种表示稀疏矩阵存储方式的不同类。存储稀疏矩阵的最基本方式是存储三个数组,其中两个包含表示非零元素的索引的整数,第三个包含相应元素的数据。这是coo_matrix类的格式。然后有压缩列 CSC(csc_matrix)和压缩行 CSR(csr_matrix)格式,它们分别提供了有效的列或行切片。sparse中还有三个额外的稀疏矩阵类,包括dia_matrix,它有效地存储非零条目沿对角线带出现的矩阵。
来自 SciPy 的sparse模块包含用于创建和处理稀疏矩阵的例程。我们使用以下import语句从 SciPy 导入sparse模块:
import numpy as np from scipy import sparse
稀疏矩阵可以从完整(密集)矩阵或其他某种数据结构创建。这是使用特定格式的构造函数来完成的,您希望将稀疏矩阵存储在其中。
例如,我们可以通过使用以下命令将密集矩阵存储为 CSR 格式:
A = np.array([[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]]) sp_A = sparse.csr_matrix(A)
如果您手动生成稀疏矩阵,该矩阵可能遵循某种模式,例如以下三对角矩阵:
在这里,非零条目出现在对角线上以及对角线两侧,并且每行中的非零条目遵循相同的模式。要创建这样的矩阵,我们可以使用sparse中的数组创建例程之一,例如diags,这是一个用于创建具有对角线模式的矩阵的便利例程:
T = sparse.diags([-1, 2, -1], (-1, 0, 1), shape=(5, 5), format="csr")
这将创建矩阵T,并按压缩稀疏行 CSR 格式存储它。第一个参数指定应出现在输出矩阵中的值,第二个参数是相对于应放置值的对角线位置的位置。因此,元组中的 0 索引表示对角线条目,-1 表示在行中对角线的左侧,+1 表示在行中对角线的右侧。shape关键字参数给出了生成的矩阵的维度,format指定了矩阵的存储格式。如果没有使用可选参数提供格式,则将使用合理的默认值。数组T可以使用toarray方法扩展为完整(密集)矩阵:
T.toarray() # array([[ 2, -1, 0, 0, 0], # [-1, 2, -1, 0, 0], # [ 0, -1, 2, -1, 0], # [ 0, 0, -1, 2, -1], # [ 0, 0, 0, -1, 2]])
当矩阵很小时(就像这里一样),稀疏求解例程和通常的求解例程之间的性能差异很小。
一旦矩阵以稀疏格式存储,我们可以使用sparse的linalg子模块中的稀疏求解例程。例如,我们可以使用该模块的spsolve例程来解决矩阵方程。spsolve例程将把矩阵转换为 CSR 或 CSC,如果矩阵不是以这些格式之一提供的话,可能会增加额外的计算时间:
from scipy.sparse import linalg linalg.spsolve(T.tocsr(), np.array([1, 2, 3, 4, 5])) # array([ 5.83333333, 10.66666667, 13.5 , 13.33333333, 9.16666667])
sparse.linalg模块还包含许多可以在 NumPy(或 SciPy)的linalg模块中找到的接受稀疏矩阵而不是完整 NumPy 数组的例程,例如eig和inv。
摘要
Python 提供了对数学的内置支持,包括一些基本的数值类型、算术和基本的数学函数。然而,对于涉及大量数值值数组的更严肃的计算,您应该使用 NumPy 和 SciPy 软件包。NumPy 提供高性能的数组类型和基本例程,而 SciPy 提供了更多用于解方程和处理稀疏矩阵(以及许多其他内容)的特定工具。
NumPy 数组可以是多维的。特别是,二维数组具有矩阵属性,可以使用 NumPy 或 SciPy 的linalg模块(前者是后者的子集)来访问。此外,Python 中有一个特殊的矩阵乘法运算符@,它是为 NumPy 数组实现的。
在下一章中,我们将开始查看一些配方。
进一步阅读
有许多数学教科书描述矩阵和线性代数的基本属性,线性代数是研究向量和矩阵的学科。一个很好的入门文本是Blyth, T. and Robertson, E. (2013). Basic Linear Algebra**. London: Springer London, Limited。
NumPy 和 SciPy 是 Python 数学和科学计算生态系统的一部分,并且有广泛的文档可以从官方网站scipy.org访问。我们将在本书中看到这个生态系统中的几个其他软件包。
有关 NumPy 和 SciPy 在幕后使用的 BLAS 和 LAPACK 库的更多信息可以在以下链接找到:BLAS:www.netlib.org/blas/ 和 LAPACK:www.netlib.org/lapack/。
第三章:使用 Matplotlib 进行数学绘图
绘图是数学中的基本工具。一个好的图可以揭示隐藏的细节,建议未来的方向,验证结果或加强论点。因此,科学 Python 堆栈中拥有一个名为 Matplotlib 的强大而灵活的绘图库并不奇怪。
在本章中,我们将以各种样式绘制函数和数据,并创建完全标记和注释的图。我们将创建三维图,自定义图的外观,使用子图创建包含多个图的图,并直接将图保存到文件中,以供在非交互式环境中运行的应用程序使用。
在本章中,我们将涵盖以下示例:
- 使用 Matplotlib 进行基本绘图
- 更改绘图样式
- 为绘图添加标签和图例
- 添加子图
- 保存 Matplotlib 图
- 表面和等高线图
- 自定义三维图
技术要求
Python 的主要绘图包是 Matplotlib,可以使用您喜欢的软件包管理器(如pip)进行安装:
python3.8 -m pip install matplotlib
这将安装最新版本的 Matplotlib,在撰写本书时,最新版本是 3.2.1。
Matplotlib 包含许多子包,但主要用户界面是matplotlib.pyplot包,按照惯例,它被导入为plt别名。可以使用以下导入语句来实现这一点:
import matplotlib.pyplot as plt
本章中的许多示例还需要 NumPy,通常情况下,它被导入为np别名。
本章的代码可以在 GitHub 存储库的Chapter 02文件夹中找到,网址为github.com/PacktPublishing/Applying-Math-with-Python/tree/master/Chapter%2002。
查看以下视频以查看代码实际操作:bit.ly/2ZOSuhs。
使用 Matplotlib 进行基本绘图
绘图是理解行为的重要部分。通过简单地绘制函数或数据,可以学到很多原本隐藏的东西。在这个示例中,我们将介绍如何使用 Matplotlib 绘制简单的函数或数据。
Matplotlib 是一个非常强大的绘图库,这意味着用它执行简单任务可能会令人畏惧。对于习惯于使用 MATLAB 和其他数学软件包的用户,有一个称为pyplot的基于状态的接口。还有一个面向对象的接口,对于更复杂的绘图可能更合适。pyplot接口是创建基本对象的便捷方式。
准备工作
通常情况下,要绘制的数据将存储在两个单独的 NumPy 数组中,我们将为了清晰起见将它们标记为x和y(尽管在实践中这个命名并不重要)。我们将演示绘制函数的图形,因此我们将生成一组x值的数组,并使用函数生成相应的y值。我们定义将要绘制的函数如下:
def f(x): return x*(x - 2)*np.exp(3 - x)
操作步骤
在我们绘制函数之前,我们必须生成要绘制的x和y数据。如果要绘制现有数据,可以跳过这些命令。我们需要创建一组覆盖所需范围的x值,然后使用函数创建y值:
- NumPy 中的
linspace例程非常适合创建用于绘图的数字数组。默认情况下,它将在指定参数之间创建 50 个等间距点。可以通过提供额外的参数来自定义点的数量,但对于大多数情况来说,50 就足够了。
x = np.linspace(-0.5, 3.0) # 100 values between -0.5 and 3.0
- 一旦我们创建了
x值,就可以生成y值:
y = f(x) # evaluate f on the x points
- 要绘制数据,我们只需要从
pyplot接口调用plot函数,该接口被导入为plt别名。第一个参数是x数据,第二个是y数据。该函数返回一个用于绘制数据的轴对象的句柄:
plt.plot(x, y)
- 这将在新的图形上绘制
y值与x值。如果你在 IPython 中工作或者使用 Jupyter 笔记本,那么图形应该会自动出现;否则,你可能需要调用plt.show函数来使图形出现:
plt.show()
如果使用plt.show,图形应该会出现在一个新窗口中。生成的图形应该看起来像图 2.1中的图形。你的默认绘图颜色可能与你的绘图不同。这是为了增加可见性而更改的默认绘图颜色:
图 2.1:使用 Matplotlib 绘制的函数的图形,没有任何额外的样式参数
我们不会在本章的其他配方中添加这个命令,但是你应该知道,如果你不是在自动渲染图形的环境中工作,比如在 IPython 控制台或 Jupyter Notebook 中,你将需要使用它。
工作原理…
如果当前没有Figure或Axes对象,plt.plot例程会创建一个新的Figure对象,向图形添加一个新的Axes对象,并用绘制的数据填充这个Axes对象。返回一个指向绘制线的句柄列表。每个句柄都是一个Lines2D对象。在这种情况下,这个列表将包含一个单独的Lines2D对象。我们可以使用这个Lines2D对象稍后自定义线的外观(参见更改绘图样式配方)。
Matplotlib 的对象层与较低级别的后端进行交互,后端负责生成图形绘图的繁重工作。plt.show函数发出指令给后端来渲染当前的图形。Matplotlib 可以使用多个后端,可以通过设置MPLBACKEND环境变量、修改matplotlibrc文件,或者在 Python 中调用matplotlib.use并指定替代后端的名称来自定义。
plt.show函数不仅仅是在图形上调用show方法。它还连接到一个事件循环,以正确显示图形。应该使用plt.show例程来显示图形,而不是在Figure对象上调用show方法。
还有更多…
有时候在调用plot例程之前手动实例化一个Figure对象是有用的,例如,强制创建一个新的图形。这个配方中的代码也可以写成如下形式:
fig = plt.figure() # manually create a figure lines = plt.plot(x, y) # plot data
plt.plot例程接受可变数量的位置输入。在前面的代码中,我们提供了两个位置参数,它们被解释为x值和y值(按顺序)。如果我们只提供了一个单一的数组,plot例程会根据数组中的位置绘制数值;也就是说,x值被视为0、1、2等等。我们还可以提供多对数组来在同一坐标轴上绘制多组数据:
x = np.linspace(-0.5, 3.0) lines = plt.plot(x, f(x), x, x**2, x, 1 - x)
前面代码的输出如下:
图 2.2:在 Matplotlib 中使用一次调用 plot 例程产生单个图形上的多个图形
有时候需要创建一个新的图形,并在该图形中显式地创建一组新的坐标轴。实现这一目标的最佳方法是使用pyplot接口中的subplots例程(参见添加子图配方)。这个例程返回一对对象,第一个对象是Figure,第二个对象是Axes:
fig, ax = plt.subplots() l1 = ax.plot(x, f(x)) l2 = ax.plot(x, x**2) l3 = ax.plot(x, 1 - x)
这一系列命令产生了与前面显示的图 2.2中相同的图形。
Matplotlib 除了这里描述的plot例程之外,还有许多其他绘图例程。例如,有一些绘图方法使用不同的比例尺来绘制坐标轴,包括分别使用对数x轴或对数y轴(semilogx或semilogy)或同时使用(loglog)。这些在 Matplotlib 文档中有解释。
更改绘图样式
Matplotlib 绘图的基本样式适用于绘制有序的函数或数据,但对于不按任何顺序呈现的离散数据来说,这种样式就不太合适了。为了防止 Matplotlib 在每个数据点之间绘制线条,我们可以将绘图样式更改为“关闭”线条绘制。在这个示例中,我们将通过向plot方法添加格式字符串参数来为坐标轴上的每条线自定义绘图样式。
准备工作
您需要将数据存储在数组对中。为了演示目的,我们将定义以下数据:
y1 = np.array([1.0, 2.0, 3.0, 4.0, 5.0]) y2 = np.array([1.2, 1.6, 3.1, 4.2, 4.8]) y3 = np.array([3.2, 1.1, 2.0, 4.9, 2.5])
我们将根据数组中的位置(即x坐标将分别为0、1、2、3或4)绘制这些点。
如何做到…
控制绘图样式的最简单方法是使用格式字符串,它作为plot命令中x-y对或plot命令中的ydata 后的可选参数提供。在绘制多组数据时,可以为每组参数提供不同的格式字符串。以下步骤提供了创建新图并在该图上绘制数据的一般过程:
*1. 我们首先使用pyplot中的subplots例程显式创建Figure和Axes对象:
fig, ax = plt.subplots()
- 现在我们已经创建了
Figure和Axes对象,可以使用Axes对象上的plot方法绘制数据。这个方法接受与pyplot中的plot例程相同的参数:
lines = ax.plot(y1, 'o', y2, 'x', y3, '*')
这将使用圆圈标记绘制第一个数据集(y1),使用x标记绘制第二个数据集(y2),使用星号(*)标记绘制第三个数据集(y3)。这个命令的输出显示在图 2.3中。格式字符串可以指定多种不同的标记线和颜色样式。如果我们改为使用pyplot接口中的plot例程,其调用签名与plot方法相同,也是一样的。
图 2.3:绘制三组数据,每组数据使用不同的标记样式绘制
它是如何工作的…
格式字符串有三个可选部分,每个部分由一个或多个字符组成。第一部分控制标记样式,即打印在每个数据点处的符号;第二部分控制连接数据点的线条样式;第三部分控制绘图的颜色。在这个示例中,我们只指定了标记样式,这意味着在相邻数据点之间不会绘制连接线。这对于绘制不需要在点之间进行插值的离散数据非常有用。有四种线条样式参数可用:实线(-);虚线(--);点划线(-.);或点线(:)。格式字符串中只能指定有限数量的颜色;它们是红色、绿色、蓝色、青色、黄色、品红色、黑色和白色。格式字符串中使用的字符是每种颜色的第一个字母(黑色除外),因此相应的字符分别是r、g、b、c、y、m、k和w。
例如,如果我们只想更改标记样式,就像在这个示例中所做的那样,改为加号字符,我们将使用"+"格式字符串。如果我们还想将线条样式更改为点划线,我们将使用"+-."格式字符串。最后,如果我们还希望将标记的颜色更改为红色,我们将使用"+-.r"格式字符串。这些指定符也可以以其他配置提供,例如在标记样式之前指定颜色,但这可能会导致 Matplotlib 解析格式字符串的方式存在歧义。
如果您正在使用 Jupyter 笔记本和subplots命令,则必须在与绘图命令相同的单元格中包含对subplots的调用,否则图形将不会被生成。
还有更多…
plot方法还接受许多关键字参数,这些参数也可以用于控制图的样式。如果同时存在关键字参数和格式字符串参数,则关键字参数优先,并且它们适用于调用绘制的所有数据集。控制标记样式的关键字是marker,线型的关键字是linestyle,颜色的关键字是color。color关键字参数接受许多不同的格式来指定颜色,其中包括 RGB 值作为(r, g, b)元组,其中每个字符都是0到1之间的浮点数,或者是十六进制字符串。可以使用linewidth关键字控制绘制的线的宽度,应该提供一个float值。plot还可以传递许多其他关键字参数;在 Matplotlib 文档中列出了一个列表。这些关键字参数中的许多都有较短的版本,例如c代表color,lw代表linewidth。
例如,我们可以使用以下命令通过在调用plot时使用color关键字参数来设置配方中所有标记的颜色:
ax.plot(y1, 'o', y2, 'x', y3, '*', color="k")
从对plot方法(或plt.plot例程)的调用返回的Line2D对象也可以用于自定义每组数据的外观。例如,可以使用Line2D对象中的set_linestyle方法,使用适当的线型格式字符串设置线型。
可以使用Axes对象上的方法自定义图的其他方面。可以使用Axes对象上的set_xticks和set_yticks方法修改坐标轴刻度,可以使用grid方法配置网格外观。pyplot接口中还有方便的方法,可以将这些修改应用于当前坐标轴(如果存在)。
例如,我们修改轴限制,在x和y方向上的每个0.5的倍数设置刻度,并通过以下命令向图添加网格:
ax.axis([-0.5, 5.5, 0, 5.5]) # set axes ax.set_xticks([0.5*i for i in range(9)]) # set xticks ax.set_yticks([0.5*i for i in range(11)] # set yticks ax.grid() # add a grid
注意我们将限制设置为略大于图的范围。这是为了避免标记放在图窗口的边界上。
如果希望在轴上绘制离散数据而不连接点与线,则scatter绘图例程可能更好。这允许更多地控制标记的样式。例如,可以根据一些额外信息调整标记的大小。
向绘图添加标签和图例
每个图应该有一个标题,并且轴应该被正确标记。对于显示多组数据的图,图例是帮助读者快速识别不同数据集的标记、线条和颜色的好方法。在本示例中,我们将向图添加轴标签和标题,然后添加一个图例来帮助区分不同的数据集。为了保持代码简单,我们将绘制上一个示例中的数据。
如何做…
按照以下步骤向您的图添加标签和图例,以帮助区分它们代表的数据集:
- 我们首先使用以下
plot命令从上一个示例中重新创建图:
fig, ax = plt.subplots() ax = ax.plot(y1, "o-", y2, "x--", y3, "*-.")
- 现在,我们有了一个
Axes对象的引用,我们可以开始通过添加标签和标题来自定义这些轴。可以使用subplots例程创建的ax对象上的set_title、set_xlabel和set_ylabel方法向图中添加标题和轴标签。在每种情况下,参数都是包含要显示的文本的字符串:
ax.set_title("Plot of the data y1, y2, and y3") ax.set_xlabel("x axis label") ax.set_ylabel("y axis label")
在这里,我们使用不同的样式绘制了三个数据集。标记样式与上一个示例中相同,但我们为第一个数据集添加了实线,为第二个数据集添加了虚线,为第三个数据集添加了点划线。
- 要添加图例,我们在
ax对象上调用legend方法。参数应该是一个包含每组数据在图例中的描述的元组或列表:
ax.legend(("data y1", "data y2", "data y3"))
上述一系列命令的结果如下:
图 2.4:使用 Matplotlib 生成的带有轴标签、标题和图例的图
工作原理…
set_title、set_xlabel和set_ylabel方法只是将文本参数添加到Axes对象的相应位置。如前面的代码中调用的legend方法,按照它们添加到图中的顺序添加标签,本例中为y1、y2,然后是y3。
可以提供一些关键字参数给set_title、set_xlabel和set_ylabel方法来控制文本的样式。例如,fontsize关键字可以用来指定标签字体的大小,通常使用pt点度量。还可以通过向例程提供usetex=True来使用 TeX 格式化标签。标签的 TeX 格式化在图 2.5中演示。如果标题或轴标签包含数学公式,这是非常有用的。不幸的是,如果系统上没有安装 TeX,就不能使用usetex关键字参数,否则会导致错误。但是,仍然可以使用 TeX 语法来格式化标签中的数学文本,但这将由 Matplotlib 而不是 TeX 来排版。
我们可以使用fontfamily关键字来使用不同的字体,其值可以是字体的名称或serif、sans-serif或monospace,它将选择适当的内置字体。可以在 Matplotlib 文档中找到matplotlib.text.Text类的完整修饰符列表。
要向图添加单独的文本注释,可以在Axes对象上使用annotate方法。这个例程接受两个参数——要显示的文本作为字符串和注释应放置的点的坐标。这个例程还接受前面提到的样式关键字参数。
添加子图
有时,将多个相关的图放在同一图中并排显示,但不在同一坐标轴上是很有用的。子图允许我们在单个图中生成一个网格的单独图。在这个示例中,我们将看到如何使用子图在单个图上并排创建两个图。
准备工作
您需要将要绘制在每个子图上的数据。例如,我们将在第一个子图上绘制应用于f(x) = x²-1函数的牛顿法的前五个迭代,初始值为x[0] = 2,对于第二个子图,我们将绘制迭代的误差。我们首先定义一个生成器函数来获取迭代:
def generate_newton_iters(x0, number): iterates = [x0] errors = [abs(x0 - 1.)] for _ in range(number): x0 = x0 - (x0*x0 - 1.)/(2*x0) iterates.append(x0) errors.append(abs(x0 - 1.)) return iterates, errors
这个例程生成两个列表。第一个列表包含应用于函数的牛顿法的迭代,第二个包含近似值的误差:
iterates, errors = generate_newton_iters(2.0, 5)
如何做…
以下步骤显示了如何创建包含多个子图的图:
- 我们使用
subplots例程创建一个新的图和每个子图中的所有Axes对象的引用,这些子图在一个行和两个列的网格中排列。我们还将tight_layout关键字参数设置为True,以修复生成图的布局。这并不是严格必要的,但在这种情况下是必要的,因为它产生的结果比默认的更好:
fig, (ax1, ax2) = plt.subplots(1, 2, tight_layout=True) # 1 row, 2 columns
- 一旦创建了
Figure和Axes对象,我们可以通过在每个Axes对象上调用相关的绘图方法来填充图。对于第一个图(显示在左侧),我们在ax1对象上使用plot方法,它与标准的plt.plot例程具有相同的签名。然后我们可以在ax1上调用set_title、set_xlabel和set_ylabel方法来设置标题和x和y标签。我们还通过提供usetex关键字参数来使用 TeX 格式化轴标签;如果您的系统上没有安装 TeX,可以忽略这一点:
ax1.plot(iterates, "x") ax1.set_title("Iterates") ax1.set_xlabel("$i$", usetex=True) ax1.set_ylabel("$x_i$", usetex=True)
- 现在,我们可以使用
ax2对象在第二个图上(显示在右侧)绘制错误值。我们使用了一种使用对数刻度的替代绘图方法,称为semilogy。该方法的签名与标准的plot方法相同。同样,我们设置了轴标签和标题。如果没有安装 TeX,可以不使用usetex。
ax2.semilogy(errors, "x") # plot y on logarithmic scale ax2.set_title("Error") ax2.set_xlabel("$i$", usetex=True) ax2.set_ylabel("Error")
这些命令序列的结果如下图所示:
图 2.5:Matplotlib 子图
左侧绘制了牛顿法的前五次迭代,右侧是以对数刻度绘制的近似误差。
工作原理…
Matplotlib 中的Figure对象只是一个特定大小的绘图元素(如Axes)的容器。Figure对象通常只包含一个Axes对象,该对象占据整个图形区域,但它可以在相同的区域中包含任意数量的Axes对象。subplots例程执行几项任务。首先创建一个新的图形,然后在图形区域内创建一个指定形状的网格。然后,在网格的每个位置添加一个新的Axes对象。然后将新的Figure对象和一个或多个Axes对象返回给用户。如果请求单个子图(一行一列,没有参数),则返回一个普通的Axes对象。如果请求单行或单列(分别具有多于一个列或行),则返回Axes对象的列表。如果请求多行和多列,则将返回一个列表的列表,其中行由填充有Axes对象的内部列表表示。然后我们可以使用每个Axes对象上的绘图方法来填充图形以显示所需的绘图。
在本示例中,我们在左侧使用了标准的plot方法,就像我们在以前的示例中看到的那样。但是,在右侧绘图中,我们使用了一个将y轴更改为对数刻度的绘图。这意味着y轴上的每个单位代表 10 的幂的变化,而不是一个单位的变化,因此0代表 10⁰=1,1代表 10,2代表 100,依此类推。轴标签会自动更改以反映这种比例变化。当值按数量级变化时,例如近似误差随着迭代次数的增加而变化时,这种缩放是有用的。我们还可以使用semilogx方法仅对x使用对数刻度进行绘制,或者使用loglog方法对两个轴都使用对数刻度进行绘制。
还有更多…
在 Matplotlib 中有几种创建子图的方法。如果已经创建了一个Figure对象,则可以使用Figure对象的add_subplot方法添加子图。或者,您可以使用matplotlib.pyplot中的subplot例程将子图添加到当前图。如果尚不存在,则在调用此例程时将创建一个新的图。subplot例程是Figure对象上add_subplot方法的便利包装。
要创建一个具有一个或多个子图的新图形,还可以使用pyplot接口中的subplots例程(如更改绘图样式中所示),它返回一个新的图形对象和一个Axes对象的数组,每个位置一个。这三种方法都需要子图矩阵的行数和列数。add_subplot方法和subplot例程还需要第三个参数,即要修改的子图的索引。返回当前子图的Axes对象。
在前面的例子中,我们创建了两个具有不同比例的y轴的图。这展示了子图的许多可能用途之一。另一个常见用途是在矩阵中绘制数据,其中列具有共同的x标签,行具有共同的y标签,这在多元统计中特别常见,用于研究各组数据之间的相关性。用于创建子图的plt.subplots例程接受sharex和sharey关键字参数,允许轴在所有子图或行或列之间共享。此设置会影响轴的比例和刻度。
另请参阅
Matplotlib 通过为subplots例程提供gridspec_kw关键字参数来支持更高级的布局。有关更多信息,请参阅matplotlib.gridspec的文档。
保存 Matplotlib 图
当您在交互式环境中工作,例如 IPython 控制台或 Jupyter 笔记本时,运行时显示图是完全正常的。但是,有很多情况下,直接将图存储到文件中而不是在屏幕上呈现会更合适。在本示例中,我们将看到如何将图直接保存到文件中,而不是在屏幕上显示。
准备工作
您需要要绘制的数据以及要存储输出的路径或文件对象。我们将结果存储在当前目录中的savingfigs.png中。在此示例中,我们将绘制以下数据:
x = np.arange(1, 5, 0.1) y = x*x
如何做…
以下步骤显示了如何将 Matplotlib 图直接保存到文件:
- 第一步是像往常一样创建图,并添加任何必要的标签、标题和注释。图将以其当前状态写入文件,因此应在保存之前进行对图的任何更改:
fig, ax = plt.subplots() ax.plot(x, y) ax.set_title("Graph of $y = x²$", usetex=True) ax.set_xlabel("$x$", usetex=True) ax.set_ylabel("$y$", usetex=True)
- 然后,我们使用
fig上的savefig方法将此图保存到文件。唯一必需的参数是要输出的路径或可以写入图形的文件对象。我们可以通过提供适当的关键字参数来调整输出格式的各种设置,例如分辨率。我们将输出图的每英寸点数(DPI)设置为300,这对于大多数应用程序来说是合理的分辨率:
fig.savefig("savingfigs.png", dpi=300)
Matplotlib 将根据给定文件的扩展名推断我们希望以便携式网络图形(PNG)格式保存图像。或者,可以通过提供关键字参数(使用format关键字)显式地提供格式,或者可以从配置文件中回退到默认格式。
它是如何工作的…
savefig方法选择适合输出格式的后端,然后以该格式呈现当前图。生成的图像数据将写入指定的路径或文件对象。如果您手动创建了Figure实例,则可以通过在该实例上调用savefig方法来实现相同的效果。
还有更多…
savefig例程接受许多额外的可选关键字参数来自定义输出图像。例如,可以使用dpi关键字指定图像的分辨率。本章中的图是通过将 Matplotlib 图保存到文件中生成的。
可用的输出格式包括 PNG、可缩放矢量图形(SVG)、PostScript(PS)、Encapsulated PostScript(EPS)和便携式文档格式(PDF)。如果安装了 Pillow 软件包,还可以保存为 JPEG 格式,但自 Matplotlib 3.1 版本以来就不再原生支持此功能。JPEG 图像还有其他自定义关键字参数,如quality和optimize。可以将图像元数据的字典传递给metadata关键字,在保存时将其写入图像元数据。
另请参阅
Matplotlib 网站上的示例库包括使用几种常见的 Python GUI 框架将 Matplotlib 图嵌入到图形用户界面(GUI)应用程序中的示例。
Python 数学应用(一)(3)https://developer.aliyun.com/article/1506374