索引
::: tip 另见
:::
数组索引是指使用方括号([])来索引数组值。索引有很多选项,它可以为numpy索引提供强大的功能,但是功能会带来一些复杂性和混淆的可能性。本节仅概述了与索引相关的各种选项和问题。除了单个元素索引之外,大多数这些选项的详细信息都可以在相关章节中找到。
赋值与引用
以下大多数示例体现在引用数组中的数据时使用索引。分配给数组时,这些示例也可以正常运行的。有关分配的原理具体示例和说明,请参见最后一节。
单个元素索引
人们期望的是1-D数组的单元素索引。它的工作方式与其他标准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)的1-D数组。因此,在返回的数组上使用单个索引会导致返回单个元素。那是:
>>> x[0][2]
2
请注意,尽管第二种情况效率较低,因为在第一个索引之后创建了一个新的临时数组,该索引随后被索引为2:x[0,2] = x[0][2]
请注意那些习惯于IDL或Fortran内存顺序的内容,因为它与索引有关。NumPy使用C顺序索引。这意味着最后一个索引通常代表最快速变化的内存位置,与Fortran或IDL不同,其中第一个索引代表内存中变化最快的位置。这种差异代表了混淆的巨大潜力。
其他索引选项
可以对数组进行切片和跨步以提取具有相同数量的尺寸但具有与原始尺寸不同的尺寸的数组。切片和跨步的工作方式与列表和元组的工作方式完全相同,只是它们也可以应用于多个维度。一些例子说明了最好的:
>>> 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()
如果不再需要原始数据,建议使用显式。
可以使用其他数组索引数组,以便从数组中选择值列表到新数组中。有两种不同的方法来实现这一点。一个使用一个或多个索引值数组。另一个涉及给出一个正确形状的布尔数组来指示要选择的值。索引数组是一个非常强大的工具,可以避免循环遍历数组中的各个元素,从而大大提高性能。
可以使用特殊功能通过索引有效地增加数组中的维数,以便生成的数组获取在表达式或特定函数中使用所需的形状。
索引数组
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]])
索引多维数组
当索引多维数组时,事情变得更加复杂,特别是对于多维索引数组。这些往往是更不寻常的用途,但它们是允许的,它们对某些问题很有用。我们将从最简单的多维情况开始(使用前面示例中的数组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值与每个像素位置相关联。
通常,结果数组的形状将是索引数组的形状(或所有索引数组被广播的形状)与被索引的数组中任何未使用的维度(未索引的那些)的形状的串联。
布尔或“掩码”索引数组
用作索引的布尔数组的处理方式与索引数组完全不同。布尔数组的形状必须与要索引的数组的初始尺寸相同。在最直接的情况下,布尔数组具有相同的形状:
>>> 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行,并组合成2-D数组。
通常,当布尔数组的维数小于被索引的数组时,这相当于y [b,...],这意味着y被b索引后跟多少:填充y的等级所需的数量。因此,结果的形状是一个维度,其中包含布尔数组的True元素的数量,后跟被索引的数组的其余维度。
例如,使用具有四个True元素的形状(2,3)的二维布尔数组来从三维形状数组(2,3,5)中选择行,从而得到形状的二维结果(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参考文档。
将索引数组与切片组合
索引数组可以与切片组合。例如:
>>> y[np.array([0,2,4]),1:3]
array([[ 1, 2],
[15, 16],
[29, 30]])
实际上,切片被转换为索引数组 np.array([[1,2]]) (shape (1,2)),它与索引数组一起广播以产生一个结果 shape(3,2) 的数组。
同样,切片可以与广播的布尔索引组合:
>>> 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]])
结构索引工具
为了便于数组形状与表达式和赋值的轻松匹配,可以在数组索引中使用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]])
为索引数组赋值
如上所述,可以选择要分配给使用单个索引,切片,索引和掩码数组的数组的子集。分配给索引数组的值必须是形状一致的(与索引生成的形状相同的形状或可广播)。例如,允许为切片分配常量:
>>> 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
<type 'exceptions.TypeError'>: can't convert complex to long; use
long(abs(z))
与某些引用(例如数组和掩码索引)不同,总是对数组中的原始数据进行赋值(实际上,其他任何内容都没有意义!)。但请注意,某些操作可能无法正常工作。这个特殊的例子通常让人惊讶:
>>> 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次。
在程序中处理可变数量的索引
索引语法非常强大,但在处理可变数量的索引时会受到限制。例如,如果要编写一个可以处理具有不同维数的参数的函数,而不必为每个可能的维度编写特殊的案例代码,那么该怎么做呢?如果向索引提供元组,则元组将被解释为索引列表。例如(使用先前的数组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