



NumPy提供了高性能的数组类型,以及在Python中操作这些数组的例程。这些数组对于处理性能至关重要的大型数据集非常有用。NumPy构成了Python中数值和科学计算的基础。在底层,NumPy利用底层库来处理向量和矩阵,如 基本线性代数子程序 (Basic Linear Algebra Subprogram, BLAS ) ,以加速计算。
传统上,将NumPy包以较短的别名np导入,可以通过以下导入语句来完成:
这种约定在NumPy文档和更广泛的科学Python生态系统(如SciPy、pandas等)中都得到了应用。
NumPy库提供的基本数据类型是ndarray(以下称为NumPy数组)。通常情况下,你不会创建自己的ndarray实例,而是使用诸如array这样的辅助例程来正确设置类型。array例程利用类似数组的对象来创建NumPy数组,该对象通常是数字列表或列表的列表。例如,我们可以通过提供包含所需元素的列表来创建一个简单的数组:
NumPy数组类型(ndarray)是围绕底层C数组结构的Python封装。数组操作是用C语言实现的,并针对性能进行了优化。NumPy数组必须由同质数据(所有元素具有相同的类型)组成,尽管这种类型可能是指向任意Python对象的指针。如果没有使用dtype关键字显式指定数据类型,NumPy将在创建过程中推断出合适的数据类型:
NumPy为许多C类型提供了类型说明符,这些类型可以传递到dtype参数中,例如,先前使用的np.float32。一般来说,这些类型说明符的形式为namexx,其中name是类型的名称(例如int、float或complex),而xx是位数,例如8、16、32、64、128。通常,NumPy在为给定的输入选择适当的数据类型方面做得相当不错,但偶尔你可能会想要覆盖它。前面的例子就是一个很好的例子,如果没有dtype=np.float32参数,则NumPy会假定它的类型为int64。
在底层,任何形状的NumPy数组都可以被看作包含原始数据和附加元数据集合的缓冲区,其中原始数据是展平的(一维)数组,附加元数据集合用于指定诸如元素类型等详细信息。
创建后,可以使用数组的dtype属性访问数据类型。修改dtype属性会产生不良后果,因为构成数组数据的原始字节将被简单地重新解释为新的数据类型。例如,如果使用Python整数创建数组,NumPy会将这些整数转换为数组中的64位整数。更改dtype值将导致NumPy将这些64位整数重新解释为新的数据类型:
每个64位整数都被重新解释为两个32位浮点数,这显然会得到无意义的值。相反,如果你希望在创建后更改数据类型,请使用astype方法指定新类型。更改数据类型的正确方法如下所示:
NumPy还提供了许多用于创建各种标准数组的例程。zeros例程会创建一个指定形状的数组,数组中的每个元素都是0,ones例程则会创建一个元素都是1的数组。
NumPy数组支持getitem协议,因此可以像访问列表一样访问数组中的元素,并支持所有逐元素执行的算术运算。这意味着我们可以使用索引符号和索引位置从指定的数组中检索元素,如下所示:
它还可以用于从现有数组中提取数据数组的常用切片语法。数组的切片还是一个数组,其中包含由切片指定的元素。例如,我们可以检索ary数组的前两个元素,或者检索ary数组的偶数索引处的元素,如下所示:
切片的语法为start:stop:step。我们可以省略start和stop参数中的一个或两个,分别从所有元素的开头或结束处检索元素。我们还可以省略step参数,在这种情况下,我们还可以删除尾随的冒号“:”。step参数描述了应该选择选定范围内的元素。step值为1表示选择每个元素,或者如示例所示,step值为2表示每隔一个元素选择一次(从0开始选择偶数索引号的元素)。此语法与Python列表的切片语法相同。
NumPy提供了许多 通用函数 (ufuncs),这些函数可以高效地对NumPy数组类型进行操作。特别地,在1.3节中讨论的所有基本数学函数在NumPy中都有类似的函数,可以用来对NumPy数组进行操作。通用函数还可以执行广播(broadcasting),以使它们能够在形状不同但兼容的数组上进行操作。
对NumPy数组的算术运算是逐元素进行的。以下示例最能说明这一点:
请注意,这里的数组必须具有相同的形状,也就是说,它们必须具有相同的长度。对不同形状的数组进行算术运算将会抛出ValueError异常。数组与单个数字进行加法、减法、乘法或除法运算将生成一个数组,相应运算作用于数组的每个元素。例如,我们可以使用以下命令将数组中的所有元素乘以2:
正如我们所见,输出的数组包含数字2、4、6和8,它们是原始数组中的各元素乘以2得到的结果。
在1.4.3节中,除了我们在这里使用的np.array例程外,我们还将研究创建NumPy数组的各种方法。
要在两个给定端点之间以规律间隔生成数组,可以使用arange例程或lins-pace例程。这两个例程的区别在于,linspace在两个端点之间生成等间隔的一定数量(默认为50个)的数值,生成的数组包括两个端点,而arange以给定的步长生成数值,最大值不包括右端点。linspace例程在闭区间 a ≤ x ≤ b 内生成数值,而arange例程则在半开区间 a ≤ x < b 内生成数值:
请注意,使用linspace生成的数组正好有5个点,这是由第三个参数指定的,这5个点中包括两个端点0和1。而使用arange生成的数组有4个点,不包括右端点1,如果再加一个步长0.3,则将等于1.2,这就大于1了。
NumPy可以创建任意维数的数组,使用的是与创建简单一维数组相同的array例程。数组的维数由提供给array例程的嵌套列表的数量指定。例如,我们可以通过提供列表的列表来创建二维数组,其中内层列表的每个元素都是数字,如下所示:
NumPy数组具有shape属性,该属性描述了每个维度中元素的排列方式。对于二维数组,shape可以解释为数组的行数和列数。
三维或三维以上的数组有时称为 张量 。事实上,可以将任何大小的数组称为张量:向量(一维数组)是1-张量;二维数组是2-张量或矩阵——请参阅1.5节。常见的 机器学习 (ML)框架,如TensorFlow和PyTorch,都实现了它们自己的张量类,这些张量类的操作方式总是与NumPy数组类似。
NumPy将形状存储为array对象上的shape属性,该属性是一个元组。该元组中的元素数量就是数组的维数:
由于NumPy数组中的数据存储在一个展平的(一维)数组中,因此通过简单地更改关联的元数据,可以很容易地重塑数组。这一过程可以使用NumPy数组的reshape方法来完成:
请注意,元素的总数必须保持不变。矩阵mat最初的形状是(2,2),总共有4个元素,而重塑后的数组是一个形状为(4,)的一维数组,同样有4个元素。如果在元素的总数不匹配时进行数组重塑,将导致ValueError错误。
要创建更高维度的数组,只需添加更多级别的嵌套列表。为了更清晰地说明这一点,在下面的例子中,我们在构建数组之前分离出第三维中每个元素的列表:
注意
数组形状的第一个元素是最外层的,最后一个元素是最内层的。
这意味着向数组添加维度只需提供相关的元数据即可。使用array例程,shape元数据由参数中每个列表的长度来描述。最外层列表的长度定义该维度的相应shape参数,以此类推。
NumPy数组在内存中的大小在很大程度上并不取决于维度的数量,而仅取决于元素的总数,即shape参数的乘积。然而,请注意,在高维数组中,元素的总数往往更大。
要访问多维数组中的元素,可以使用通常的索引表示法,但是与提供单个数字不同,你需要在每个维度中提供索引。对于2×2矩阵,这意味着指定所需元素的行和列:
索引表示法还支持在每个维度中进行切片,因此我们可以通过使用切片mat[:,0]来提取单个列的所有元素,如下所示:
请注意,切片的结果是一维数组。
数组创建函数zeros和ones可以通过简单地指定具有多个维度参数的形状来创建多维数组。
在1.5节中,我们将研究二维NumPy数组的特殊情况,在Python中它们可以用作矩阵。