什么是 NumPy?#

NumPy 是 Python 中科学计算的基本包.它是一个 Python 库,提供了一个多维数组对象,各种派生对象(如掩码数组和矩阵),以及一系列用于快速操作数组的例程,包括数学、逻辑、形状操作、排序、选择、I/O、离散傅里叶变换、基本线性代数、基本统计操作、随机模拟等等.

在 NumPy 包的核心,是 ndarray 对象.这个对象封装了同质数据类型的 n 维数组,许多操作在编译代码中执行以提高性能.NumPy 数组与标准 Python 序列有几个重要区别:

  • NumPy 数组在创建时具有固定大小,不像 Python 列表(可以动态增长).改变 ndarray 的大小将创建一个新数组并删除原始数组.

  • NumPy 数组中的元素都必须是相同的数据类型,因此在内存中大小相同.例外情况:可以有(Python,包括 NumPy)对象的数组,从而允许不同大小的元素的数组.

  • NumPy 数组便于对大量数据进行高级数学和其他类型的操作.通常,与使用 Python 的内置序列相比,这些操作可以更高效地执行并且代码更少.

  • 越来越多的基于Python的科学和数学包正在使用NumPy数组;尽管这些包通常支持Python序列输入,但它们在处理之前会将这种输入转换为NumPy数组,并且它们经常输出NumPy数组.换句话说,为了高效使用当今许多(甚至可能是大多数)基于Python的科学/数学软件,仅仅知道如何使用Python的内置序列类型是不够的——还需要知道如何使用NumPy数组.

序列大小和速度的点在科学计算中特别重要.作为一个简单的例子,考虑将一个一维序列中的每个元素与另一个相同长度的序列中的相应元素相乘的情况.如果数据存储在两个Python列表中,``a`` 和 b,我们可以遍历每个元素:

c = []
for i in range(len(a)):
    c.append(a[i]*b[i])

这会产生正确的答案,但如果 ab 各自包含数百万个数字,我们将为 Python 中循环的低效付出代价.通过用 C 语言编写(为了清晰起见,我们忽略了变量声明和初始化、内存分配等),我们可以更快地完成相同的任务.

for (i = 0; i < rows; i++) {
  c[i] = a[i]*b[i];
}

这节省了涉及解释Python代码和操作Python对象的所有开销,但代价是失去了用Python编码带来的好处.此外,所需的编码工作量随着我们数据维度的增加而增加.例如,在2-D数组的情况下,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 代码快速执行.

c = a * b

以接近C语言的速度完成前面示例的工作,但代码的简洁性是我们期望基于Python的东西所能提供的.确实,NumPy的惯用法甚至更简单!这个最后的示例说明了NumPy的两个特性,这两个特性是其强大功能的基础:向量化和广播.

为什么 NumPy 快?#

向量化描述了代码中没有任何显式的循环、索引等 - 这些事情当然在发生,只是”在幕后”在优化的、预编译的 C 代码中进行.向量化代码有许多优点,其中包括:

  • 向量化代码更简洁且易于阅读

  • 较少的代码行通常意味着较少的错误

  • 代码更接近于标准数学符号(通常,这使得正确编码数学结构变得更容易)

  • 矢量化使代码更加”Pythonic”.没有矢量化,我们的代码将会充斥着低效且难以阅读的 for 循环.

广播是描述操作隐式逐元素行为的一个术语;一般来说,在NumPy中,不仅仅是算术操作,还有逻辑、位运算、函数等所有操作都以这种隐式的逐元素方式进行,即它们进行广播.此外,在上面的例子中,``a`` 和 b 可以是形状相同的多维数组,或者是标量和数组,甚至是形状不同的两个数组,前提是较小的数组可以以一种不模糊的方式扩展到较大数组的形状.有关广播的详细”规则”,请参见 Broadcasting.

还有谁使用 NumPy?#

NumPy 完全支持面向对象的方法,再次从 ndarray 开始.例如,`~numpy.ndarray` 是一个类,拥有众多方法和属性.它的许多方法在外层 NumPy 命名空间中都有对应的函数,允许程序员使用他们偏好的范式编写代码.这种灵活性使得 NumPy 数组方言和 NumPy ndarray 类成为 Python 中多维数据交换的 事实标准 语言.