Python中的索引机制可以表示为:x[obj]
。当对象obj是一个元组时,元组的括号可以省略,因此x[(exp1, exp2, ..., expN)]
的索引写法与x[exp1, exp2, ..., expN]
是等价的。在NumPy中,根据对象obj的不同,数组索引可以分成基础索引和高级索引两大类。
数组的基础索引
数组的基础索引满足以下条件:
- 索引对象是整数
- 索引对象是
slice
对象 - 索引对象是一个由整数、
slice
对象构成的元组 - 索引对象还可以是
np.newaxis
,或者Python
内置的省略对象Ellipsis
import numpy as np
a = np.arange(80).reshape(8, 10)
a
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],
[30, 31, 32, 33, 34, 35, 36, 37, 38, 39],
[40, 41, 42, 43, 44, 45, 46, 47, 48, 49],
[50, 51, 52, 53, 54, 55, 56, 57, 58, 59],
[60, 61, 62, 63, 64, 65, 66, 67, 68, 69],
[70, 71, 72, 73, 74, 75, 76, 77, 78, 79]])
最简单的索引,使用一个N维整数元组,索引N维数组的单个元素:
a[1, 2]
12
使用切片slice对象,索引得到一个子数组:
a[1:9:2, 3:5]
array([[13, 14],
[33, 34],
[53, 54],
[73, 74]])
如果索引N维数组时,使用的元组大小小于N,NumPy会自动将后面缺失的维度补成:
,例如,a[1:3]
相当于a[1:3,:]
:
a[1:3]
array([[10, 11, 12, 13, 14, 15, 16, 17, 18, 19],
[20, 21, 22, 23, 24, 25, 26, 27, 28, 29]])
a[1:3, :]
array([[10, 11, 12, 13, 14, 15, 16, 17, 18, 19],
[20, 21, 22, 23, 24, 25, 26, 27, 28, 29]])
在索引时,还可以使用数字与slice
对象进行混合使用,整数i
的作用相当于slice
对象i:i+1
,但是维度会被消去,即每使用一个整数,得到的数组的维度就会比N小1:
a[1:2, :]
array([[10, 11, 12, 13, 14, 15, 16, 17, 18, 19]])
a[1, :]
array([10, 11, 12, 13, 14, 15, 16, 17, 18, 19])
a[1:2, :].shape
(1, 10)
a[1, :].shape
(10,)
最后的:
还可以省略:
a[1]
array([10, 11, 12, 13, 14, 15, 16, 17, 18, 19])
索引单个元素可以看成是一种特殊的混用。对于N维数组,如果都使用i:i+1的形式,最后会得到一个大小全1的N维数组;全部替换为整数i时,得到的数组维度要相应降低N维,得到0维数组,即普通的数字。
np.newaxis
可以在数组中插入新的维度。新维度插入后,索引对应的维度位置要相应改变:
a[1:3, np.newaxis, 1:4].shape
(2, 1, 3)
Ellipsis对象“...”可以用来省略一些维度,NumPy会根据具体的索引值,将缺少的维度自动补全:
b = a.reshape(2, 4, 2, 5)
b[..., 1].shape
(2, 4, 2)
b[Ellipsis, 1].shape
(2, 4, 2)
b[0, Ellipsis, :].shape
(4, 2, 5)
可以通过基础索引修改原来数组的值。例如,修改单个值的情况:
a
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],
[30, 31, 32, 33, 34, 35, 36, 37, 38, 39],
[40, 41, 42, 43, 44, 45, 46, 47, 48, 49],
[50, 51, 52, 53, 54, 55, 56, 57, 58, 59],
[60, 61, 62, 63, 64, 65, 66, 67, 68, 69],
[70, 71, 72, 73, 74, 75, 76, 77, 78, 79]])
a[0, 0] = 100
a
array([[100, 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],
[ 30, 31, 32, 33, 34, 35, 36, 37, 38, 39],
[ 40, 41, 42, 43, 44, 45, 46, 47, 48, 49],
[ 50, 51, 52, 53, 54, 55, 56, 57, 58, 59],
[ 60, 61, 62, 63, 64, 65, 66, 67, 68, 69],
[ 70, 71, 72, 73, 74, 75, 76, 77, 78, 79]])
对于子数组的情况,可以使用x[obj]=value
的形式进行赋值,只要数组x[obj]
的形状和value
的形状能够在数组广播机制下匹配即可。例如,将第二行修改为同一个值:
a[1] = 1
a[1]
array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1])
用一维数组给二维数组赋值,将第二三行变成同一个一维数组:
a[1:3] = np.arange(10)
a[1:3]
array([[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]])
用形状为(1,2)的数组给第二三行赋值:
a[1:3] = np.array([[1], [2]])
a[1:3]
array([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[2, 2, 2, 2, 2, 2, 2, 2, 2, 2]])
基础索引返回的是原来数组的一个引用,与原来的数组共享同一块内存:
a
array([[100, 1, 2, 3, 4, 5, 6, 7, 8, 9],
[ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
[ 30, 31, 32, 33, 34, 35, 36, 37, 38, 39],
[ 40, 41, 42, 43, 44, 45, 46, 47, 48, 49],
[ 50, 51, 52, 53, 54, 55, 56, 57, 58, 59],
[ 60, 61, 62, 63, 64, 65, 66, 67, 68, 69],
[ 70, 71, 72, 73, 74, 75, 76, 77, 78, 79]])
修改基础索引赋值的变量,会导致原来的数组也发生变化:
b = a[0]
b[0] = 0
a[0, 0]
0
数组的高级索引
数组支持高级索引操作,高级索引又叫花式索引(Fancy Indexing),与基础索引不同,高级索引返回的结果始终是原来数组的一个复制。
数组的高级索引需要满足:
- 索引对象是非元组的序列。
- 索引对象是整型或者布尔型的数组。
- 索引对象是包含至少一个前两种类型的元素的元组。
可以使用N个整型数组或者列表组成的索引值,来索引N维数组中的任意元素。例如:
a = np.array([[1, 2], [3, 4], [5, 6]])
a
array([[1, 2],
[3, 4],
[5, 6]])
在维度0使用列表索引位置[0,1,2]
,维度1使用列表索引位置[0,1,0]
,最终得到数组位置在(0,0)
,(1,1)
和(2,0)
的三个元素:
a[[0, 1, 2], [0, 1, 0]]
array([1, 4, 5])
再如,有这样一个4×3的数组:
b = np.arange(12).reshape(4, 3)
b
array([[ 0, 1, 2],
[ 3, 4, 5],
[ 6, 7, 8],
[ 9, 10, 11]])
使用两个数组来索引这个数组的第一、四行的第一、三列:
rows = np.array([[0, 0], [3, 3]])
cols = np.array([[0, 2], [0, 2]])
b[rows, cols]
array([[ 0, 2],
[ 9, 11]])
每个维度传入的索引数组的形状为(2,2)
,因此最后得到的数组形状为(2,2)
,第一行对应位置(0,0)
和(0,2)
,第二行对应位置(3,0)
和(3,2)
。
高级索引也支持广播机制,将刚才的索引进行简化:
rows = np.array([0, 3])
cols = np.array([0, 2])
它们的形状都是(2,)
,如果对其直接索引,会得到位置(0,0)
和(3,2)
的两个元素:
b[rows, cols]
array([ 0, 11])
这并不是预期想要的结果。为此,可以将rows的形状修改为(2,1),再进行索引:
rows.shape = 2, 1
b[rows, cols]
array([[ 0, 2],
[ 9, 11]])
rows的形状为(2,1),cols的形状为(2,),两个维度的索引数组形状不对应,NumPy先通过广播机制,将它们广播为匹配后的形状(2,2),最终得到与刚才一致的结果。
高级索引与基础索引可以混用,此时,情况会变得十分复杂。例如,基础索引下有:
b[1:2, 1:3]
array([[4, 5]])
将1:3
改成数组[1, 2]
,得到的结果相同,但是是高级索引:
b[1:2, [1, 2]]
array([[4, 5]])
NumPy有一套相应的规则来确定索引得到的结果。当索引中的高级索引不相邻时,高级索引对应的维度将被放在索引结果的最前面,之后是基础索引的维度。例如,数组a的形状为(10,20,30,40,50)
,考虑形状均为(2,3,4)
的两个索引数组ind1
、ind2
,索引a[ind1, ..., ind2]
的形状为(2,3,4,20,30,40)
,因为ind1
和ind2
不相邻:
a = np.ones((10, 20, 30, 40, 50))
ind = np.ones((2, 3, 4), dtype=int)
a[ind, ..., ind].shape
(2, 3, 4, 20, 30, 40)
而当所有的高级索引相邻时,它会替换掉对应的维度。例如,索引a[..., ind1, ind2, :]
会得到一个(10,20,2,3,4,50)
的数组,高级索引的维度(2,3,4)
替换了对应位置的(30,40)
:
a[..., ind, ind, :].shape
(10, 20, 2, 3, 4, 50)
在高级索引与基础索引混用时,使用单个数字的维度会被当作高级索引,因此,索引a[ind1,...,1]的形状为(2,3,4,20,30,40),因为1被当作高级索引,广播成了2×3×4的大小,导致高级索引的位置不相邻:
a[ind, ..., 1].shape
(2, 3, 4, 20, 30, 40)
还可以用一个与维度大小相等的布尔数组进行索引,并把其中为True
的位置拿出来。利用逻辑运算时可以得到:
a = np.array([1, 2, 3, 4, 5, 6])
a % 3 == 0
array([False, False, True, False, False, True])
a[a % 3 == 0]
array([3, 6])
a[a < 4]
array([1, 2, 3])