6.12. 选择与重排

归约将数组折叠为一个标量或更低阶的结果。选择则涵盖了决定哪些元素得以保留以及它们落在何处的操作:条件选取、裁剪、排序、查找索引、沿某个轴重新排序。

6.12.1. 条件选取

where() 返回一个数组:在条件为真处取自 x 的元素,否则取自 y。三个操作数会一起广播:

a = np.array([1, 2, 3, 4, 5], dtype=np.float)
np.where(a < 3, a, 0.0)
# array([1.0, 2.0, 0.0, 0.0, 0.0])

这是无需编写 Python 循环就能实现“逐元素 if/else”的正确工具。

clip()maximum(lo, minimum(a, hi)) 的简写——将值饱和到某个范围内:

np.clip(a, 2.0, 4.0)
# array([2.0, 2.0, 3.0, 4.0, 4.0])

maximum()minimum() 接受两个操作数,并返回逐元素较大 / 较小的值:

np.maximum(a, 3.0)
np.minimum(a, np.array([5, 4, 3, 2, 1]))

6.12.2. 查找索引

nonzero() 返回每个非零元素的坐标,并按维度拆分为一个个索引数组。对于二维输入,结果是两个数组组成的元组:第一个保存行索引,第二个保存列索引。将它们按列配对即可得到每个非零位置的 (row, col):

m = np.array([[0, 2, 0],
              [3, 0, 0]], dtype=np.float)
np.nonzero(m)
# (array([0, 1], dtype=uint16), array([1, 0], dtype=uint16))

m 中的非零项是 m[0, 1] = 2m[1, 0] = 3。返回的第一个数组 [0, 1] 给出它们的行索引;第二个数组 [1, 0] 给出它们的列索引。把这两个数组并排读取就能还原出位置 (0, 1)(1, 0)

另有两个归约同样会产生索引:

  • argmin() / argmax() —— 最小 / 最大元素的索引。

  • argsort() —— 一个整数数组,按它能将输入沿给定轴排序(默认为最后一个轴):

    a = np.array([40, 10, 30, 20], dtype=np.uint8)
    idx = np.argsort(a)             # array([1, 3, 2, 0], dtype=uint16)
    a[idx]                          # array([10, 20, 30, 40])
    

    argsort 始终返回 uint16;因此被排序的数组在排序轴上的元素不能超过 65,535 个。

bincount() 统计一维 uint8 / uint16 输入中每个非负整数出现的次数:

histogram = np.bincount(np.array([0, 1, 1, 2, 2, 2], dtype=np.uint8))
# array([1, 2, 3], dtype=uint16)

对于无需编写 Python 循环即可构建小整数像素值的直方图非常有用。

6.12.3. 排序与重排

sort() 返回沿给定轴(默认为最后一个轴)排序后的数组副本。若需原地排序版本,请直接对数组使用 sort():

np.sort(np.array([3, 1, 2], dtype=np.float))
# array([1.0, 2.0, 3.0])

flip() 沿给定轴反转元素顺序(未传入 axis 时反转每个轴):

np.flip(np.array([1, 2, 3, 4]))
# array([4, 3, 2, 1])

roll() 按给定的数量循环移位元素。可用于实现环形缓冲区式的移位寄存器:

np.roll(np.array([1, 2, 3, 4]), 1)
# array([4, 1, 2, 3])

take() 是花式索引的显式形式——在任意索引处选取元素:

a = np.array([10, 20, 30, 40, 50], dtype=np.uint8)
np.take(a, [0, 2, 4])
# array([10, 30, 50], dtype=uint8)

6.12.4. 过滤与结构性编辑

compress() 是布尔索引的显式形式——返回 a 中由布尔条件选出的切片:

a = np.array([10, 20, 30, 40], dtype=np.uint8)
np.compress(a > 15, a)
# array([20, 30, 40], dtype=uint8)

delete() 返回一个删除了给定索引处条目的副本:

a = np.array([10, 20, 30, 40, 50], dtype=np.uint8)
np.delete(a, [1, 3])
# array([10, 30, 50], dtype=uint8)

diff() 返回数组沿某个轴的第 n 阶离散前向差分。用于计算相邻样本之间的一阶变化:

samples = np.array([1, 3, 6, 10, 15], dtype=np.float)
np.diff(samples)
# array([2.0, 3.0, 4.0, 5.0])

6.12.5. 各操作的开销

本页几乎所有函数都会返回一个新分配的数组。有两个例外:

  • sort() 原地排序;自由函数 sort() 返回排序后的副本。

  • take() 接受 out= 关键字,可写入一个已存在的缓冲区。

在每秒运行多次的循环中,应优先使用原地的 sort(),并在其他地方都复用预分配的缓冲区。布尔掩码本身在每次比较运行时都会被分配——应只构建一次掩码并在多个操作间复用,而不是在每次迭代内部重新构建它。