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])

這是實作「逐元素 if/else」而不需撰寫 Python 迴圈的合適工具。

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() 會傳回每個非零元素的座標,並依維度各自拆分為一個索引陣列。對於二維輸入,其結果是一個包含兩個陣列的 tuple:第一個保存列索引,第二個保存欄索引。將它們逐欄配對,即可得到每個非零位置的 (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() 是花式索引(fancy indexing)的明確形式——可挑選任意索引位置的元素:

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(),並在其他所有地方重複使用預先配置的緩衝區。布林遮罩本身在每次比較執行時都會被配置——請一次性建立遮罩並在各項運算間重複使用,而不要在每次迭代內重新建立它。