1.NumPy 介绍
1.1 NumPy 演变史
在 NumPy 之前,有两个 Python 数组包:
-
Numeric 包
Numeric 包开发于 20 世纪 90 年代中期,在 Python 中提供了数组对象和数组感知函数。它由 C 语言编写,并与线性代数的标准快速实现相链接。它最早的用途之一是引导 C++ 应用程序,用于劳伦斯利弗莫尔国家实验室(Lawrence Livermore National Laboratory)的惯性约束聚变研究。
-
Numarray 包
为了处理来自哈勃太空望远镜的大型天文图像,Numeric 的重新实现(称为 Numarray)增加了对结构化数组、灵活索引、内存映射、字节序变体、更高效的内存使用、灵活的 IEEE 754 标准错误处理能力以及更好的类型转换规则的支持。尽管 Numarray 与 Numeric 高度兼容,但这两个软件包之间的差异足以导致社区分裂;
2005 年,NumPy 作为 "两全其美 "的统一出现了——将 Numarray 的功能与 Numeric 的小数组性能及其丰富的 C API 相结合。
15 年后的今天,NumPy 几乎支撑了所有进行科学或数值计算的 Python 库,包括 SciPy、Matplotlib、pandas、scikit-learn 和 scikit-image。NumPy 是一个社区开发的开源库,它提供了一个多维 Python 数组对象,以及可对其进行操作的数组感知函数。由于其固有的简单性,NumPy 数组是 Python 中数组数据事实上的交换格式。
NumPy 使用中央处理器(CPU)操作内存内数组(in-memory arrays)。为了利用现代的专用存储和硬件,Python 数组软件包最近大量涌现。与 Numarray-Numeric 之分不同的是,考虑到已经有大量工作建立在 NumPy 之上,这些新库现在更难分裂用户社区。不过,为了让社区能够使用新的探索性技术,NumPy 正在向一个中央协调机制过渡,该机制指定了一个定义明确的数组编程 API,并根据需要将其分派给专门的数组实现。
1.2 NumPy 核心
NumPy包的核心是 ndarray 对象。它封装了python原生的同数据类型的 n 维数组,但 NumPy数组 和 原生Python Array(数组)之间有几个重要的区别:
-
NumPy 数组在创建时具有固定的大小,与Python的原生数组对象(可以动态增长)不同。更改ndarray的大小将创建一个新数组并删除原来的数组。
-
NumPy 数组中的元素都需要具有相同的数据类型,因此在内存中的大小相同。 例外情况:Python 的原生数组里包含了 NumPy 的对象的时候,这种情况下就允许不同大小元素的数组。
-
NumPy 数组有助于对大量数据进行高级数学和其他类型的操作。通常,这些操作的执行效率更高,比使用Python原生数组的代码更少。
-
越来越多的基于 Python 的科学和数学软件包使用 NumPy 数组; 虽然这些工具通常都支持 Python 的原生数组作为参数,但它们在处理之前会还是会将输入的数组转换为 NumPy 的数组,而且也通常输出为 NumPy 数组。换句话说,为了高效地使用当今科学/数学基于 Python 的工具(大部分的科学计算工具),你只知道如何使用 Python 的原生数组类型是不够的——还需要知道如何使用 NumPy 数组。
1.3 NumPy 为什么这么快?
例如矩阵运算中常见的逐元素相乘:
-
若是使用 Python 的循环实现,则会付出Python中循环的效率低下的代价。
-
而若是通过 C 的循环更快地完成相同的任务,则节省了解释 Python 代码和操作 Python 对象所涉及的所有开销,但牺牲了用 Python 编写代码所带来的好处。
因此我们的目标应该是以近 C 速度执行前面的示例所做的事情,但是我们期望基于 Python 的代码具有简单性。
NumPy 提供了两全其美的解决方案:当涉及到 ndarray 时,逐个元素的操作是“默认模式”,但逐个元素的操作由预编译的C代码快速执行。
NumPy 有两个特征,它们是 NumPy 的大部分功能的基础:
-
矢量化:
对整个数组而不是单个元素进行操作是数组编程的关键。这意味着在 C 语言中需要几十行才能表达的操作,往往可以用一个简单明了的 Python 表达式来实现。这使得代码更加简洁,用户可以专注于分析细节,而 NumPy 对数组元素的循环处理也接近最优,例如,将跨步考虑在内,以充分利用计算机的高速缓冲存储器。
-
广播:
在两个形状相同的数组上执行矢量化操作(如加法)时很简单。通过 "广播",NumPy 允许维数不同,并产生符合直觉的结果。简单而言,依托广播机制可以实现直接向数组中添加一个标量值,但广播还能推广到更复杂的例子,例如缩放数组的每一列或生成坐标网格。在广播中,一个数组或两个数组实际上仅为虚拟复制(即不在内存中复制任何数据),这样操作数的形状就会匹配。当使用索引数组对数组进行索引时,也会应用广播。