本文最后更新于 396 天前,其中的信息可能已经过时,如有错误请发送邮件到 wuxianglongblog@163.com
Python 中的索引机制可以表示为:x[obj]
。当对象 obj 是一个元组时,元组的括号可以省略,因此 x[(exp1, exp2, ..., expN)]
的索引写法与 x[exp1, exp2, ..., expN]
是等价的。在 NumPy 中,根据对象 obj 的不同,数组索引可以分成基础索引和高级索引两大类。
数组的基础索引满足以下条件:
- 索引对象是整数
- 索引对象是
slice
对象
- 索引对象是一个由整数、
slice
对象构成的元组
- 索引对象还可以是
np.newaxis
,或者 Python
内置的省略对象 Ellipsis
| a = np.arange(80).reshape(8, 10) |
| 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 维数组的单个元素:
使用切片 slice 对象,索引得到一个子数组:
| array([[13, 14], |
| [33, 34], |
| [53, 54], |
| [73, 74]]) |
如果索引 N 维数组时,使用的元组大小小于 N,NumPy 会自动将后面缺失的维度补成:
,例如,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]]) |
| 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:
| array([[10, 11, 12, 13, 14, 15, 16, 17, 18, 19]]) |
| array([10, 11, 12, 13, 14, 15, 16, 17, 18, 19]) |
最后的:
还可以省略:
| 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 |
Ellipsis 对象 “...” 可以用来省略一些维度,NumPy 会根据具体的索引值,将缺少的维度自动补全:
| b = a.reshape(2, 4, 2, 5) |
可以通过基础索引修改原来数组的值。例如,修改单个值的情况:
| 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]]) |
| 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
的形状能够在数组广播机制下匹配即可。例如,将第二行修改为同一个值:
| array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1]) |
用一维数组给二维数组赋值,将第二三行变成同一个一维数组:
| 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]]) |
| array([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1], |
| [2, 2, 2, 2, 2, 2, 2, 2, 2, 2]]) |
基础索引返回的是原来数组的一个引用,与原来的数组共享同一块内存:
| 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]]) |
修改基础索引赋值的变量,会导致原来的数组也发生变化:
数组支持高级索引操作,高级索引又叫花式索引(Fancy Indexing),与基础索引不同,高级索引返回的结果始终是原来数组的一个复制。
数组的高级索引需要满足:
- 索引对象是非元组的序列。
- 索引对象是整型或者布尔型的数组。
- 索引对象是包含至少一个前两种类型的元素的元组。
可以使用 N 个整型数组或者列表组成的索引值,来索引 N 维数组中的任意元素。例如:
| a = np.array([[1, 2], [3, 4], [5, 6]]) |
| array([[1, 2], |
| [3, 4], |
| [5, 6]]) |
在维度 0 使用列表索引位置 [0,1,2]
,维度 1 使用列表索引位置 [0,1,0]
,最终得到数组位置在 (0,0)
,(1,1)
和 (2,0)
的三个元素:
再如,有这样一个 4×3 的数组:
| b = np.arange(12).reshape(4, 3) |
| 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]]) |
| array([[ 0, 2], |
| [ 9, 11]]) |
每个维度传入的索引数组的形状为 (2,2)
,因此最后得到的数组形状为 (2,2)
,第一行对应位置 (0,0)
和 (0,2)
,第二行对应位置 (3,0)
和 (3,2)
。
高级索引也支持广播机制,将刚才的索引进行简化:
它们的形状都是 (2,)
,如果对其直接索引,会得到位置 (0,0)
和 (3,2)
的两个元素:
这并不是预期想要的结果。为此,可以将 rows 的形状修改为 (2,1),再进行索引:
| array([[ 0, 2], |
| [ 9, 11]]) |
rows 的形状为 (2,1),cols 的形状为 (2,),两个维度的索引数组形状不对应,NumPy 先通过广播机制,将它们广播为匹配后的形状 (2,2),最终得到与刚才一致的结果。
高级索引与基础索引可以混用,此时,情况会变得十分复杂。例如,基础索引下有:
将 1:3
改成数组 [1, 2]
,得到的结果相同,但是是高级索引:
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[..., ind1, ind2, :]
会得到一个 (10,20,2,3,4,50)
的数组,高级索引的维度 (2,3,4)
替换了对应位置的 (30,40)
:
| a[..., ind, ind, :].shape |
在高级索引与基础索引混用时,使用单个数字的维度会被当作高级索引,因此,索引 a [ind1,...,1] 的形状为 (2,3,4,20,30,40),因为 1 被当作高级索引,广播成了 2×3×4 的大小,导致高级索引的位置不相邻:
还可以用一个与维度大小相等的布尔数组进行索引,并把其中为 True
的位置拿出来。利用逻辑运算时可以得到:
| a = np.array([1, 2, 3, 4, 5, 6]) |
| array([False, False, True, False, False, True]) |