什么是NumPy?#
NumPy是Python科学计算的基础包.它是一个Python库,提供了一个多维数组对象,各种派生对象(例如,屏蔽数组和矩阵)以及用于快速操作数组的各种例程,包括数学,逻辑,形状操作,排序,选择,I/O,离散傅里叶变换,基本线性代数,基本统计运算,随机模拟等等.
NumPy包的核心是 ndarray 对象.它封装了同构数据类型的n维数组,许多操作都在编译后的代码中执行以提高性能.NumPy数组和标准Python序列之间有几个重要的区别:
与Python列表(可以动态增长)不同,NumPy数组在创建时具有固定大小.更改
ndarray的大小将创建一个新数组并删除原始数组.NumPy数组中的元素都必须是相同的数据类型,因此在内存中占用相同的大小.例外情况:可以拥有(Python,包括NumPy)对象的数组,从而允许使用不同大小元素的数组.
NumPy数组有助于对大量数据进行高级数学和其他类型的运算.通常,与使用Python的内置序列相比,此类操作的执行效率更高,代码更少.
越来越多的基于Python的科学和数学软件包正在使用NumPy数组;尽管这些软件包通常支持Python序列输入,但它们会在处理之前将此类输入转换为NumPy数组,并且它们通常输出NumPy数组.换句话说,为了有效地使用当今的大部分(甚至可能是大多数)基于Python的科学/数学软件,仅仅知道如何使用Python的内置序列类型是不够的 - 还需要知道如何使用NumPy数组.
关于序列大小和速度的观点在科学计算中尤为重要.作为一个简单的例子,考虑将1-D序列中的每个元素与相同长度的另一个序列中的相应元素相乘的情况.如果数据存储在两个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编码中获得的好处.此外,所需的编码工作量随着数据的维度而增加.例如,在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代码快速执行.在NumPy中
c = a * b
以接近C的速度执行前面示例的操作,但代码的简洁性是我们期望从基于Python的东西中获得的.实际上,NumPy的习惯用法甚至更简单!最后一个例子说明了NumPy的两个特性,这两个特性是其大部分功能的基础:向量化和广播.
为什么NumPy这么快?#
向量化描述了代码中没有任何显式循环,索引等 - 当然,这些事情正在发生,只是在优化的预编译C代码中“在幕后”.向量化代码具有许多优点,其中包括:
向量化代码更简洁,更易于阅读
通常代码行数越少,意味着 bug 越少
代码更接近于标准数学符号(通常,使得正确编码数学结构更容易)
向量化会产生更“Pythonic”的代码.如果没有向量化,我们的代码将充斥着低效且难以阅读的
for循环.
广播 (Broadcasting) 是用来描述操作的隐式逐元素行为的术语;一般来说,在 NumPy 中,所有操作,不仅仅是算术操作,还有逻辑,位运算,函数等等,都以这种隐式的逐元素方式进行,即它们会进行广播.此外,在上面的例子中, a 和 b 可以是相同形状的多维数组,也可以是标量和数组,甚至是两个形状不同的数组,只要较小的数组可以 “扩展” 到较大数组的形状,并且生成的广播没有歧义.有关广播的详细“规则”, 请参见 Broadcasting .
还有谁在使用 NumPy?#
NumPy 完全支持面向对象的方法,再次从 ndarray 开始. 例如, ndarray 是一个类,拥有许多方法和属性. 它的许多方法都与最外层 NumPy 命名空间中的函数相对应,允许程序员以他们喜欢的任何范例进行编码. 这种灵活性使得 NumPy 数组方言和 NumPy ndarray 类成为 Python 中多维数据交换的事实标准语言.