Wetts's blog

Stay Hungry, Stay Foolish.

0%

Python-numpy-索引与切片

参考:https://www.numpy.org.cn/user_guide/numpy_basics/indexing.html

索引

单个元素索引

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

>>> 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
2
>>> x[0][2]
2

因此,请注意,x[0, 2] = x[0][2], 但是第二种情况效率更低,因为一个新的临时数组在第一个索引后创建了,这个临时数组随后才被2这个数字索引。

其他索引选项

可以对数组进行切片和步进,以提取具有相同数量维数的数组,但其大小与原始数据不同。切片和跨步的工作方式与对列表和元组完全相同,除此之外它们还可以应用于多个维度。

1
2
3
4
5
6
7
8
9
10
11
>>> 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]])

请注意,数组切片不会复制内部数组数据,但也会产生原始数据的新视图。

索引数组

数组索引指的是使用方括号([])来索引数组值。有很多选项来索引,这使numpy索引很强大,但功能上的强大也带来一些复杂性和潜在的混乱。

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

1
2
3
4
5
>>> 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])

索引值超出范围是错误的:

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

一般来说,使用索引数组时返回的是与索引数组具有相同形状的数组,但是索引数组的类型和值。作为一个例子,我们可以使用多维索引数组来代替:

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

索引多维数组

对多维数组进行索引时,情况会变得更加复杂,特别是对于多维索引数组。这些往往是更常用的用途,但它们是允许的,并且它们对于一些问题是有用的。我们将从最简单的多维情况开始:

1
2
3
>>> y = np.arange(35).reshape(5,7)
>>> 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]

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

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

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

1
2
3
4
>>> 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值的三倍与每个像素位置相关联。

通常,resulant数组的形状将是索引数组形状(或所有索引数组广播的形状)与索引数组中任何未使用的维(未索引的维)的形状的串联。

布尔值或掩码索引数组

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

1
2
3
>>> 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的维数更高,则结果将是多维的。例如:

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

这里第4行和第5行是从索引数组中选择出来的,并组合起来构成一个二维数组。

一般来说,当布尔数组的维数小于被索引的数组时,这相当于y[b, ...]

组合索引和切片

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

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

实际上,切片被转换为索引数组np.array([[1,2]])(形状(1,2)),该数组与索引数组一起广播以产生形状的结果数组(3,2) 。

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

1
2
3
>>> y[b[:,5],1:3]
array([[22, 23],
[29, 30]])

结构化索引工具

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

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

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

1
2
3
4
5
6
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]])

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

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

这相当于:

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

给被索引的数组赋值

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

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

或者正确大小的数组:

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

请注意,如果将较高类型分配给较低类型(在int类型中添加浮点数(floats))或甚至导致异常(将复数分配给int/float类型),分配可能会导致更改:

1
2
3
4
5
6
>>> x[1] = 1.2
>>> x[1]
1
>>> x[1] = 1.2j
<type 'exceptions.TypeError'>: can't convert complex to long; use
long(abs(z))

与一些引用(如数组和掩码索引)不同,赋值通常是对数组中的原始数据进行赋值的(事实上,没有其他意义了!)。但请注意,有些行为可能不会如人们所期望的那样行事。这个特殊的例子通常令人惊讶:

1
2
3
4
5
6
>>> 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,1,1,1,1的值,则将值1添加到临时数据中,然后将临时数据分配回原始数组。因此,x[1]+1处的数组值被分配给x[1]三次,而不是递增3次。

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

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

1
2
3
4
>>> z = np.arange(81).reshape(3,3,3,3)
>>> indices = (1,1,1,1)
>>> z[indices]
40

所以可以使用代码来构造任意数量的索引的元组,然后在索引中使用这些元组。

切片可以通过在Python中使用slice()函数在程序中指定。例如:

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

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

````

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

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

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

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
```