numpy.ma
numpy.ma模块
基本原理
当数组元素包括缺失值或异常值时,该数组被称为掩码数组。numpy.ma
模块的工作方式可以这么来解释:支持数值数组中包含掩码元素。
什么是掩码数组呢?
在很多情况下,数据集可能是不完整或存在无效数据的。例如,一个传感器对于某个数值可能有以下两种存储情况:存储失败、存入一个无效数据。numpy.ma
模块通过引入掩码数组,为这种问题的解决提供了一种便捷的方法。
掩码数组是将标准的多维数组numpy.ndarray
和掩码相结合。掩码要么是nomask
,表示与该掩码有关数组的所有值都是有效的。要么是一个布尔值数组,用于确定关联数组的每个元素值是否有效。当掩码中某个元素值为False,那么关联数组的对应元素是有效的,即被认为是未掩码的。当掩码中某个元素值为True,那么关联数组的对应元素是无效的,即被认为是掩码的。
这个包可以确保掩码项不参与计算。
为了让大家有一个直观的认识,我们给出下例:
import numpy as np
import numpy.ma as ma
x = np.array([1, 2, 3, -1, 5])
我们想要将x数组中第四个值标记为无效数据。最便捷的方法是创建一个掩码数组:
mx = ma.masked_array(x, mask=[0, 0, 0, 1, 0])
现在,我们可以在不考虑-1
这个异常值的情况下,计算数组x的均值:
print('仅仅计算[1, 2, 3, 5]的均值,计算结果为:{}'.format(mx.mean()))
仅仅计算[1, 2, 3, 5]的均值,计算结果为: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
类。 - 第二种方式是使用两种掩码数组构造函数,
array
和masked_array
。array
(data[, dtype, copy, order, mask, …]): 定义了掩码值的数组类masked_array
: 和MaskedArray
一样
- 第三种方式是获取现有数组的视图。在这种情况下,如果数组没有命名字段,或者没有与数组结构相同的布尔数组,则将视图的掩码设置为
nomask
。
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, 1.e+20),
dtype=[(‘a’, ‘<i4’), (‘b’, ‘<f8’)])
- 以下函数也可以创建掩码数组:
函数名 | 功能 |
---|---|
asarray (a[, dtype, order]) | 基于给定的数值类型将输入数据转换为掩码数组 |
asanyarray (a[, dtype]) | 不改变子类的前提下,将输入数据转换为掩码数组 |
fix_invalid (a[, mask, copy, fill_value]) | 将输入数组中的无效元素用填充值进行替代 |
masked_equal (x, value[, copy]) | 对数组中等于value的值进行掩码操作 |
masked_greater (x, value[, copy]) | 对数组中大于value的值进行掩码操作 |
masked_greater_equal (x, value[, copy]) | 对数组中大于等于value的值进行掩码操作 |
masked_inside (x, v1, v2[, copy]) | 对数组中落在给定区间的值进行掩码操作 |
masked_invalid (a[, copy]) | 对数组中无效数据(例如NaN或inf)进行掩码操作 |
masked_less (x, value[, copy]) | 对数组中小于value的值进行掩码操作 |
masked_less_equal (x, value[, copy]) | 对数组中小于等于value的值进行掩码操作 |
masked_not_equal (x, value[, copy]) | 对数组中不等于value的值进行掩码操作 |
masked_object (x, value[, copy, shrink]) | 对数组(元素为’cats’等对象)中等于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
表示无效数据。使用getmask
和getmaskarray
函数也可以获取到掩码。如果x
为掩码数组getmask(x)
将返回x
的掩码,否则返回nomask
。如果x
为掩码数组getmaskarray(x)
将返回x
的掩码。如果x
没有无效值或者其不为掩码数组,该函数返回len(x)
个False
组成的布尔型数组。
仅获取有效值
为了检索数组中的有效值,我们可以使用掩码取反作为索引。掩码取反操作可以使用函数numpy.logical_not
来完成,或者仅仅使用~
操作符:
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
的返回值通常为1维。
修改掩码
屏蔽一个条目
将一个掩码数组中的一个或多个特定项标记为无效的推荐方法是将掩码值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
。
一个数组中的所有元素可以一并设置其掩码为True
:
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=int32)
可以通过对布尔值序列进行赋值来确定数组中的哪些元素用掩码表示:
x = ma.array([1, 2, 3])
x.mask = [0, 1, 0]
x
masked_array(data=[1, --, 3],
mask=[False, True, False],
fill_value=999999)
对屏蔽条目解除屏蔽
我们可以通过对屏蔽条目赋予新的有效值来解除其屏蔽:
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)
注意:
当掩码数组属性为hard mask
时,可能不能有效地对其解除屏蔽。这个特性是为了防止用户对掩码值进行覆盖。要想取消数组的hard_mask
属性,必须在解除屏蔽前先对其利用soften_mask
方法进行软化。当然,修改完毕之后也可以使用harden_mask
属性防止掩码部分被修改:
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() # 软化
x[-1] = 5
x
masked_array(data=[1, 2, 5],
mask=[False, False, False],
fill_value=999999)
x[-1] = ma.masked
xx.harden_mask() # 防止掩码部分修改
x[-1] = 2
x
masked_array(data=[1, 2, --],
mask=[False, False, True],
fill_value=999999)
对掩码数组(非hard mask
)所有屏蔽部分解屏蔽的最简单方法是给mask
赋常数nomask
:
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
):
x = ma.array([1, 2, 3], mask=[0, 0, 1])
x[0]
1
x[-1]
masked
x[-1] is ma.masked
True
如果掩码数组有命名空间,则从中获取一个元素和多维数组的返回机制相同:
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, --)
当从掩码数组中获取一个切片时,其返回值仍然是掩码数组,并且为原数组的一个视图。返回数组的mask
既不是nomask
(如果原始数组中没有无效数据),也不是原始数组mask
的一个副本。所以在切片时最好做深拷贝,以防止对切片数据修改的同时也修改了原始数组。
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
。
对掩码数组进行操作
掩码数组支持数学运算和逻辑运算。掩码数组中的无效数据将不参与运算,这意味着相应的数据项在操作前后应该不会发生改变。
注意:
我们应该强调掩码部分不发生改变的特性不是掩码数组自带的属性,在一些情况下掩码数值有可能在运算的过程中发生改变,所以用户不应该在运算过程中将掩码数值不变当作一个假设。
numpy.ma
支持大多数的数学运算。对于一些有特定定义域的函数(例如log
、divide
),当运算结果为无效值时,返回masked
常数:
ma.log([-1, 0, 1, 2])
masked_array(data=[–, --, 0.0, 0.6931471805599453],
mask=[ True, True, False, False],
fill_value=1e+20)
掩码数组也支持标准的numpy函数,输出为掩码数组。当函数的输入值被屏蔽时,函数不会对该部分进行计算。当输入值超出了函数的定义域,函数会返回掩码值:
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('掩码数组的均值为:{}'.format(mx.mean()))
print('每个值与均值的差值为:{}'.format(mx - mx.mean()))
print('每个值与均值的差值为:{}'.format(mx.anom()))
掩码数组的均值为:2.0
每个值与均值的差值为:[-2.0 -1.0 – 1.0 2.0]
每个值与均值的差值为:[-2.0 -1.0 – 1.0 2.0]
对缺失值进行填充
假设我们现在相对上一个数组进行打印,其中缺失值以均值进行替代。
print('用均值填充缺失值后的结果:{}'.format(mx.filled(mx.mean())))
用均值填充缺失值后的结果:[0. 1. 2. 3. 4.]
数值计算
在掩码数组中,我们无需担心缺失值便可进行数值计算,例如分母为0,对复数求平方根等等:
import numpy as np, 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(np.sqrt(x/y))
[1.0 – -- 1.0 – --]
输出结果中有四个值为无效值,第一个是因为对负数取平方根,第二个是因为除0,最后两个是因为输入值被掩码。
忽略极端值
假设我们有一个数组d
,其元素为0到1之间随机的浮点数。我们想对其[0.1, 0.9]
区间范围内的值取平均:
d = np.random.random(size=100)
print(ma.masked_outside(d, 0.1, 0.9).mean())
0.46533658283140283