numpy.ma 模組#

原理#

遮罩陣列是可能具有遺失或無效條目的陣列。numpy.ma 模組提供了一個幾乎可替代 NumPy 的方案,它支援帶有遮罩的資料陣列。

什麼是遮罩陣列?#

在許多情況下,資料集可能不完整或被無效資料的存在所污染。例如,感測器可能未能記錄資料,或記錄了無效值。numpy.ma 模組提供了一種解決此問題的便捷方法,即引入遮罩陣列。

遮罩陣列是標準 numpy.ndarray 和遮罩的組合。遮罩可以是 nomask,表示關聯陣列的任何值都無效;或者是一個布林值陣列,用於確定關聯陣列的每個元素是否有效。當遮罩的元素為 False 時,關聯陣列的對應元素有效,並稱為未遮罩。當遮罩的元素為 True 時,關聯陣列的對應元素稱為已遮罩(無效)。

該套件確保遮罩條目不被用於計算中。

作為一個範例,讓我們考慮以下資料集

>>> import numpy as np
>>> import numpy.ma as ma
>>> x = np.array([1, 2, 3, -1, 5])

我們希望將第四個條目標記為無效。最簡單的方法是建立一個遮罩陣列

>>> mx = ma.masked_array(x, mask=[0, 0, 0, 1, 0])

我們現在可以計算資料集的平均值,而不將無效資料納入考量

>>> mx.mean()
2.75

numpy.ma 模組#

numpy.ma 模組的主要功能是 MaskedArray 類別,它是 numpy.ndarray 的子類別。該類別及其屬性和方法在 MaskedArray 類別 章節中有更詳細的描述。

numpy.ma 模組可以作為 numpy 的附加元件使用

>>> import numpy as np
>>> import numpy.ma as ma

要建立一個第二個元素無效的陣列,我們可以這樣做

>>> y = ma.array([1, 2, 3], mask = [0, 1, 0])

要建立一個遮罩陣列,其中所有接近 1.e20 的值都無效,我們可以這樣做

>>> z = ma.masked_values([1.0, 1.e20, 3.0, 4.0], 1.e20)

有關遮罩陣列建立方法的完整討論,請參閱 建構遮罩陣列 章節。

使用 numpy.ma#

建構遮罩陣列#

有幾種方法可以建構遮罩陣列。

  • 第一種可能性是直接調用 MaskedArray 類別。

  • 第二種可能性是使用兩個遮罩陣列建構子,arraymasked_array

    array(data[, dtype, copy, order, mask, ...])

    一個可能具有遮罩值的陣列類別。

    masked_array

    MaskedArray 的別名

  • 第三種選擇是取得現有陣列的檢視。在這種情況下,如果陣列沒有具名欄位,則檢視的遮罩會設定為 nomask;否則,檢視的遮罩會設定為與陣列結構相同的布林值陣列。

    >>> import numpy as np
    >>> x = np.array([1, 2, 3])
    >>> x.view(ma.MaskedArray)
    masked_array(data=[1, 2, 3],
                mask=False,
          fill_value=999999)
    >>> x = np.array([(1, 1.), (2, 2.)], dtype=[('a',int), ('b', float)])
    >>> x.view(ma.MaskedArray)
    masked_array(data=[(1, 1.0), (2, 2.0)],
                mask=[(False, False), (False, False)],
          fill_value=(999999, 1e+20),
                dtype=[('a', '<i8'), ('b', '<f8')])
    
  • 另一種可能性是使用以下任何函數

    asarray(a[, dtype, order])

    將輸入轉換為給定資料型別的遮罩陣列。

    asanyarray(a[, dtype])

    將輸入轉換為遮罩陣列,保留子類別。

    fix_invalid(a[, mask, copy, fill_value])

    返回輸入,其中無效資料被遮罩並替換為填充值。

    masked_equal(x, value[, copy])

    遮罩陣列中等於給定值的位置。

    masked_greater(x, value[, copy])

    遮罩陣列中大於給定值的位置。

    masked_greater_equal(x, value[, copy])

    遮罩陣列中大於或等於給定值的位置。

    masked_inside(x, v1, v2[, copy])

    遮罩陣列中位於給定區間內的位置。

    masked_invalid(a[, copy])

    遮罩陣列中出現無效值(NaN 或 inf)的位置。

    masked_less(x, value[, copy])

    遮罩陣列中小於給定值的位置。

    masked_less_equal(x, value[, copy])

    遮罩陣列中小於或等於給定值的位置。

    masked_not_equal(x, value[, copy])

    遮罩陣列中等於給定值的位置。

    masked_object(x, value[, copy, shrink])

    遮罩陣列 x 中資料完全等於 value 的位置。

    masked_outside(x, v1, v2[, copy])

    遮罩陣列中位於給定區間外的位置。

    masked_values(x, value[, rtol, atol, copy, ...])

    使用浮點數相等性進行遮罩。

    masked_where(condition, a[, copy])

    遮罩陣列中滿足條件的位置。

存取資料#

遮罩陣列的底層資料可以通過幾種方式存取

  • 通過 data 屬性。輸出是陣列的檢視,作為 numpy.ndarray 或其子類別之一,具體取決於遮罩陣列建立時底層資料的型別。

  • 通過 __array__ 方法。輸出然後是一個 numpy.ndarray

  • 通過直接取得遮罩陣列的檢視,作為 numpy.ndarray 或其子類別之一(實際上是使用 data 屬性所做的事情)。

  • 通過使用 getdata 函數。

如果某些條目被標記為無效,這些方法都不完全令人滿意。作為一般規則,當需要陣列的表示形式,且不包含任何遮罩條目時,建議使用 filled 方法填充陣列。

存取遮罩#

遮罩陣列的遮罩可以通過其 mask 屬性存取。我們必須記住,遮罩中的 True 條目表示無效資料。

另一種可能性是使用 getmaskgetmaskarray 函數。getmask(x) 輸出 x 的遮罩,如果 x 是一個遮罩陣列,否則輸出特殊值 nomaskgetmaskarray(x) 輸出 x 的遮罩,如果 x 是一個遮罩陣列。如果 x 沒有無效條目或不是遮罩陣列,該函數會輸出一個由 False 組成的布林值陣列,其中元素的數量與 x 相同。

僅存取有效條目#

要僅檢索有效條目,我們可以將遮罩的反轉用作索引。遮罩的反轉可以使用 numpy.logical_not 函數或直接使用 ~ 運算子來計算

>>> import numpy as np
>>> x = ma.array([[1, 2], [3, 4]], mask=[[0, 1], [1, 0]])
>>> x[~x.mask]
masked_array(data=[1, 4],
               mask=[False, False],
         fill_value=999999)

檢索有效資料的另一種方法是使用 compressed 方法,該方法返回一個一維 ndarray(或其子類別之一,具體取決於 baseclass 屬性的值)

>>> x.compressed()
array([1, 4])

請注意,compressed 的輸出始終為 1D。

修改遮罩#

遮罩條目#

將遮罩陣列的一個或多個特定條目標記為無效的建議方法是將特殊值 masked 指派給它們

>>> x = ma.array([1, 2, 3])
>>> x[0] = ma.masked
>>> x
masked_array(data=[--, 2, 3],
             mask=[ True, False, False],
       fill_value=999999)
>>> y = ma.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
>>> y[(0, 1, 2), (1, 2, 0)] = ma.masked
>>> y
masked_array(
  data=[[1, --, 3],
        [4, 5, --],
        [--, 8, 9]],
  mask=[[False,  True, False],
        [False, False,  True],
        [ True, False, False]],
  fill_value=999999)
>>> z = ma.array([1, 2, 3, 4])
>>> z[:-2] = ma.masked
>>> z
masked_array(data=[--, --, 3, 4],
             mask=[ True,  True, False, False],
       fill_value=999999)

第二種可能性是直接修改 mask,但不建議這樣使用。

注意

當使用簡單、非結構化的資料型別建立新的遮罩陣列時,遮罩最初會設定為特殊值 nomask,它大致對應於布林值 False。嘗試設定 nomask 的元素將會失敗,並出現 TypeError 例外,因為布林值不支援項目指派。

陣列的所有條目可以通過將 True 指派給遮罩來一次性遮罩

>>> import numpy.ma as ma
>>> x = ma.array([1, 2, 3], mask=[0, 0, 1])
>>> x.mask = True
>>> x
masked_array(data=[--, --, --],
             mask=[ True,  True,  True],
       fill_value=999999,
            dtype=int64)

最後,可以通過將布林值序列指派給遮罩來遮罩和/或取消遮罩特定條目

>>> x = ma.array([1, 2, 3])
>>> x.mask = [0, 1, 0]
>>> x
masked_array(data=[1, --, 3],
             mask=[False,  True, False],
       fill_value=999999)

取消遮罩條目#

要取消遮罩一個或多個特定條目,我們只需將一個或多個新的有效值指派給它們

>>> import numpy.ma as ma
>>> x = ma.array([1, 2, 3], mask=[0, 0, 1])
>>> x
masked_array(data=[1, 2, --],
             mask=[False, False,  True],
       fill_value=999999)
>>> x[-1] = 5
>>> x
masked_array(data=[1, 2, 5],
             mask=[False, False, False],
       fill_value=999999)

注意

如果遮罩陣列具有遮罩,則通過直接指派取消遮罩條目將會靜默失敗,如 hardmask 屬性所示。引入此功能是為了防止覆寫遮罩。要強制取消遮罩陣列具有硬遮罩的條目,必須先使用 soften_mask 方法軟化遮罩,然後再進行分配。可以使用 harden_mask 重新硬化,如下所示

>>> import numpy.ma as ma
>>> x = ma.array([1, 2, 3], mask=[0, 0, 1], hard_mask=True)
>>> x
masked_array(data=[1, 2, --],
               mask=[False, False,  True],
         fill_value=999999)
>>> x[-1] = 5
>>> x
masked_array(data=[1, 2, --],
               mask=[False, False,  True],
         fill_value=999999)
>>> x.soften_mask()
masked_array(data=[1, 2, --],
               mask=[False, False,  True],
         fill_value=999999)
>>> x[-1] = 5
>>> x
masked_array(data=[1, 2, 5],
               mask=[False, False, False],
         fill_value=999999)
>>> x.harden_mask()
masked_array(data=[1, 2, 5],
               mask=[False, False, False],
         fill_value=999999)

要取消遮罩遮罩陣列的所有遮罩條目(前提是遮罩不是硬遮罩),最簡單的解決方案是將常數 nomask 指派給遮罩

>>> import numpy.ma as ma
>>> x = ma.array([1, 2, 3], mask=[0, 0, 1])
>>> x
masked_array(data=[1, 2, --],
             mask=[False, False,  True],
       fill_value=999999)
>>> x.mask = ma.nomask
>>> x
masked_array(data=[1, 2, 3],
             mask=[False, False, False],
       fill_value=999999)

索引和切片#

由於 MaskedArraynumpy.ndarray 的子類別,因此它繼承了其索引和切片機制。

當存取沒有具名欄位的遮罩陣列的單個條目時,如果遮罩的對應條目為 False,則輸出為純量;如果遮罩的對應條目為 True,則輸出為特殊值 masked

>>> import numpy.ma as ma
>>> x = ma.array([1, 2, 3], mask=[0, 0, 1])
>>> x[0]
1
>>> x[-1]
masked
>>> x[-1] is ma.masked
True

如果遮罩陣列具有具名欄位,則當沒有任何欄位被遮罩時,存取單個條目會返回 numpy.void 物件;如果至少有一個欄位被遮罩,則返回與初始陣列具有相同資料型別的 0 維遮罩陣列。

>>> import numpy.ma as ma
>>> y = ma.masked_array([(1,2), (3, 4)],
...                mask=[(0, 0), (0, 1)],
...               dtype=[('a', int), ('b', int)])
>>> y[0]
(1, 2)
>>> y[-1]
(3, --)

當存取切片時,輸出是一個遮罩陣列,其 data 屬性是原始資料的檢視,其遮罩可以是 nomask(如果原始陣列中沒有無效條目),也可以是原始遮罩的對應切片的檢視。需要檢視以確保將遮罩的任何修改傳播到原始遮罩。

>>> import numpy.ma as ma
>>> x = ma.array([1, 2, 3, 4, 5], mask=[0, 1, 0, 0, 1])
>>> mx = x[:3]
>>> mx
masked_array(data=[1, --, 3],
             mask=[False,  True, False],
       fill_value=999999)
>>> mx[1] = -1
>>> mx
masked_array(data=[1, -1, 3],
             mask=[False, False, False],
       fill_value=999999)
>>> x.mask
array([False, False, False, False,  True])
>>> x.data
array([ 1, -1,  3,  4,  5])

存取具有結構化資料型別的遮罩陣列的欄位會返回 MaskedArray

遮罩陣列上的運算#

遮罩陣列支援算術和比較運算。盡可能不處理遮罩陣列的無效條目,這表示對應的 data 條目在運算前後應該相同。

警告

我們需要強調,此行為可能不系統化,在某些情況下,遮罩資料可能會受到運算的影響,因此使用者不應依賴此資料保持不變。

numpy.ma 模組帶有大多數 ufunc 的特定實作。具有有效域(例如 logdivide)的單元和二元函數在輸入被遮罩或超出有效域時,會返回 masked 常數

>>> import numpy.ma as ma
>>> ma.log([-1, 0, 1, 2])
masked_array(data=[--, --, 0.0, 0.6931471805599453],
             mask=[ True,  True, False, False],
       fill_value=1e+20)

遮罩陣列也支援標準 NumPy ufunc。輸出然後是一個遮罩陣列。單元 ufunc 的結果在輸入被遮罩的任何位置都被遮罩。二元 ufunc 的結果在任何輸入被遮罩的任何位置都被遮罩。如果 ufunc 也返回可選的上下文輸出(包含 ufunc 名稱、其引數及其域的三元素元組),則會處理上下文,並且輸出遮罩陣列的條目在對應輸入超出有效域的任何位置都被遮罩

>>> import numpy.ma as ma
>>> x = ma.array([-1, 1, 0, 2, 3], mask=[0, 0, 0, 0, 1])
>>> np.log(x)
masked_array(data=[--, 0.0, --, 0.6931471805599453, --],
             mask=[ True, False,  True, False,  True],
       fill_value=1e+20)

範例#

具有給定值表示遺失資料的資料#

讓我們考慮一個元素列表 x,其中值 -9999. 代表遺失資料。我們希望計算資料的平均值和異常向量(與平均值的偏差)

>>> import numpy.ma as ma
>>> x = [0.,1.,-9999.,3.,4.]
>>> mx = ma.masked_values (x, -9999.)
>>> print(mx.mean())
2.0
>>> print(mx - mx.mean())
[-2.0 -1.0 -- 1.0 2.0]
>>> print(mx.anom())
[-2.0 -1.0 -- 1.0 2.0]

填補遺失資料#

假設現在我們希望列印相同的資料,但將遺失值替換為平均值。

>>> import numpy.ma as ma
>>> mx = ma.masked_values (x, -9999.)
>>> print(mx.filled(mx.mean()))
[0.  1.  2.  3.  4.]

數值運算#

可以輕鬆執行數值運算,無需擔心遺失值、除以零、負數的平方根等問題。

>>> import numpy.ma as ma
>>> x = ma.array([1., -1., 3., 4., 5., 6.], mask=[0,0,0,0,1,0])
>>> y = ma.array([1., 2., 0., 4., 5., 6.], mask=[0,0,0,0,0,1])
>>> print(ma.sqrt(x/y))
[1.0 -- -- 1.0 -- --]

輸出的四個值是無效的:第一個來自對負數取平方根,第二個來自除以零,最後兩個是輸入被遮罩的位置。

忽略極端值#

讓我們考慮一個介於 0 和 1 之間的浮點數陣列 d。我們希望計算 d 值的平均值,同時忽略範圍 [0.2, 0.9] 之外的任何資料

>>> import numpy as np
>>> import numpy.ma as ma
>>> d = np.linspace(0, 1, 20)
>>> print(d.mean() - ma.masked_outside(d, 0.2, 0.9).mean())
-0.05263157894736836