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` 类。
第二个可能性是使用两个掩码数组构造函数,`array` 和 `masked_array`。
array(data[, dtype, copy, order, mask, ...])
一个可能包含被掩码值的数组类。
别名:`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, order])
将输入转换为带掩码数组,保留子类。
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])
当数据与 value 完全相等时掩码数组 x。
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` 条目表示无效数据。
另一种可能性是使用 `getmask` 和 `getmaskarray` 函数。 `getmask(x)` 在 `x` 是掩码数组时输出 `x` 的掩码,否则输出特殊值 `nomask`。 `getmaskarray(x)` 在 `x` 是掩码数组时输出 `x` 的掩码。如果 `x` 没有无效条目或不是掩码数组,则函数会输出一个包含与 `x` 相同数量元素的 `False` 布尔数组。
仅访问有效条目#
要仅检索有效条目,我们可以使用掩码的逆作为索引。掩码的逆可以通过 `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` 的输出始终是一维的。
修改掩码#
掩码条目#
将一个或多个特定条目标记为无效的推荐方法是将特殊值 `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)
索引和切片#
由于 `MaskedArray` 是 `numpy.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 的特定实现。具有有效域的一元和二元函数(例如 `log` 或 `divide`)将在输入被掩码或超出有效域时返回 `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 ufuncs。输出则是一个掩码数组。一元 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 -- --]
输出中有四个值无效:第一个来自负数的平方根,第二个来自零除,最后两个来自输入被掩码的地方。
忽略极端值#
让我们考虑一个浮点数数组 `d`,其值介于 0 和 1 之间。我们希望计算 `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