常见问题解答¶
关于 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?¶
您可以通过邮件联系我们邮件列表。我们热烈欢迎更多人参与代码编写、单元测试、文档编写(包括翻译成其他语言)以及网站维护等工作。
是否有商业支持?¶
NumPy 与 SciPy 与其他包的比较¶
NumPy 和 SciPy 之间的区别是什么?¶
在理想情况下,NumPy 只包含数组数据类型和最基本的操作:索引、排序、重塑、基本逐元素函数等。所有数值代码都应该放在 SciPy 中。然而,NumPy 的重要目标之一是兼容性,因此 NumPy 试图保留其任何一个前身支持的所有功能。因此,NumPy 包含一些线性代数函数和傅里叶变换,尽管这些函数更适合放在 SciPy 中。无论如何,SciPy 包含更完整版本的线性代数模块,以及许多其他数值算法。如果您使用 Python 进行科学计算,您可能需要同时安装 NumPy 和 SciPy。大多数新功能都属于 SciPy 而不是 NumPy。
如何使用 NumPy/SciPy 绘制图表?¶
绘图功能超出了 NumPy 和 SciPy 的范围,它们专注于数值对象和算法。有几个包与 NumPy 和 Pandas 紧密集成,可以生成高质量的图表,例如非常受欢迎的 Matplotlib。其他流行的选择包括 Bokeh、Plotly、Altair 和 Chaco。
如何使用 NumPy/SciPy 创建 3D 图表/可视化?¶
与 2D 绘图一样,3D 图形也超出了 NumPy 和 SciPy 的范围,但与 2D 情况一样,也有一些包与 NumPy 集成。 Matplotlib 在 mplot3d
子包中提供了基本的 3D 绘图功能,而 Mayavi 提供了广泛的高质量 3D 可视化功能,利用强大的 VTK 引擎。
为什么同时使用 numpy.linalg
和 scipy.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 矩阵将被弃用,不要在新的代码中使用它们。以下答案的其余部分出于历史原因保留。
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 邮件列表 上提问。首先搜索答案,因为可能有人已经找到了解决您问题的方案,使用它将节省每个人的时间。