nan_policy
的設計規範#
scipy.stats
中的許多函數都有一個名為 nan_policy
的參數,用於決定函數如何處理包含 nan
的資料。在本節中,我們提供 SciPy 開發者指南,說明 nan_policy
的預期使用方式,以確保在將此參數新增至新函數時,我們能維持一致的 API。
基本 API#
nan_policy
參數接受三個可能的字串:'omit'
、'raise'
和 'propagate'
。其含義如下:
nan_policy='omit'
:忽略輸入中出現的nan
。如果輸入包含nan
,則不產生警告 (除非移除nan
值後的等效輸入會產生警告)。例如,對於接受單一陣列並傳回純量值的簡單函數案例 (且暫時忽略可能使用的axis
)func([1.0, 3.0, np.nan, 5.0], nan_policy='omit')
應與以下行為相同
func([1.0, 3.0, 5.0])
更廣泛地說,對於傳回純量值的函數,
func(a, nan_policy='omit')
的行為應與func(a[~np.isnan(a)])
相同。對於將向量轉換為相同大小的新向量,且輸出陣列中的每個條目都取決於輸入陣列中不只對應值的函數[1] (例如
scipy.stats.zscore
、scipy.stats.boxcox
,當lmbda
為 None 時),y = func(a, nan_policy='omit')
應與以下行為相同
nan_mask = np.isnan(a) y = np.empty(a.shape, dtype=np.float64) y[~nan_mask] = func(a[~nan_mask]) y[nan_mask] = np.nan
(一般來說,
y
的 dtype 可能取決於a
以及func
的預期行為)。換句話說,輸入中的 nan 會在輸出中產生對應的 nan,但該 nan 的存在不會影響非 nan 值的計算。用於測試此屬性的單元測試應被用於測試處理
nan_policy
的函數。對於傳回純量值且接受兩個或多個引數,但其值不相關的函數 (例如
scipy.stats.ansari
、scipy.stats.f_oneway
),相同的概念適用於每個輸入陣列。因此func(a, b, nan_policy='omit')
應與以下行為相同
func(a[~np.isnan(a)], b[~np.isnan(b)])
對於具有相關或成對值的輸入 (例如
scipy.stats.pearsonr
、scipy.stats.ttest_rel
),建議的行為是省略所有相關值中任何值為nan
的值。對於具有兩個相關陣列輸入的函數,這表示y = func(a, b, nan_policy='omit')
應與以下行為相同
hasnan = np.isnan(a) | np.isnan(b) # Union of the isnan masks. y = func(a[~hasnan], b[~hasnan])
此類函數的 docstring 應清楚說明此行為。
nan_policy='raise'
:引發ValueError
。nan_policy='propagate'
:將nan
值傳播到輸出。通常,這表示直接執行函數而不檢查nan
,但請參閱以取得可能導致意外輸出的範例。
nan_policy
與 axis
參數結合使用#
這裡沒有什麼令人驚訝的地方——當函數具有 axis
參數時,上述原則仍然適用。假設,例如,func
將一維陣列縮減為純量值,並將 n 維陣列處理為一維陣列的集合,其中 axis
參數指定要沿其應用縮減的軸。如果,例如
func([1, 3, 4]) -> 10.0
func([2, -3, 8, 2]) -> 4.2
func([7, 8]) -> 9.5
func([]) -> -inf
則
func([[ 1, nan, 3, 4],
[ 2, -3, 8, 2],
[nan, 7, nan, 8],
[nan, nan, nan, nan]], nan_policy='omit', axis=-1)
必須給出結果
np.array([10.0, 4.2, 9.5, -inf])
邊緣情況#
實作 nan_policy
參數的函數應優雅地處理輸入陣列中所有值都是 nan
的情況。上述基本原則仍然適用
func([nan, nan, nan], nan_policy='omit')
應與以下行為相同
func([])
實際上,在將 nan_policy
新增至現有函數時,發現該函數尚未以明確定義的方式處理這種情況並不少見,並且可能需要應用一些思考和設計來確保其正常運作。正確的行為 (無論是傳回 nan
、傳回其他值、引發例外,還是其他) 將根據具體情況而定。
為什麼 nan_policy
不適用於 inf
?#
雖然我們在小學學到「無限不是一個數字」,但浮點數值 nan
和 inf
在性質上是不同的。inf
和 -inf
值的行為更像是一般的浮點數值,而不是 nan
。
人們可以將
inf
與其他浮點數值進行比較,它的行為符合預期,例如3 < inf
為 True。在大多數情況下,算術運算在
inf
的情況下「如預期般」運作,例如inf + inf = inf
、-2*inf = -inf
、1/inf = 0
等。許多現有的函數在
inf
的情況下「如預期般」運作:np.log(inf) = inf
、np.exp(-inf) = 0
、np.array([1.0, -1.0, np.inf]).min() = -1.0
等。
因此,雖然 nan
幾乎總是表示「發生了錯誤」或「遺失了某些東西」,但在許多情況下,inf
可以被視為有用的浮點數值。
這也與 NumPy 的 nan
函數不忽略 inf
的行為一致
>>> np.nanmax([1, 2, 3, np.inf, np.nan])
inf
>>> np.nansum([1, 2, 3, np.inf, np.nan])
inf
>>> np.nanmean([8, -np.inf, 9, 1, np.nan])
-inf
如何不實作 nan_policy
#
過去 (以及可能目前),某些 stats
函數透過使用遮罩陣列來遮罩 nan
值,然後使用 mstats
子套件中的函數計算結果來處理 nan_policy
。這種方法的問題在於,遮罩陣列程式碼可能會將 inf
轉換為遮罩值,而這不是我們想要做的 (請參閱上文)。這也表示,如果沒有謹慎處理,傳回值將會是遮罩陣列,如果使用者傳入的是常規陣列,這可能會讓他們感到驚訝。
腳註