NumPy 索引

2021-09-01 09:48 更新

数组索引是指使用方括号 ([]) 来索引数组值。索引有很多选项,这赋予了 NumPy 索引强大的功能,但功能带来了一些复杂性和混乱的可能性。本节只是对与索引相关的各种选项和问题的概述。除了单元素索引之外,大多数这些选项的详细信息都可以在相关部分中找到。

1、赋值 vs 引用

以下大多数示例显示了在引用数组中的数据时索引的使用。这些示例在分配给数组时也能正常工作。有关分配如何工作的具体示例和说明,请参阅最后的部分。

2、单元素索引

一维数组的单个元素索引是人们所期望的。它的工作方式与其他标准 Python 序列完全一样。它是基于 0 的,并接受从数组末尾开始索引的负索引。

>>> x = np.arange(10)
>>> x[2]
2
>>> x[-2]
8

与列表和元组不同,NumPy 数组支持多维数组的多维索引。这意味着没有必要将每个维度的索引分隔到它自己的一组方括号中。

>>> x.shape = (2,5) # now x is 2-dimensional
>>> x[1,3]
8
>>> x[1,-1]
9

请注意,如果索引一个多维数组的索引少于维数,则会得到一个子维数组。例如:

>>> x[0]
array([0, 1, 2, 3, 4])

也就是说,指定的每个索引都选择与所选维度的其余部分相对应的数组。在上面的示例中,选择 0 意味着长度为 5 的剩余维度未指定,并且返回的是该维度和大小的数组。必须注意,返回的数组不是原始数组的副本,而是指向与原始数组相同的内存中的值。在这种情况下,返回第一个位置 (0) 的一维数组。因此,在返回的数组上使用单个索引会导致返回单个元素。那是:

>>> x[0][2]
2

所以请注意,尽管第二种情况效率更低,因为在第一个索引之后创建了一个新的临时数组,该索引随后被 2 索引。x[0,2] = x[0][2]

请注意那些习惯于 IDL 或 Fortran 内存顺序的人,因为它与索引有关。NumPy 使用 C 顺序索引。这意味着最后一个索引通常代表变化最快的内存位置,与 Fortran 或 IDL 不同,第一个索引代表内存中变化最快的位置。这种差异代表了很大的混淆可能性。

3、其他索引选项

可以对数组进行切片和跨步以提取维数相同但大小与原始数组不同的数组。切片和跨步的工作方式与列表和元组的工作方式完全相同,只是它们也可以应用于多个维度。几个例子最能说明问题:

>>> x = np.arange(10)
>>> x[2:5]
array([2, 3, 4])
>>> x[:-7]
array([0, 1, 2])
>>> x[1:7:2]
array([1, 3, 5])
>>> y = np.arange(35).reshape(5,7)
>>> y[1:5:2,::3]
array([[ 7, 10, 13],
       [21, 24, 27]])

请注意,数组切片不会复制内部数组数据,而只会生成原始数据的新视图。这与列表或元组切片不同,copy()如果不再需要原始数据,建议使用显式切片。

为了从数组中选择值列表到新数组中,可以用其他数组索引数组。有两种不同的方法可以实现这一点。一种使用一个或多个索引值数组。另一个涉及提供适当形状的布尔数组来指示要选择的值。索引数组是一种非常强大的工具,可以避免遍历数组中的单个元素,从而大大提高性能。

可以使用特殊功能通过索引有效地增加数组中的维数,以便生成的数组获得在表达式或特定函数中使用所需的形状。

4、索引数组

NumPy 数组可以与其他数组(或任何其他可以转换为数组的类似序列的对象,例如列表,元组除外;有关原因,请参阅本文档末尾)进行索引。索引数组的使用范围从简单、直接的情况到复杂、难以理解的情况。对于索引数组的所有情况,返回的是原始数据的副本,而不是切片的视图。

索引数组必须是整数类型。数组中的每个值指示使用数组中的哪个值来代替索引。为了显示:

>>> x = np.arange(10,1,-1)
>>> x
array([10,  9,  8,  7,  6,  5,  4,  3,  2])
>>> x[np.array([3, 3, 1, 8])]
array([7, 7, 9, 2])

由值 3、3、1 和 8 组成的索引数组相应地创建了一个长度为 4 的数组(与索引数组相同),其中每个索引都被索引数组在被索引的数组中具有的值替换。

负值是允许的,并且可以像处理单个索引或切片一样工作:

>>> x[np.array([3,3,-3,8])]
array([7, 7, 4, 2])

索引值越界是错误的:

>>> x[np.array([3, 3, 20, 8])]
<type 'exceptions.IndexError'>: index 20 out of bounds 0<=index<9

一般而言,使用索引数组时返回的是一个与索引数组形状相同的数组,但被索引的数组的类型和值。例如,我们可以改用多维索引数组:

>>> x[np.array([[1,1],[2,3]])]
array([[9, 9],
       [8, 7]])

5、索引多维数组

当多维数组被索引时,事情变得更加复杂,尤其是多维索引数组。这些往往是更不寻常的用途,但它们是允许的,并且它们对某些问题很有用。我们将从最简单的多维情况开始(使用前面示例中的数组 y):

>>> y[np.array([0,2,4]), np.array([0,1,2])]
array([ 0, 15, 30])

在这种情况下,如果索引数组具有匹配的形状,并且被索引的数组的每个维度都有一个索引数组,则结果数组与索引数组具有相同的形状,并且值对应于为每个维度设置的索引在索引数组中的位置。在此示例中,两个索引数组的第一个索引值都是 0,因此结果数组的第一个值是 y[0,0]。下一个值是 y[2,1],最后一个值是 y[4,2]。

如果索引数组的形状不同,则会尝试将它们广播为相同的形状。如果它们不能广播到相同的形状,则会引发异常:

>>> y[np.array([0,2,4]), np.array([0,1])]
<type 'exceptions.ValueError'>: shape mismatch: objects cannot be
broadcast to a single shape

广播机制允许索引数组与其他索引的标量组合。效果是标量值用于索引数组的所有对应值:

>>> y[np.array([0,2,4]), 1]
array([ 1, 15, 29])

跳到下一个复杂级别,可以仅部分索引具有索引数组的数组。理解在这种情况下会发生什么需要一些思考。例如,如果我们只使用一个带有 y 的索引数组:

>>> y[np.array([0,2,4])]
array([[ 0,  1,  2,  3,  4,  5,  6],
       [14, 15, 16, 17, 18, 19, 20],
       [28, 29, 30, 31, 32, 33, 34]])

结果是构建一个新数组,其中索引数组的每个值从被索引的数组中选择一行,结果数组具有结果形状(索引元素的数量,行的大小)。 这可能有用的一个例子是颜色查找表,我们希望将图像的值映射到 RGB 三元组以进行显示。查找表可以有一个形状 (nlookup, 3)。用 dtype=np.uint8(或任何整数类型,只要值在查找表的范围内)形状为 (ny, nx) 的图像索引这样的数组将导致形状为 (ny, nx, 3) 其中三个 RGB 值与每个像素位置相关联。 通常,结果数组的形状将是索引数组的形状(或所有索引数组被广播到的形状)与被索引的数组中任何未使用的维度(未索引的维度)的形状的串联.

6、布尔或“掩码”索引数组

用作索引的布尔数组的处理方式与索引数组完全不同。布尔数组的形状必须与被索引的数组的初始维度相同。在最直接的情况下,布尔数组具有相同的形状:

>>> b = y>20
>>> y[b]
array([21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34])

与整数索引数组的情况不同,在布尔情况下,结果是一个一维数组,其中包含索引数组中与布尔数组中所有真实元素相对应的所有元素。索引数组中的元素总是以行优先(C 样式)顺序迭代和返回 。结果也与 相同 y[np.nonzero(b)]。与索引数组一样,返回的是数据的副本,而不是切片时的视图。

如果 y 的维度多于 b,结果将是多维的。例如:

>>> b[:,5] # use a 1-D boolean whose first dim agrees with the first dim of y
array([False, False, False,  True,  True])
>>> y[b[:,5]]
array([[21, 22, 23, 24, 25, 26, 27],
       [28, 29, 30, 31, 32, 33, 34]])

这里从索引数组中选择第 4 行和第 5 行并组合成一个二维数组。

一般来说,当布尔数组的维数比被索引的数组少时,这相当于 y[b, …],这意味着 y 由 b 索引,后跟尽可能多的 : 以填充 y 的秩。因此,结果的形状是一维,包含布尔数组的 True 元素的数量,然后是被索引的数组的其余维度。

例如,使用具有四个 True 元素的形状 (2,3) 的二维布尔数组从形状 (2,3,5) 的 3-D 数组中选择行会导致形状 (4 ,5):

>>> x = np.arange(30).reshape(2,3,5)
>>> x
array([[[ 0,  1,  2,  3,  4],
        [ 5,  6,  7,  8,  9],
        [10, 11, 12, 13, 14]],
       [[15, 16, 17, 18, 19],
        [20, 21, 22, 23, 24],
        [25, 26, 27, 28, 29]]])
>>> b = np.array([[True, True, False], [False, True, True]])
>>> x[b]
array([[ 0,  1,  2,  3,  4],
       [ 5,  6,  7,  8,  9],
       [20, 21, 22, 23, 24],
       [25, 26, 27, 28, 29]])

有关更多详细信息,请参阅有关数组索引的 NumPy 参考文档。

7、将索引数组与切片结合

索引数组可以与切片组合。例如:

>>> y[np.array([0, 2, 4]), 1:3]
array([[ 1,  2],
       [15, 16],
       [29, 30]])

实际上,切片和索引数组操作是独立的。切片操作提取索引为 1 和 2 的列(即第 2 和第 3 列),然后是索引数组操作提取索引为 0、2 和 4 的行(即第一、第三和第五行)。

这相当于:

>>> y[:, 1:3][np.array([0, 2, 4]), :]
array([[ 1,  2],
       [15, 16],
       [29, 30]])

同样,切片可以与广播布尔索引结合使用:

>>> b = y > 20
>>> b
array([[False, False, False, False, False, False, False],
      [False, False, False, False, False, False, False],
      [False, False, False, False, False, False, False],
      [ True,  True,  True,  True,  True,  True,  True],
      [ True,  True,  True,  True,  True,  True,  True]])
>>> y[b[:,5],1:3]
array([[22, 23],
       [29, 30]])

8、结构索引工具

为了便于数组形状与表达式和赋值的匹配,可以在数组索引中使用 np.newaxis 对象来添加大小为 1 的新维度。例如:

>>> y.shape
(5, 7)
>>> y[:,np.newaxis,:].shape
(5, 1, 7)

请注意,数组中没有新元素,只是维度增加了。这可以方便地以一种方式组合两个数组,否则将需要显式重塑操作。例如:

>>> x = np.arange(5)
>>> x[:,np.newaxis] + x[np.newaxis,:]
array([[0, 1, 2, 3, 4],
       [1, 2, 3, 4, 5],
       [2, 3, 4, 5, 6],
       [3, 4, 5, 6, 7],
       [4, 5, 6, 7, 8]])

省略号语法可用于指示完全选择任何剩余的未指定维度。例如:

>>> z = np.arange(81).reshape(3,3,3,3)
>>> z[1,...,2]
array([[29, 32, 35],
       [38, 41, 44],
       [47, 50, 53]])

这相当于:

>>> z[1,:,:,2]
array([[29, 32, 35],
       [38, 41, 44],
       [47, 50, 53]])

9、为索引数组赋值

如前所述,可以使用单个索引、切片以及索引和掩码数组来选择要分配给的数组子集。分配给索引数组的值必须形状一致(与索引生成的形状相同或可广播)。例如,允许为切片分配一个常量:

>>> x = np.arange(10)
>>> x[2:7] = 1

或正确大小的数组:

>>> x[2:7] = np.arange(5)

请注意,如果将较高类型分配给较低类型(如浮点数为整数)甚至异常(将复杂数分配为浮点数或整数),则赋值可能会导致更改:

>>> x[1] = 1.2
>>> x[1]
1
>>> x[1] = 1.2j
TypeError: can't convert complex to int

与某些引用(例如数组和掩码索引)不同,总是对数组中的原始数据进行赋值(实际上,其他任何事情都没有意义!)。但请注意,某些操作可能不会像人们天真地预期的那样起作用。这个特殊的例子常常让人们感到惊讶:

>>> x = np.arange(0, 50, 10)
>>> x
array([ 0, 10, 20, 30, 40])
>>> x[np.array([1, 1, 3, 1])] += 1
>>> x
array([ 0, 11, 20, 31, 40])

人们期望第一个位置会增加 3。实际上,它只会增加 1。原因是因为从原始数组中提取了一个新数组(作为临时数组),其中包含 1、1、3 处的值, 1,然后将值 1 添加到临时数组,然后将临时数组分配回原始数组。因此,数组在 x[1]+1 处的值被分配给 x[1] 3 次,而不是增加 3 次。

10、处理程序中可变数量的索引

索引语法非常强大,但在处理可变数量的索引时会受到限制。例如,如果您想编写一个函数,该函数可以处理具有各种维数的参数,而不必为每个可能的维数编写特殊的案例代码,那该怎么做呢?如果向索引提供一个元组,则该元组将被解释为索引列表。例如(使用数组 z 的先前定义):

>>> indices = (1,1,1,1)
>>> z[indices]
40

因此,可以使用代码构造任意数量索引的元组,然后在索引中使用这些元组。 可以使用 Python 中的 slice() 函数在程序中指定切片。例如:

>>> indices = (1,1,1,slice(0,2)) # same as [1,1,1,0:2]
>>> z[indices]
array([39, 40])

同样,可以使用 Ellipsis 对象通过代码指定省略号:

>>> indices = (1, Ellipsis, 1) # same as [1,...,1]
>>> z[indices]
array([[28, 31, 34],
       [37, 40, 43],
       [46, 49, 52]])

出于这个原因,可以直接使用 np.nonzero() 函数的输出作为索引,因为它总是返回索引数组的元组。

由于元组的特殊处理,它们不会像列表那样自动转换为数组。举个例子:

>>> z[[1,1,1,1]] # produces a large array
array([[[[27, 28, 29],
         [30, 31, 32], ...
>>> z[(1,1,1,1)] # returns a single value
40
以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号