SciPy

常见问题解答

关于 NumPy 的一般问题

什么是 NumPy?

NumPy 是一个 Python 扩展模块,它提供对同类数据数组的有效操作。它允许 Python 作为一种高级语言来操作数值数据,就像 IDL 或 MATLAB 一样。

为什么我应该使用 NumPy 而不是 IDL、MATLAB 或 Octave?

与往常一样,您应该选择适合您的问题和环境的编程工具。许多人提到的优点是它是开源的,它不花任何钱,它使用通用编程语言(Python),Python 非常流行,并且几乎可以为任何任务提供高质量的库,而且它相对容易连接现有的 C 和 Fortran 代码到 Python 解释器。

什么是 NumPy 数组?

NumPy 数组是相同类型对象的 多维数组。在内存中,它是一个指向内存块的对象,跟踪存储在该内存中的数据类型,跟踪有多少个维度以及每个维度的大小,以及 - 重要的是 - 沿每个轴的元素之间的间距。

例如,您可能有一个 NumPy 数组,它表示从零到九的数字,存储为 32 位整数,一个接一个,在一个内存块中。(相比之下,每个 Python 整数都需要在它旁边存储一些类型信息。)您可能还拥有从零到八的偶数数组,存储在同一个内存块中,但元素之间有四个字节(一个 32 位整数)的间隙。这称为 **步长**,这意味着您通常可以创建一个新的数组,引用数组中元素的子集,而无需复制任何数据。此类子集称为 **视图**。这显然是一种效率提升,但它也允许以各种方式修改数组中选定元素。

NumPy 数组的一个重要约束是,对于给定的轴,所有元素在内存中的间距必须相同。NumPy 无法使用双重间接寻址来访问数组元素,因此需要此类寻址模式的索引模式必须生成副本。此约束使得 NumPy 内部所有内部循环都可以用高效的 C 代码编写。

NumPy 数组提供了许多其他可能性,包括使用内存映射磁盘文件作为数组的存储空间,以及 **记录数组**,其中每个元素都可以具有自定义的复合数据类型。

NumPy 数组相对于(嵌套的)Python 列表有哪些优势?

Python 的列表是高效的通用容器。它们支持(相当)高效的插入、删除、追加和连接,而 Python 的列表推导式使它们易于构建和操作。但是,它们也有一些局限性:它们不支持“向量化”操作,例如逐元素加法和乘法,而且它们可以包含不同类型对象的这一事实意味着 Python 必须为每个元素存储类型信息,并且在操作每个元素时必须执行类型分派代码。这也意味着很少有列表操作可以通过高效的 C 循环执行——每次迭代都需要类型检查和其他 Python API 簿记。

Numeric、numarray 和 NumPy 背后的故事是什么?

简而言之,Numeric 是最初为 Python 提供高效同质数值数组的软件包,但一些开发者认为它缺乏某些基本功能,因此他们开始开发一个名为 numarray 的独立实现。拥有两个不兼容的数组实现显然是一场灾难,因此 NumPy 被设计为对两者进行改进。

Numeric 和 numarray 目前都不再支持。NumPy 已经成为多年来的标准数组软件包。如果您使用 Numeric 或 numarray,您应该升级;NumPy 被明确设计为拥有两者所有功能(并且已经拥有其前身软件包中没有的新功能)。有一些工具可以简化升级过程;只有 C 代码需要进行大量修改。

关于 SciPy 的一般问题

什么是 SciPy?

SciPy 是一套针对 Python 的开源(BSD 许可)科学和数值工具。它目前支持特殊函数、积分、常微分方程 (ODE) 求解器、梯度优化、并行编程工具、用于快速执行的表达式到 C++ 编译器等等。一个好的经验法则是,如果它在数值计算的通用教科书(例如,著名的 Numerical Recipes 系列)中有所介绍,它可能在 SciPy 中实现。

它多少钱?

SciPy 可免费使用。它作为开源软件发布,这意味着您可以完全访问源代码,并可以以其宽松的 BSD 许可证允许的任何方式使用它。

SciPy 的许可条款是什么?

SciPy 的许可证根据 BSD 许可证的条款,可用于商业和非商业用途,此处

如果 SciPy 是用 Python 这样的解释型语言编写的,它怎么能很快呢?

实际上,时间关键的循环通常是用 C、C++ 或 Fortran 实现的。SciPy 的部分代码是建立在 http://www.netlib.org/ 上的科学例程之上的薄层代码。Netlib 是一个巨大的存储库,包含用 C 和 Fortran 编写的极其宝贵且健壮的科学算法。重新编写这些算法是愚蠢的,而且需要数年时间才能调试它们。SciPy 使用各种方法来生成这些算法的“包装器”,以便它们可以在 Python 中使用。一些包装器是通过手工用 C 代码生成的。其余的则是使用 SWIG 或 f2py 生成的。SciPy 中的一些较新的贡献要么完全是用 Cython 编写的,要么是用它包装的。

第二个答案是,对于困难的问题,更好的算法可以极大地减少解决问题所需的时间。因此,使用 SciPy 的内置算法可能比用 C 编写的简单算法快得多。

我发现了一个错误。我该怎么办?

SciPy 开发团队努力使 SciPy 尽可能可靠,但与任何软件产品一样,错误确实会发生。如果您发现影响您软件的错误,请通过在 SciPy 错误跟踪器NumPy 错误跟踪器 中输入票证来告诉我们,具体取决于情况。

我如何参与 SciPy?

您可以通过邮件联系我们邮件列表。我们热烈欢迎更多人参与代码编写、单元测试、文档编写(包括翻译成其他语言)以及网站维护等工作。

是否有商业支持?

是的,SciPy 的商业支持由多家公司提供,例如 AnacondaEnthoughtQuansight

NumPy 与 SciPy 与其他包的比较

NumPy 和 SciPy 之间的区别是什么?

在理想情况下,NumPy 只包含数组数据类型和最基本的操作:索引、排序、重塑、基本逐元素函数等。所有数值代码都应该放在 SciPy 中。然而,NumPy 的重要目标之一是兼容性,因此 NumPy 试图保留其任何一个前身支持的所有功能。因此,NumPy 包含一些线性代数函数和傅里叶变换,尽管这些函数更适合放在 SciPy 中。无论如何,SciPy 包含更完整版本的线性代数模块,以及许多其他数值算法。如果您使用 Python 进行科学计算,您可能需要同时安装 NumPy 和 SciPy。大多数新功能都属于 SciPy 而不是 NumPy。

如何使用 NumPy/SciPy 绘制图表?

绘图功能超出了 NumPy 和 SciPy 的范围,它们专注于数值对象和算法。有几个包与 NumPy 和 Pandas 紧密集成,可以生成高质量的图表,例如非常受欢迎的 Matplotlib。其他流行的选择包括 BokehPlotlyAltairChaco

如何使用 NumPy/SciPy 创建 3D 图表/可视化?

与 2D 绘图一样,3D 图形也超出了 NumPy 和 SciPy 的范围,但与 2D 情况一样,也有一些包与 NumPy 集成。 Matplotlibmplot3d 子包中提供了基本的 3D 绘图功能,而 Mayavi 提供了广泛的高质量 3D 可视化功能,利用强大的 VTK 引擎。

为什么同时使用 numpy.linalgscipy.linalg?它们有什么区别?

scipy.linalg 是对 Fortran LAPACK 的更完整封装,使用 f2py

NumPy 的设计目标之一是在没有 Fortran 编译器的情况下构建,如果您没有 LAPACK,NumPy 将使用自己的实现。SciPy 需要 Fortran 编译器才能构建,并且严重依赖于封装的 Fortran 代码。

NumPy 和 SciPy 中的 linalg 模块有一些共同的功能,但具有不同的文档字符串,并且 scipy.linalg 包含 numpy.linalg 中没有的功能,例如与 LU 分解舒尔分解 相关的函数,计算伪逆的多种方法,以及矩阵超越函数,例如 矩阵对数。在两个模块中都存在的某些函数在 scipy.linalg 中具有增强的功能;例如,scipy.linalg.eig() 可以接受第二个矩阵参数来解决 广义特征值问题

Python 版本支持

NumPy 和 SciPy 是否支持 Python 2.7?

支持 Python 2.7 的最后一个 NumPy 版本是 NumPy 1.16.x。支持 Python 2.7 的最后一个 SciPy 版本是 SciPy 1.2.x。支持 Python 3.x 的第一个 NumPy 版本是 NumPy 1.5.0。SciPy 中的 Python 3 支持是在 SciPy 0.9.0 中引入的。

NumPy/SciPy 是否与 PyPy 兼容?

一般来说,是的。最近对 PyPy 的改进使科学 Python 堆栈能够与 PyPy 协同工作。NumPy 和 SciPy 项目在持续集成中运行 PyPy,并旨在随着时间的推移进一步改进支持。由于 NumPy 和 SciPy 的大部分代码是用 C 扩展模块实现的,因此代码可能不会运行得更快(在大多数情况下,它仍然明显更慢,但是 PyPy 正在积极努力改进这一点)。与往常一样,在进行基准测试时,您的经验是最好的指南。

NumPy/SciPy 是否与 Jython 或 C#/.NET 兼容?

不支持。Jython 从未工作过,因为它运行在 Java 虚拟机之上,并且无法与为标准 Python(CPython)解释器编写的 C 扩展进行交互。

几年前,有人努力使 NumPy 和 SciPy 与 .NET 兼容。当时的一些用户报告称,在 32 位 Windows 上使用 Ironclad 成功使用 NumPy。

基本的 NumPy/SciPy 用法

检查空(零元素)数组的最佳方法是什么?

如果您确定一个变量是数组,则使用 size 属性。如果变量是列表或其他序列类型,则使用 len()。size 属性比 len 更可取,因为

>>> a = numpy.zeros((1,0))
>>> a.size
0

>>> len(a)
1

我想从文本文件加载数据。如何使这段代码更高效?

使用 numpy.loadtxt()。即使您的文本文件包含标题和页脚行或注释,loadtxt 也几乎可以肯定可以读取它;它方便且高效。

如果您发现这仍然太慢,您可以尝试使用 pandas(它有一个更快的 csv 读取器,例如)。如果这没有帮助,您应该考虑更改为比纯文本更有效的文件格式。有很多替代方案,具体取决于您的需求(以及您使用的 NumPy/SciPy 版本)

  • 文本文件:慢、庞大、可移植、人类可读;内置于 NumPy

  • pickle:有点慢,有点可移植(可能与不同的 NumPy 版本不兼容);内置于 NumPy

  • HDF5:功能强大的多功能格式;PyTablesh5py 都在用 C 编写的核心 HDF5 库之上提供了 NumPy 友好的接口

  • FITS:天文学中标准的多功能格式;astropy 库通过其 io.fits 包提供了方便的 Python 接口

  • .npy: NumPy 原生二进制数据格式,简单、高效、可移植;从 1.0.5 版本开始内置于 NumPy 中。

矩阵和数组有什么区别?

注意:NumPy 矩阵将被弃用,不要在新的代码中使用它们。以下答案的其余部分出于历史原因保留。

NumPy 的基本数据类型是多维数组。它们可以是 1-D(即一个索引,如列表或向量),2-D(两个索引,如图像),3-D 或更多(0-D 数组存在,并且是稍微奇怪的边缘情况)。它们支持各种操作,包括加法、减法、乘法、求幂等等 - 但所有这些都是逐元素操作。如果您想要两个 2-D 数组之间的矩阵乘法,函数 numpy.dot() 或内置 Python 运算符 @ 可以做到这一点。它也适用于获取 2-D 数组和 1-D 数组的矩阵乘积,无论哪种方向,或者两个 1-D 数组。如果您想要对更高维数组(张量收缩)进行某种矩阵乘法类操作,您需要考虑要收缩哪些索引。tensordot()rollaxis() 的某种组合应该可以满足您的需求。

但是,一些用户发现他们做了很多矩阵乘法,总是不得不写 dot 作为前缀太麻烦了,或者他们真的想将行向量和列向量分开。对于这些用户,有一个矩阵类。这只是一个围绕数组的透明包装器,它强制数组至少为 2-D,并且重载了乘法和求幂运算。乘法变为矩阵乘法,求幂变为矩阵求幂。如果您想要逐元素乘法,请使用 numpy.multiply()

函数 asmatrix() 将数组转换为矩阵(不复制任何数据);asarray() 将矩阵转换为数组。 asanyarray() 确保结果是矩阵或数组(而不是列表)。不幸的是,NumPy 的许多函数中有一些使用 asarray(),而应该使用 asanyarray(),因此,有时您可能会发现您的矩阵意外地被转换为数组。只需对这些操作的输出使用 asmatrix(),并考虑提交错误报告。

为什么不为矩阵乘法使用单独的运算符?

从 Python 3.5 开始,@ 符号将被定义为矩阵乘法运算符,NumPy 和 SciPy 将使用它。此添加是 PEP 465 的主题。单独的矩阵和数组类型存在是为了解决早期版本的 Python 中缺少此运算符的问题。

如何查找数组中满足某个条件的元素的索引?

首选的做法是使用 numpy.nonzero() 函数或数组的 nonzero() 方法。给定一个数组 a,条件 a > 3 返回一个布尔数组,由于 False 在 Python 和 NumPy 中被解释为 0,因此 np.nonzero(a > 3) 会产生 a 中满足条件的元素的索引。

>>> import numpy as np
>>> a = np.array([[1,2,3],[4,5,6],[7,8,9]])
>>> a > 3
array([[False, False, False],
       [ True,  True,  True],
       [ True,  True,  True]], dtype=bool)
>>> np.nonzero(a > 3)
(array([1, 1, 1, 2, 2, 2]), array([0, 1, 2, 0, 1, 2]))

布尔数组的 nonzero() 方法也可以被调用。

>>> (a > 3).nonzero()
(array([1, 1, 1, 2, 2, 2]), array([0, 1, 2, 0, 1, 2]))

如何计算整数数组中每个值出现的次数?

使用 numpy.bincount()。结果数组是

>>> arr = numpy.array([0, 5, 4, 0, 4, 4, 3, 0, 0, 5, 2, 1, 1, 9])
>>> numpy.bincount(arr)

bincount() 的参数必须由正整数或布尔值组成。不支持负整数。

高级 NumPy 使用

NumPy 是否支持 nan

nan,缩写为“非数字”,是 IEEE-754 规范定义的特殊浮点值,与 inf(无穷大)和其他值和行为一起。理论上,IEEE nan 是专门为解决缺失值问题而设计的,但现实情况是,不同的平台表现不同,使生活更加困难。在某些平台上,nan 的存在会使计算速度降低 10-100 倍。对于整数数据,不存在 nan 值。

尽管存在所有这些问题,NumPy(和 SciPy)仍然努力支持 IEEE-754 行为(基于 NumPy 的前身 numarray)。最重大的挑战是 Python 本身缺乏跨平台支持。由于 NumPy 是为了利用支持 IEEE-754 的 C99 而编写的,它可以在内部绕过这些问题,但用户在 Python 解释器中比较值时仍然可能遇到问题。

那些希望避免潜在头痛的人会对另一种解决方案感兴趣,这种解决方案在 NumPy 的前身中有着悠久的历史——**掩码数组**。掩码数组是标准数组,具有相同形状的第二个“掩码”数组,用于指示值是否存在或缺失。掩码数组是 numpy.ma 模块的领域,并延续了跨平台的 Numeric/numarray 传统。例如,请参见“Cookbook/Matplotlib/Plotting values with masked arrays”(TODO),以避免在 Matplotlib 中绘制缺失数据。尽管它们需要额外的内存,但掩码数组在许多浮点单元上比 nans 更快。另请参见 NumPy 文档中的掩码数组

另一个不错的选择是使用 pandas - 它以类似于 NumPy 的方式使用 nan 来处理浮点数据,并且从 pandas 0.25.0 开始也支持缺失的整数值。

为什么 A[[0, 1, 1, 2]] += 1 没有按照我的预期执行?

这个问题在 邮件列表 上时不时会出现。参见 这里 的一个详细讨论。

>>> A = numpy.zeros(3)
>>> A[[0,1,1,2]] += 1
>>> A
array([ 1.,  1.,  1.])

人们可能会合理地预期 A 包含 [1,2,1]。不幸的是,NumPy 中的实现并非如此。此外,Python 参考手册 指定

>>> x = x + y

以及

>>> x += y

应该导致 x 具有相同的值(尽管不一定具有相同的标识)。此外,即使 NumPy 开发人员想要修改此行为,Python 也不提供可重载的 __indexed_iadd__() 函数;代码的行为类似于

>>> tmp = A.__getitem__([0,1,1,2])
>>> tmp.__iadd__(1)
>>> A.__setitem__([0,1,1,2],tmp)

这会导致其他一些特殊情况;如果索引操作实际上能够提供视图而不是副本,则 __iadd__() 会写入数组,然后将视图复制到数组中,因此数组会被写入两次。

但是,不要绝望!NumPy 包含用于此类行为的功能,可以通过使用 ufunc at() 来获得,它是矩阵和标量之间的加法 (np.add)、减法 (np.subtract)、乘法 (np.multiply) 和除法 (np.divide) ufunc 的属性

>>> A = numpy.zeros(3)
>>> numpy.add.at(A, [0, 1, 1, 2], 1)
>>> A
array([ 1.,  2.,  1.])

在哪里寻求帮助

您可以在 StackOverflow 上使用 SciPy 标签 或在 scipy-user 邮件列表 上提问。首先搜索答案,因为可能有人已经找到了解决您问题的方案,使用它将节省每个人的时间。

目录

搜索