numpy03:数组索引与切片

数组的索引与切片

数组的索引

类似 Python 的列表,numpy 中的数组也可以用中括号指定获取索引值位置的元素。索引值同样从零开始计数,且可以使用负数索引获取末尾元素:

a1 = np.arange(10)
print(a1[0], a1[4], a1[-2])
0 4 8

不同与 Python 的嵌套列表,可以用逗号分隔的索引元组获取多维数组的某一元素,而不需要多次索引:

a2 = np.arange(12).reshape((3, 4))
print(a2)
print(a2[0, 0], a2[2, 1], a2[-3, -1])
[[ 0 1 2 3] [ 4 5 6 7] [ 8 9 10 11]] 0 9 3

索引元组是多次切片的一个便捷替代,因此前面的值代表的是外层数组的索引。

可以用以上的索引方式为数组内某个位置的元素分配一个新值:

a2[2, 1] = 23
a2
array([[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 23, 10, 11]])

注意,numpy 的数组是固定类型的,当将一个不同类型的元素插入数组时,它会被隐式地截短转换:

a1[0] = 10.55
a1
array([10, 0, 3, 3, 7, 9])

数组的切片

numpy 的数组也支持切片语法,语法和 Python 对序列的切片一致。如果 arr 是一个数组,可以用以下语法对 arr 切片:

arr[start:stop:step]

对一维数组的切片很简单,与 Python 切片语法一致。例如以下几个简单示例:

print(a1[4:7])
print(a1[:5:-1])
print(a1[6::-2])
[4 5 6] [9 8 7 6] [6 4 2 0]

多维数组的切片也采用类似索引的方法处理:可以用逗号分隔的切片对象来逐维切片,例如:

a2[:2, :3]  # slice to line, slice to col
array([[32, 5, 2], [ 7, 6, 8]])

该切片的意思是先在第二个维度(列方向)取前 2 项元素,再在第一个维度(行方向)取前三项元素。下面再给出一个更复杂的示例:

a2[1:3, ::2]
array([[ 4, 6], [ 8, 10]])

一种常见的需求是获取多维数组的特定行和列。这种情况下,可以用索引与切片组合起来实现该功能,用单个冒号“ : ”表示空切片(即不做切片,获取该维度的所有元素):

a2[:, 0]
array([0, 4, 8])

意思是不对第二个维度做切片(保留所有行),然后获取每行的首个元素。再如:

a2[0, :]  # equals to a2[0]
array([0, 1, 2, 3])

在更高维的数组切片中,还可以使用省略号来表示多个连续的空切片,例如:

a3 = np.arange(3 * 4 * 5).reshape((3, 4, 5))
a3[..., -1]  # equals to a3[:, :, -1]
array([[ 4, 9, 14, 19], [24, 29, 34, 39], [44, 49, 54, 59]])

为了不发生歧义,一个切片中最多只能使用一个省略号。

最后关于数组切片需要注意的是,切片返回的也是数组的视图,因此在切片中修改元素也会影响原数组。如果确实需要得到一个副本子数组的话,可以对切片对象使用 .copy() 方法得到副本。

切片与数组升维

切片还可用于给数组增加维度。一种常见的需求是将一个数组变成二维数组的行或列,此时可以在数组切片中使用 np.newaxis 变量给数组添加新维度,具体操作为:

a1[:, np.newaxis][:3]
array([[10], [ 1], [ 2]])

以上操作在 np.newaxis 对应的位置加上了一个维度,使数组的形状变成了 (-1, 1) ,形成二维数组(列向量)。

可以结合以下升维再切片降维的过程理解,两个切片互为逆过程:

a1[np.newaxis, :, np.newaxis, np.newaxis][0, :, 0, 0]
array([10, 1, 2, 3, 4, 5, 6, 7, 8, 9])

np.newaxis 实际上是 None 的别名。该过程虽然是升维,但得到的也是数组的视图。

高级索引机制

数组索引

接下来简单介绍另外一种索引方式数组索引,也称作花哨的索引(fancy indexing)。花哨的索引和前面那些简单的索引非常类似,但是传递的是索引数组,而不是单个标量。这种强大的索引能够快速获得并修改复杂的数组值的子数据集。

如果在索引中传递单个列表或数组,它包括的元素会被当做索引值,可以获取若干元素构成的数组(视图):

a1[[0, 1, 2, 4, 8, -1]]
array([10, 1, 2, 4, 8, 9])

数组索引得到的数组形状与索引的形状一致,而不是与原数组的形状一致:

ind1 = np.array([[0, 1], [5, 8]])
a1[ind1]
array([[10, 1], [ 5, 8]])

这种索引方式也适用于多维数组,不过多维数组下的数组索引是这样工作的:如果向索引内传入多个数组,那么每个数组分别确定每个维度的索引。例如:

a2[[1, 2, 2], [0, 1, 3]]
array([ 4, 23, 11])

以上两个数组结合确定了三个位置:[1, 0] [2, 1] 和 [2, 3] 。

可以将普通索引、切片和数组索引组合使用,使数组在某些维度做普通索引,某些维度做切片,某些维度用数组索引。例如,索引:

a2[1, :-1]
array([4, 5, 6])

取第二行的数组中除最后一个元素外的所有元素,但不改变数组间的嵌套关系。再如:

a2[1:, [0, -1]]
array([[ 4, 7], [ 8, 11]])

取第一行及往后的第一个和最后一个元素,但不改变数组间的嵌套关系。

用数组索引修改值

切片和数组索引可以用于获取部分元素,并且返回的是数组视图。如果对视图内的元素赋值,可以批量修改数组的一部分数据,例如:

a1[0:5] = 20
a1
array([20, 20, 20, 20, 20, 5, 6, 7, 8, 9])

可以做任意的赋值操作,例如:

a1[0:5] //= 4
a1
array([5, 5, 5, 5, 5, 5, 6, 7, 8, 9])

数组索引有一种特殊情况,那就是对同一个位置做了多次索引,导致重复获取该位置的值。如果对一个位置做重复的赋值,会导致前面的赋值被覆盖:

a1[[9, -1]] = [12, 13]
array([ 5, 5, 5, 5, 5, 5, 6, 7, 8, 13])

因为该操作对最后一个位置的元素赋值了两次。覆盖的本质是只有最后一次对元素的操作才有效,因此对同一个元素的多次复合运算赋值不会累计起效:

a1[[0, 0, 0, 0]] -= 10
a1
array([-5, 5, 5, 5, 5, 5, 6, 7, 8, 13])

本节对数组的索引机制暂时到这边结束。下一节将介绍通用函数和数组的广播机制,可以结合数组索引做一些更复杂的操作。

京ICP备2021034974号
contact me by hello@frozencandles.fun