花式索引

已完成

到目前为止,我们已经探讨了如何使用简单索引(如 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 结合使用。

要点

利用花式索引,可以一次选择和操作多个数组成员。 此类编程数据操作在数据科学中很常见:通常情况下,你希望对数据执行的操作也同时要在多个数据点上执行。