NEP 24 — 遺失資料功能 - NEP 12 的替代方案 1#
- 作者:
Nathaniel J. Smith <njs@pobox.com>、Matthew Brett <matthew.brett@gmail.com>
- 狀態:
已延遲
- 類型:
標準追蹤
- 建立於:
2011-06-30
摘要#
背景:此 NEP 是作為 NEP 12 的替代方案撰寫的,在撰寫本文時,NEP 12 的實作已合併到 NumPy 主分支中。
此 NEP 的原則是根據以下項目分離遮罩和遺失值的 API:
遮罩陣列的目前實作 (NEP 12)
此提案。
此討論僅關於 API,而非實作。
詳細描述#
理由#
此 NEP 的目的是定義兩個介面 – 一個用於處理「遺失值」,另一個用於處理「遮罩陣列」。
普通值就像整數或浮點數。遺失值是因某些原因而無法取得的普通值的預留位置。例如,在處理統計資料時,我們經常建立表格,其中每一列代表一個項目,每一欄代表該項目的屬性。例如,我們可以找一群人,並記錄每個人的身高、年齡、教育程度和收入,然後將這些值放入表格中。但是,我們後來發現我們的研究助理搞砸了,忘記記錄我們其中一個人的年齡。我們也可以丟棄其餘的資料,但這將會很浪費;即使是不完整的列,對於某些分析來說仍然完全可用(例如,我們可以計算身高和收入的相關性)。處理此問題的傳統方法是將一些特定的無意義值放入遺失資料中,例如,將此人的年齡記錄為 0。但是,這非常容易出錯;我們稍後可能會忘記這些特殊值,同時執行其他分析,並驚訝地發現嬰兒的收入高於青少年。(在這種情況下,解決方案是僅省略所有未記錄年齡的項目,但這不是通用的解決方案;許多分析需要更聰明的方法來處理遺失值。)因此,我們不使用像 0 這樣的普通值,而是定義一個特殊的「遺失」值,寫為「NA」,表示「不可用」。
因此,遺失值具有以下屬性:與任何其他值一樣,它們必須由陣列的 dtype 支援 – 您無法在 dtype=int32 的陣列中儲存浮點數,也無法在其中儲存 NA。您需要一個 dtype=NAint32 或類似的陣列(確切語法待定)。否則,它們的行為與任何其他值完全相同。特別是,您可以對它們套用算術函數等等。預設情況下,任何以 NA 作為引數的函數始終也會傳回 NA,無論其他引數的值為何。這確保了如果我們嘗試計算收入與年齡的相關性,我們將得到「NA」,意思是「假設某些條目可能是任何值,則答案也可能是任何值」。這提醒我們花一點時間思考應該如何重新措辭我們的問題,使其更有意義。為了方便您在確實決定只想知道已知年齡和收入之間的相關性時,您可以透過在函數呼叫中新增單一引數來啟用此行為。
對於浮點數計算,NA 和 NaN 的行為(幾乎?)相同。但是它們代表不同的事物 – NaN 代表無效計算,例如 0/0,NA 代表不可用的值 – 區分這些事物很有用,因為在某些情況下應以不同的方式處理它們。(例如,插補程序應將 NA 替換為插補值,但可能應保持 NaN 不變。)而且無論如何,我們不能將 NaN 用於整數、字串或布林值,因此我們仍然需要 NA,一旦我們為所有這些類型提供 NA 支援,我們不妨也為了保持一致性而支援浮點數。
從概念上講,遮罩陣列是一個普通的矩形 numpy 陣列,在其上放置了任意形狀的遮罩。結果本質上是矩形陣列的非矩形視圖。原則上,您可以使用遮罩陣列完成的任何操作,也可以透過顯式保留常規陣列和布林遮罩陣列,並使用 numpy 索引將它們組合以進行每個操作來完成,但是當您需要在陣列的遮罩視圖上執行複雜操作時,將它們組合到單一結構中會方便得多,同時仍然能夠以通常的方式操作遮罩。因此,遮罩會透過索引保留,並且函數通常會將遮罩掉的值視為它們甚至不是陣列的一部分。(也許這是一個好的啟發法:如果長度為 4 的陣列中的最後一個值被遮罩掉,則它的行為就像普通的長度為 3 的陣列一樣,只要您不更改遮罩即可。)當然,除非您可以隨時以任意方式自由操作遮罩;它只是一個標準的 numpy 陣列。
在某些簡單的情況下,可以使用這些工具中的任何一種來完成工作 – 或完全使用其他工具,例如使用指定的替代值(年齡 = 0)、單獨的遮罩陣列等。但是,遺失值旨在特別有助於遺失性是資料的內在特徵的情況 – 其中存在一個應該存在的值,如果它確實存在,則它會意味著某些特定的含義,但它不存在。遮罩陣列旨在特別有助於我們只想暫時忽略某些確實存在的資料,或者通常在我們需要處理具有非矩形形狀的資料時(例如,如果您在覆蓋在圓形瓊脂培養皿上的網格上的每個點進行一些測量,那麼落在培養皿外部的點不是遺失的測量值,它們只是沒有意義)。
初始化#
首先,遺失值可以設定並顯示為 np.NA, NA
>>> np.array([1.0, 2.0, np.NA, 7.0], dtype='NA[f8]')
array([1., 2., NA, 7.], dtype='NA[<f8]')
由於初始化沒有歧義,因此可以在不使用 NA dtype 的情況下編寫
>>> np.array([1.0, 2.0, np.NA, 7.0])
array([1., 2., NA, 7.], dtype='NA[<f8]')
遮罩值可以設定並顯示為 np.IGNORE, IGNORE
>>> np.array([1.0, 2.0, np.IGNORE, 7.0], masked=True)
array([1., 2., IGNORE, 7.], masked=True)
由於初始化沒有歧義,因此可以在不使用 masked=True
的情況下編寫
>>> np.array([1.0, 2.0, np.IGNORE, 7.0])
array([1., 2., IGNORE, 7.], masked=True)
Ufuncs#
預設情況下,NA 值會傳播
>>> na_arr = np.array([1.0, 2.0, np.NA, 7.0])
>>> np.sum(na_arr)
NA('float64')
除非設定 skipna
旗標
>>> np.sum(na_arr, skipna=True)
10.0
預設情況下,遮罩不會傳播
>>> masked_arr = np.array([1.0, 2.0, np.IGNORE, 7.0])
>>> np.sum(masked_arr)
10.0
除非設定 propmask
旗標
>>> np.sum(masked_arr, propmask=True)
IGNORE
陣列可以被遮罩,並且包含 NA 值
>>> both_arr = np.array([1.0, 2.0, np.IGNORE, np.NA, 7.0])
在預設情況下,行為很明顯
>>> np.sum(both_arr)
NA('float64')
使用 skipna=True
時該怎麼做也很明顯
>>> np.sum(both_arr, skipna=True)
10.0
>>> np.sum(both_arr, skipna=True, propmask=True)
IGNORE
為了打破 NA 和 MSK 之間的僵局,NA 更容易傳播
>>> np.sum(both_arr, propmask=True)
NA('float64')
賦值#
在 NA 情況下很明顯
>>> arr = np.array([1.0, 2.0, 7.0])
>>> arr[2] = np.NA
TypeError('dtype does not support NA')
>>> na_arr = np.array([1.0, 2.0, 7.0], dtype='NA[f8]')
>>> na_arr[2] = np.NA
>>> na_arr
array([1., 2., NA], dtype='NA[<f8]')
遮罩情況下的直接賦值是神奇且令人困惑的,因此僅透過遮罩發生
>>> masked_array = np.array([1.0, 2.0, 7.0], masked=True)
>>> masked_arr[2] = np.NA
TypeError('dtype does not support NA')
>>> masked_arr[2] = np.IGNORE
TypeError('float() argument must be a string or a number')
>>> masked_arr.visible[2] = False
>>> masked_arr
array([1., 2., IGNORE], masked=True)
版權#
本文檔已置於公共領域。