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 class 节中进行了更详细的描述.

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)

有关掩码数组创建方法的完整讨论,请参见 Constructing masked arrays 一节.

使用 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 函数.如果 x 是屏蔽数组,则 getmask(x) 输出 x 的掩码,否则输出特殊值 nomask .如果 x 是屏蔽数组,则 getmaskarray(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 ),要么是特殊值 masked (如果掩码的相应条目为 True ):

>>> 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 对象;如果至少有一个字段被掩盖,则返回一个与初始数组具有相同 dtype 的 0d 被掩盖数组.

>>> 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 的名称,其参数及其域的 3 元素元组),则将处理该上下文,并且当相应的输入超出有效域时,输出被掩盖数组的条目将被掩盖:

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

数值运算#

可以轻松地执行数值运算,而无需担心缺失值,除以零,负数的平方根等问题.

.. try_examples::
>>> 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