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
命令中的y
data 后的可选参数提供。在绘制多组数据时,可以为每组参数提供不同的格式字符串。以下步骤提供了创建新图并在该图上绘制数据的一般过程:
*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