什么是NumPy?

NumPy是Python中科学计算的基础软件包。 它是一个提供多了维数组对象,多种派生对象(如:掩码数组、矩阵)以及用于快速操作数组的函数及API, 它包括数学、逻辑、数组形状变换、排序、选择、I/O 、离散傅立叶变换、基本线性代数、基本统计运算、随机模拟等等

NumPy包的核心是 ndarray 对象。它封装了python原生的同数据类型的n维数组,为了保证其性能优良, 其中有许多操作都是代码在本地进行编译后执行的。NumPy数组和标准Python Array(数组)之间有几个重要的区别:

  • NumPy数组在创建时具有固定的大小,与Python的原生数组对象(可以动态增长)不同。 更改 ndarray 的大小将创建一个新数组并删除原来的数组
  • NumPy数组中的元素都需要具有相同的数据类型,因此在内存中的大小相同。例外情况: Python的原生数组里包含了NumPy的对象的时候,这种情况下就允许不同大小元素的数组。
  • NumPy数组有助于对大量数据进行高级数学和其他类型的操作。 通常,这些操作的执行效率更高,比使用Python原生数组的代码更少。
  • 越来越多的基于Python的科学和数学软件包使用NumPy数组。虽然这些工具通常都支持Python 的原生数组作为参数,但它们在处理之前会还是会将输入的数组转换为NumPy的数组, 而且也通常输出为NumPy数组。 换句话说,为了高效地使用当今科学/数学基于Python的工具 (大部分的科学计算工具),你只知道如何使用Python的原生数组类型是不够的,还需要知道如何使用NumPy数组。

关于序列大小和速度的问题在科学计算中尤为重要。作为一个简单的例子,考虑将一维序列中的每个元素与相同长度的 另一个序列中的对应元素相乘的情况。如果数据存储在两个Python列表 ab 中,我们可以迭代每个元素

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编写代码所带来的好处。 此外,所需的编码工作随着数据维数的增加而增加。例如,在二维数组的情况下,C代码(与以前一样简写)扩展为

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

NumPy为我们提供了这两个世界中最好的:当涉及到ndarray时,元素逐元素操作是 default mode , 但是元素逐元素操作是通过预编译的C代码快速执行的。在NumPy中

c = a * b

以接近C的速度做前面的示例所做的事情。由于代码的简单性,我们期望基于Python来写代码。事实上,NumPy的写法甚至 更简单!最后一个例子说明了NumPy的两个特性,它们是NumPy大部分功能的基础:矢量化和广播

矢量化描述了代码中没有任何明确的循环、索引等等-当然,这些事情只是在优化的、预编译的C代码中发生的。 矢量化代码有许多优点,其中包括:

  • 矢量化代码更简洁,更易于阅读。
  • 代码行的减少通常意味着bug的减少。
  • 代码更像标准的数学表示法(通常更容易正确地编码数学结构)

-矢量化会产生更多的“Pythonic”代码。如果没有矢量化,我们的代码将到处是效率低下和难以阅读的 for 循环.

广播是用来描述操作的隐式逐元素行为的术语;一般来说,在NumPy中,所有操作,不仅仅是算术运算, 还可以是逻辑的、位的、函数的运算等等,都是以这种隐式逐元素的方式表现的,即它们是广播的。此外,在上面的例子中 ab 可以是相同形状的多维数组,也可以是标量和数组,甚至是两个形状不同的数组,条件是较小的数组可扩展到较大的数组的形状, 这样产生的广播是明确的。有关广播的详细规则,请参阅 numpy.doc.broadcasting

NumPy完全支持面向对象的方法。例如, ndarray 是一个类,具有许多方法和属性。它的许多方法反映了最外层的NumPy命名空间中的函数, 使程序员完全可以自由地用ta喜欢的任何范例和/或最适合当前任务的范例进行编码。