什么是 NumPy?
NumPy是Python中科学计算的基础包。它是一个Python库,提供多维数组对象,各种派生对象(如掩码数组和矩阵),以及用于数组快速操作的各种API,有包括数学、逻辑、形状操作、排序、选择、输入输出、离散傅立叶变换、基本线性代数,基本统计运算和随机模拟等等。
NumPy包的核心是 ndarray 对象。它封装了python原生的同数据类型的 n 维数组,为了保证其性能优良,其中有许多操作都是代码在本地进行编译后执行的。
NumPy数组 和 原生Python Array(数组)之间有几个重要的区别:
- NumPy 数组在创建时具有固定的大小,与Python的原生数组对象(可以动态增长)不同。更改ndarray的大小将创建一个新数组并删除原来的数组。
- NumPy 数组中的元素都需要具有相同的数据类型,因此在内存中的大小相同。 例外情况:Python的原生数组里包含了NumPy的对象的时候,这种情况下就允许不同大小元素的数组。
- NumPy 数组有助于对大量数据进行高级数学和其他类型的操作。通常,这些操作的执行效率更高,比使用Python原生数组的代码更少。
- 越来越多的基于Python的科学和数学软件包使用NumPy数组; 虽然这些工具通常都支持Python的原生数组作为参数,但它们在处理之前会还是会将输入的数组转换为NumPy的数组,而且也通常输出为NumPy数组。换句话说,为了高效地使用当今科学/数学基于Python的工具(大部分的科学计算工具),你只知道如何使用Python的原生数组类型是不够的 - 还需要知道如何使用 NumPy 数组。
关于数组大小和速度的要点在科学计算中尤为重要。举一个简单的例子,考虑将1维数组中的每个元素与相同长度的另一个序列中的相应元素相乘的情况。如果数据存储在两个Python 列表 a
和 b
中,我们可以迭代每个元素,如下所示:
c = []
for i in range(len(a)):
c.append(a[i]*b[i])
确实符合我们的要求,但如果a
和b
每一个都包含数以百万计的数字,我们会付出Python中循环的效率低下的代价。我们可以通过在C中写入以下代码,更快地完成相同的任务(为了清楚起见,我们忽略了变量声明和初始化,内存分配等)。
for (i = 0; i < rows; i++): {
c[i] = a[i]*b[i];
}
这节省了解释Python代码和操作Python对象所涉及的所有开销,但牺牲了用Python编写代码所带来的好处。此外,编码工作需要增加的维度,我们的数据。例如,对于二维数组,C代码(如前所述)会扩展为这样:
for (i = 0; i < rows; i++): {
for (j = 0; j < columns; j++): {
c[i][j] = a[i][j]*b[i][j];
}
}
NumPy 为我们提供了两全其美的解决方案:当涉及到 ndarray 时,逐个元素的操作是“默认模式”,但逐个元素的操作由预编译的C代码快速执行。在NumPy中:
c = a * b
以近C速度执行前面的示例所做的事情,但是我们期望基于Python的代码具有简单性。的确,NumPy的语法更为简单!最后一个例子说明了NumPy的两个特征,它们是NumPy的大部分功能的基础:矢量化和广播。
为什么 NumPy 这么快?
矢量化描述了代码中没有任何显式的循环,索引等 - 这些当然是预编译的C代码中“幕后”优化的结果。矢量化代码有许多优点,其中包括:
- 矢量化代码更简洁,更易于阅读
- 更少的代码行通常意味着更少的错误
- 代码更接近于标准的数学符号(通常,更容易正确编码数学结构)
- 矢量化导致产生更多 “Pythonic” 代码。如果没有矢量化,我们的代码就会被低效且难以阅读的
for
循环所困扰。
广播是用于描述操作的隐式逐元素行为的术语; 一般来说,在NumPy中,所有操作,不仅仅是算术运算,而是逻辑,位,功能等,都以这种隐式的逐元素方式表现,即它们进行广播。此外,在上面的例子中,a
并且b
可以是相同形状的多维数组,或者标量和数组,或者甚至是具有不同形状的两个数组,条件是较小的数组可以“扩展”到更大的形状。结果广播明确无误的方式。有关广播的详细“规则”,请参阅numpy.doc.broadcasting
。
还有谁在使用 NumPy?
NumPy完全支持面向对象的方法,我们再夸奖一次 ndarray 。 我们知道 ndarray 是一个类,拥有许多方法和属性。它的许多方法都由最外层的NumPy命名空间中的函数镜像,允许程序员在他们喜欢的范例中进行编码。这种灵活性使NumPy数组方言和NumPy ndarray 类成为在Python中使用的多维数据交换的 首选 对象。