NumPy基础知识(四)

  • 数据类型
  • 数组创建
  • 使用NumPy进行I / O
  • 索引编制
  • 分配与参考
  • 单元素索引
  • 其他索引选项
  • 索引数组
  • 索引多维数组
  • 布尔或“掩码”索引数组
  • 将索引数组与切片组合
  • 结构索引工具
  • 将值分配给索引数组
  • 处理程序中可变数量的索引
  • 广播
  • 字节交换
  • 结构化数组
  • 编写自定义数组容器
  • 子数组ndarray

数组索引是指使用方括号([])来索引数组值。索引有很多选择,它们赋予numpy索引强大的功能,但是随着功能的加入,会带来一些复杂性和潜在的混乱。本节仅概述与索引相关的各种选项和问题。除了单元素索引编制之外,有关这些选项中大多数的详细信息,请参见相关章节。

分配与参考

以下大多数示例说明了在引用数组中的数据时使用索引的方法。分配给数组时,这些示例也能正常工作。有关具体示例和分配工作原理的说明,请参见最后一节。


单个元素的索引

一维数组的单元素索引是人们所期望的。它的工作方式与其他标准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不同,在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行是从索引数组中选择的,并组合成一个二维数组。

通常,当布尔数组的维数小于要索引的数组的维数时,这等效于y [b,…],这意味着y由b索引,后跟任意多个:来填充y的等级。因此,结果的形状是一维,其中包含布尔数组的True元素的数量,然后是要索引的数组的其余维。

例如,使用具有四个True元素的形状(2,3)的2-D布尔数组从形状(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参考文档。


结合索引数组和切片

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



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



实际上,切片和索引阵列操作是独立的。切片操作提取索引为1和2的列(即第二列和第三列),然后进行索引数组操作,提取索引为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]])


结构索引工具

为了便于将数组形状与表达式和赋值轻松匹配,可以在数组索引中使用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
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次。


与计划中的指标数量可变处理

索引语法非常强大,但是在处理可变数目的索引时会受到限制。例如,如果您要编写一个函数,该函数可以处理各种维数的参数,而不必为每种可能的维数写特殊情况代码,那该怎么办?如果将一个元组提供给索引,则该元组将被解释为索引列表。例如(使用数组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