花式索引
到目前为止,我们已经探讨了如何使用简单索引(如 arr[0])和切片(如 arr[:5])访问和修改数组的各个部分。 现在可以使用花式索引,在其中将一组索引传递到数组,以便同时访问或修改多个数组元素。
试用一下:
rand = np.random.RandomState(42)
arr = rand.randint(100, size=10)
print(arr)
输出为:
[51 92 14 71 60 20 82 86 74 74]
假设需要访问三个不同的元素。 使用你目前拥有的工具,你的代码可能类似于以下代码:
[arr[3], arr[7], arr[2]]
输出为:
[71, 86, 14]
利用花式索引,可以传递单个索引列表或数组来执行相同的操作:
ind = [3, 7, 4]
arr[ind]
输出为:
array([71, 86, 60])
花式索引的另一个有用方面是,输出数组的形状反映你提供的索引数组的形状,而不是要访问的数组的形状。 这很方便,因为作为数据科学家,你经常需要以特定方式从数组获取数据,例如将数据传递给机器学习 API。 接下来通过一个示例查看此属性:
ind = np.array([[3, 7],
[4, 5]])
arr[ind]
输出为:
array([[71, 86],
[60, 20]])
arr 是一维数组,但 ind 索引数组是 2 x 2 数组,因此结果会以后者的形状返回。
亲自试一试
如果索引数组大于目标数组,会发生什么情况?
提示(展开以显示)
ind = np.arange(0, 12).reshape((6, 2))
arr(ind)
输出为:
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
in
2 # Hint: you could use a large one-dimensional array or something fancier like ind = np.arange(0, 12). reshape((6, 2))
3 ind = np.arange(0, 12).reshape((6, 2))
----> 4 arr(ind)
TypeError: 'numpy.ndarray' object is not callable
花式索引还适用于多个维度:
arr2 = np.arange(12).reshape((3, 4))
arr2
输出为:
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
与标准索引一样,第一个索引指的是行,第二个索引指的是列:
row = np.array([0, 1, 2])
col = np.array([2, 1, 3])
arr2[row, col]
输出为:
array([ 2, 5, 11])
此处获得的最终结果是什么? 结果数组中的第一个值为 arr2[0, 2],第二个值为 arr2[1, 1],第三个值为 arr2[2, 3]。
花式索引中的索引配对遵循我们之前介绍的所有广播规则。 因此,如果将列向量和行向量组合到索引中,则会获得一个二维结果:
arr2[row[:, np.newaxis], col]
输出为:
array([[ 2, 1, 3],
[ 6, 5, 7],
[10, 9, 11]])
此处,每个行值都与每个列向量匹配,正如我们在算术运算的广播中看到的一样。
亲自试一试
现在试着自行广播。
- 使用
row[:, np.newaxis] * col结果如何? - 那
row[:, np.newaxis] * row呢? -
col[:, np.newaxis] * row呢?
请回顾广播规则。
提示(展开以显示)
请尝试:
row[:, np.newaxis] * col
输出为:
array([[0, 0, 0],
[2, 1, 3],
[4, 2, 6]])
对于:
row[:, np.newaxis] * row
输出为:
array([[0, 0, 0],
[0, 1, 2],
[0, 2, 4]])
而对于:
col[:, np.newaxis] * row
输出为:
array([[0, 2, 4],
[0, 1, 2],
[0, 3, 6]])
关键要点
务必记住,花式索引将返回由索引的广播形状反映的值,而不是要编制索引的数组的形状。
合并的索引
你还可以将花式索引与你已学习的其他索引方案结合使用。 再次考虑 arr2:
print(arr2)
输出为:
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
现在合并花式索引和简单索引:
arr2[2, [2, 0, 1]]
输出为:
array([10, 8, 9])
结果如何? 行 2(第三行)位置 2、0 和 1 的元素。
你还可以将花式索引与切片结合使用:
arr2[1:, [2, 0, 1]]
输出为:
array([[ 6, 4, 5],
[10, 8, 9]])
同样,请考虑获得的输出结果:第一行之后的每行(即第二行和第三行)位置 2、0 和 1 的元素。
还可以将花式索引与掩码结合使用:
mask = np.array([1, 0, 1, 0], dtype=bool)
arr2[row[:, np.newaxis], mask]
输出为:
array([[ 0, 2],
[ 4, 6],
[ 8, 10]])
使用花式索引修改值
当然,花式索引不仅仅用于访问数组的各个部分。 它还用于修改数组的各个部分:
ind = np.arange(10)
arr = np.array([2, 1, 8, 4])
ind[arr] = 99
print(ind)
输出为:
[ 0 99 99 3 99 5 6 7 99 9]
还可以在此处使用 ufunc,并从数组的每个元素中减去 10:
ind[arr] -= 10
print(ind)
输出为:
[ 0 89 89 3 89 5 6 7 89 9]
对此类操作使用重复索引时,请谨慎行事。 它们不一定会得出你期望的结果。 例如:
ind = np.zeros(10)
ind[[0, 0]] = [4, 6]
print(ind)
输出为:
[6. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
4 去哪儿了? 此操作的结果是首先分配 ind[0] = 4,然后分配 ind[0] = 6。 因此结果是 ind[0] 包含值 6。
但并不是每个操作都按你认为的方式重复:
arr = [2, 3, 3, 4, 4, 4]
ind[arr] += 1
ind
输出为:
array([6., 0., 1., 1., 1., 0., 0., 0., 0., 0.])
我们可能期望 ind[3] 包含值 2,ind[4] 包含值 3。 毕竟,这就是每个索引重复的次数。 发生了什么情况?
出现此输出是因为 ind[arr] += 1 实际上是 ind[arr] = ind[arr] + 1 的简写。 计算 ind[arr] + 1,然后将结果分配给 ind 中的索引。 因此,与前面的示例类似,这并不是多次发生的扩大。 这是一个分配,可能会导致潜在的反常结果。
但如果想要重复操作,该怎么办? 为此,请使用 ufuncs 的 at() 方法:
ind = np.zeros(10)
np.add.at(ind, arr, 1)
print(ind)
输出为:
[0. 0. 1. 2. 3. 0. 0. 0. 0. 0.]
亲自试一试
np.subtract.at(ind, arr, 1) 的结果是什么?
提示(展开以显示)
np.subtract.at(ind, arr, 1)
print(ind)
输出为:
[ 0. 0. -2. -4. -6. 0. 0. 0. 0. 0.]
与我们见过的其他 ufuncs 结合使用。
要点
利用花式索引,可以一次选择和操作多个数组成员。 此类编程数据操作在数据科学中很常见:通常情况下,你希望对数据执行的操作也同时要在多个数据点上执行。