6.11. 歸約運算¶
歸約運算 會沿著一個或多個軸,透過求和、求平均、取最小值等方式將陣列收合。每個歸約運算都是針對整個陣列的單次函式庫呼叫,遠比等效的 Python 迴圈快得多。numpy 涵蓋了日常常用的這些運算:
sum()-- 所有元素的總和mean()-- 算術平均值(總和除以元素個數)std()-- 標準差,ddof=可調整除數(N - ddof)median()-- 元素排序後的中間值(第 50 百分位數)
6.11.1. 不使用 axis 關鍵字¶
在不指定 axis= 的情況下呼叫時,歸約運算會傳回涵蓋整個陣列的純量值:
a = np.array([1, 2, 3, 4], dtype=np.float)
np.sum(a) # 10.0
np.mean(a) # 2.5
np.std(a) # 1.118...
np.median(a) # 2.5
b = np.array([40, 10, 30, 20], dtype=np.float)
np.max(b) # 40.0
np.argmax(b) # 0 (index of the maximum)
6.11.2. 使用 axis 關鍵字¶
axis= 會收縮指定的某一個軸,並保留其餘各軸不變。其結果是一個比輸入低一階的陣列:
m = np.arange(12, dtype=np.float).reshape((3, 4))
np.sum(m) # 66.0 - scalar
np.sum(m, axis=0) # length-4 - column sums
np.sum(m, axis=1) # length-3 - row sums
相同的形狀規則適用於每一種歸約運算:axis=0 收合第一個軸,axis=1 收合第二個軸,依此類推。舉例來說,沿著列求平均值/標準差會寫成 np.mean(m, axis=1) 和 np.std(m, axis=1)。其結果的長度為 另一個 軸的長度。
keepdims=True 關鍵字會把被收縮的軸以長度 1 的形式保留在原處,而不是將其捨棄。當歸約後的結果需要重新對原始陣列進行廣播時,這個差別就很重要:keepdims 會保留階數,使廣播規則能夠逐軸對齊。
從每一列減去該列的平均值,是其典型用途:
m = np.arange(12, dtype=np.float).reshape((3, 4))
row_means = np.mean(m, axis=1, keepdims=True)
# row_means has shape (3, 1)
centred = m - row_means
# (3, 4) - (3, 1) -> (3, 4), each row centred on its own mean
若不使用 keepdims,np.mean(m, axis=1) 會傳回形狀為 (3,) 的一維結果。廣播 (3, 4) - (3,) 時,在階數前置補齊後會把 (3,) 對齊成 (1, 3),這與 (3, 4) 不相容:最後一個軸不一致(4 對 3),且兩者皆非 1,因此 numpy 會引發 ValueError。keepdims=True 正是讓這個減法保持有效的關鍵。
6.11.3. 記憶體布局很重要¶
結合 形狀與步幅 所介紹的列優先(row-major)布局,沿著 最後一個 軸進行歸約是成本最低的情況。歸約運算會沿著資料區塊的儲存方向逐一走訪,不需要在列與列之間跳躍:
m = np.arange(2000, dtype=np.float).reshape((2, 1000))
np.sum(m, axis=1) # cheap - long axis is the inner one
np.sum(m, axis=0) # has to jump rows on every step
當應用程式可以選擇如何安排緩衝區的布局時,請把較長的軸放在最後,使沿著它進行的歸約能以快速的方向執行。
6.11.4. 以可迭代物件作為輸入¶
大多數歸約運算可以接受 Python 的可迭代物件(list、range、tuple)來取代 ndarray。這種便利性會因為隱含的轉換而付出幾微秒的代價——這在迴圈中會快速累積。當同一份資料需要被多次歸約時,請先一次性建立 ndarray,再將其傳遞使用。