文章目录
1. 概述
numpy.ma.MaskedArray
: ndarray
的子类,专门用于处理含无效数据的数值数组,
类定义:
class MaskedArray(ndarray[_ShapeType_co, _DType_co]):
__array_priority__: Any
def __new__(cls, data=..., mask=..., dtype=..., copy=..., subok=..., ndmin=..., fill_value=..., keep_mask=..., hard_mask=..., shrink=..., order=...): ...
def __array_finalize__(self, obj): ...
def __array_wrap__(self, obj, context=..., return_scalar=...): ...
def view(self, dtype=..., type=..., fill_value=...): ...
def __getitem__(self, indx): ...
def __setitem__(self, indx, value): ...
@property
def dtype(self): ...
# ......
掩码(Mask
)其本意是遮盖或隐藏某些东西,在计算机领域,这个术语被用来描述一种操作模式,即通过对数据的部分内容进行屏蔽、过滤或修改,从而达到特定的操作目的。比如在敏感数据脱敏场景中,经常会使用掩码技术,通过将敏感数据的一部分字符替换为掩码(如 0987 **** 4321
),隐藏个人隐私(身份证号、银行卡)、医疗记录等敏感信息,防止数据泄露。
在许多情况下,数据集可能不完整或受到无效数据的污染。例如,传感器可能未能记录数据,或者记录了无效的值。numpy.ma
模块通过引入掩码数组(MaskedArray
)提供了一个便捷的解决方案,是一个支持带有掩码的数组。
掩码数组由两部分组成:
data
:原始数值数组(numpy.ndarray
)mask
:布尔数组,标记data
中无效元素(True
=无效,False
=有效)
应用场景:
- 数据清洗(如传感器异常值、缺失值)
- 图像处理(屏蔽无效像素区域)
- 统计分析(排除无效数据后计算均值、方差等)
2. numpy.ma 模块
numpy.ma
模块的主要特性是 MaskedArray
类,可以作为 numpy
的附加模块使用:
import numpy as np
import numpy.ma as ma
可以直接使用 ma.array
创建一个掩码数组:
y = ma.array([1, 2, 3], mask = [0, 1, 0]) # 第二个元素无效
numpy.ma
模块包含了以下常量:
masked
nomask
masked_print_option
masked
表示被屏蔽(无效)的元素值,类型为 MaskedConstant(MaskedArray)
。在输出或计算时,被屏蔽的元素会显示为 --
(默认)或指定的填充值。
nomask
表示无掩码状态,即相关数组的值没有无效值,它是一个布尔值 False
的别名。
masked_print_option
表示掩码元素的显示方式,默认值为 --
。
3. 创建掩码数组
3.1 直接调用构造函数
通过显式调用 numpy.ma.MaskedArray
构造函数创建掩码数组,需指定原始数据 data
和掩码 mask
。
构造函数定义:
def __new__(cls, data=None, mask=nomask, dtype=None, copy=False,
subok=True, ndmin=0, fill_value=None, keep_mask=True,
hard_mask=None, shrink=True, order=None)
参数说明:
data
:原始数据。mask
:布尔掩码数组,标记无效数据(True
=屏蔽,False
=有效)。dtype
:指定输出数组的数据类型。copy
:制是否复制输入数据。默认值False
,返回视图。subok
:是否返回MaskedArray
子类实例。默认值True
,保留子类类型。ndmin
:指定最小维度。如ndmin=2
强制二维数组。fill_value
:无效数据的填充值。默认值None
,自动选择类型相关默认值。keep_mask
:是否保留输入数组的掩码。默认值True
,与mask
参数冲突时,优先使用mask
。hard_mask
:启用硬掩码模式(禁止修改掩码值)。默认值None
,使用全局默认设置。shrink
:是否压缩掩码数组。默认值True
。order
:控制内存布局。
示例 1 ,使用布尔数组指定需要掩码的数据:
data = [1, 2, 3, -999, 5]
mask = [False, False, False, True, False] # True 表示屏蔽
masked_arr = ma.MaskedArray(data, mask=mask)
print(masked_arr) # 输出: [1 2 3 -- 5]
3.2 array、masked_array 函数
它们实际调用的还是 MaskedArray
类的构造函数,两者功能完全统一,无实质差异:
ma.array
: 函数源码中有说,是为了方便和向后兼容,选项的顺序有所不同。ma.masked_array
: 则是MaskedArray
类的构造函数的别名,直接点过去就会跳转到__new__
。
ma.array
函数定义:
def array(data, dtype=None, copy=False, order=None,
mask=nomask, fill_value=None, keep_mask=True,
hard_mask=False, shrink=True, subok=True, ndmin=0):
return MaskedArray(data, mask=mask, dtype=dtype, copy=copy,
subok=subok, keep_mask=keep_mask,
hard_mask=hard_mask, fill_value=fill_value,
ndmin=ndmin, shrink=shrink, order=order)
array.__doc__ = masked_array.__doc__
3.3 asarray、asanyarray 函数
ma.asarray
:将输入转换为指定数据类型的掩码数组,不保留输入的子类信息。
ma.asanyarray
:将输入转换为指定数据类型的掩码数组,保留子类信息(如自定义数组类型)。
示例 1 :
# 输入为列表
x_list = [1, 4, 8, 7, 2, 5]
arr_list = np.ma.asarray(x_list)
print(arr_list) # 输出: [1 4 8 7 2 5](掩码数组形式)
3.4 单值条件函数
ma.masked_equal(x, value[, copy])
:x == value
时掩码
ma.masked_not_equal(x, value[, copy])
:x != value
时掩码
ma.masked_greater(x, value[, copy])
:x > value
时掩码
ma.masked_less(x, value[, copy])
:x < value
时掩码
ma.masked_greater_equal(x, value[, copy])
:x ≥ value
时掩码
ma.masked_less_equal(x, value[, copy])
: x ≤ value
时掩码
示例 1 :
# masked_equal: 屏蔽所有 0
print(ma.masked_equal([0, 1, 0], 0)) # 输出: [-- 1 --]
# masked_not_equal: 屏蔽所有非 3 的值
print(ma.masked_not_equal([2, 3, 4], 3)) # 输出: [2 -- 4]
# masked_greater: 屏蔽所有大于5的值
print(ma.masked_greater([5, 2, 8], 5)) # 输出: [-- 2 8]
# masked_less: 屏蔽所有小于0的值
print(ma.masked_less([-1, 7, 3], 0)) # 输出: [-- 7 3]
# masked_greater_equal: 屏蔽所有≥5的值
print(ma.masked_greater_equal([4, 5, 6], 5)) # 输出: [4 -- --]
# masked_less_equal: 屏蔽所有≤2的值
print(ma.masked_less_equal([1, 2, 3], 2)) # 输出: [1 -- --]
3.5 区间条件函数
ma.masked_inside(x, v1, v2[, copy])
:v1 ≤ x ≤ v2
时掩码
ma.masked_outside(x, v1, v2[, copy])
:x < v1
或 x > v2
时掩码
示例 1 ,屏蔽区间内的值:
# 原始数据:含负值、小数、边界值
data = np.array([-1.5, 0.31, 1.2, 0.01, 0.2, -0.4, -1.1, 5, 10])
# 屏蔽 [-0.3, 0.3] 区间内的值
masked_data = ma.masked_inside(data, -0.3, 0.3)
print("屏蔽区间 [-0.3, 0.3] 内的值:")
print(masked_data)
示例 2 ,屏蔽区间外的值:
# 同一组数据
data = np.array([-1.5, 0.31, 1.2, 0.01, 0.2, -0.4, -1.1, 5, 10])
# 屏蔽 [-0.3, 0.3] 区间外的值
masked_data = ma.masked_outside(data, -0.3, 0.3)
print("屏蔽区间 [-0.3, 0.3] 外的值:")
print(masked_data)
3.6 自定义条件函数
ma.masked_where(condition, a, copy=True)
:根据自定义条件对数组进行掩码处理,返回一个掩码数组。
参数说明:
a
:待掩码的输入数组(支持任意维度)。condition
:布尔条件数组。当条件为True
时,a
的对应位置被屏蔽。若涉及浮点数相等性判断(如x == 0.3
),建议改用masked_values()
(避免浮点精度误差)。copy
:默认True
,返回副本(不修改原数组)。
示例 1 :
a = np.arange(4) # [0, 1, 2, 3]
masked = ma.masked_where(a <= 2, a)
print(masked) # 输出: [-- -- -- 3]
3.7 无效值函数
ma.fix_invalid()
:将无效值(NaN
/Inf
)自动掩码并替换为指定填充值,返回掩码数组。
参数说明:
a
:待掩码的输入数组(支持任意维度)。mask
:自定义掩码(布尔数组),True
表示预先屏蔽。copy
:默认True
,返回副本(不修改原数组)。fill_value
:替换无效数据的填充值。
ma.masked_invalid()
:仅屏蔽无效值(NaN
/Inf
)。
参数说明:
a
:待掩码的输入数组(支持任意维度)。copy
:默认True
,返回副本(不修改原数组)。
示例 1 :
# 创建含无效数据的数组(NaN, Inf)
x = ma.array([1., -1, np.nan, np.inf], mask=[True, False, False, False])
fixed = ma.fix_invalid(x)
print(fixed)
# 输出: [-- -1.0 -- --]
# 掩码状态: [True, False, True, True]
示例 2 :
a = np.array([1, np.nan, 3, np.inf])
masked_a = ma.masked_invalid(a)
print(masked_a)
# 输出: [1.0 -- 3.0 --] (原数据未修改)
3.8 精确值函数
ma.masked_object()
:对数组 x
中精确等于 value
的值进行掩码处理,返回掩码数组。
参数说明:
x
:待掩码的输入数组(通常为对象数组)。value
:需要屏蔽的精确匹配值。copy
:若为True
返回副本;若为False
返回视图(修改会影响原数组)。shrink
:若掩码全为False
,是否压缩为nomask
(无掩码状态)。
注意事项: 对浮点数组请用 masked_values()
(避免精度问题)。
示例 1 ,过滤对象数组中的无效标签(如屏蔽特定分类名称):
categories = np.array(['A', 'B', 'Invalid', 'C'], dtype=object)
cleaned = ma.masked_object(categories, 'Invalid') # 屏蔽'Invalid'
示例 2 ,替换对象数组中的占位符(如屏蔽 ‘N/A’ 标记)::
survey_data = np.array(['Yes', 'N/A', 'No'], dtype=object)
valid_responses = ma.masked_object(survey_data, 'N/A')
3.9 近似值函数
ma.masked_values()
:对数组 x
中精确等于 value
的值进行掩码处理,返回掩码数组。
对数组 x
中近似等于 value
的值进行掩码处理(基于浮点容差机制),返回掩码数组(MaskedArray
)。此函数专为浮点数组设计,整数数组会自动退化为精确匹配(等价于 masked_equal
)。
参数说明
x
待掩码的输入数组。value
:需要屏蔽的目标值。rtol
:相对容差(相对value
的比例误差)。atol
:绝对容差(与value
的绝对差值)。copy
:若为True
返回副本;若为False
返回视图 。shrink
:若掩码全为False
,是否压缩为nomask
(无掩码状态)。
示例 1 ,精确屏蔽浮点值(默认容差):
x = np.array([1, 1.1, 2, 1.1, 3])
masked_arr = ma.masked_values(x, 1.1)
print(masked_arr)
示例 2 ,调整容差扩大屏蔽范围:
masked_arr = ma.masked_values(x, 2.1, atol=0.1) # 绝对容差 0.1
print(masked_arr) # [1.0, 1.1, --, 1.1, 3.0] # 屏蔽接近2.1的值(如2.0)
4. 各种操作
4.1 访问数据
可以通过多种方式访问掩码数组的底层数据:
方法 | 输出类型 | 处理掩码 | 内存共享 | 适用场景 |
---|---|---|---|---|
data 属性 | ndarray 或其子类 | 不处理 | 是 | 需直接操作原始数据 |
__array__ | ndarray | 不处理 | 是 | 强制转换为普通数组 |
直接转换 | ndarray | 不处理 | 是 | 语法简洁的场景 |
getdata | ndarray | 不处理 | 是 | 兼容性需求 |
filled | ndarray | 替换无效值 | 否 | 安全计算或输出完整数据 |
示例 1 ,通过 data
属性直接访问底层数据,不处理掩码状态。返回原始数据的视图,类型为 numpy.ndarray
或其子类:
arr = ma.array([1, 2, 3], mask=[False, True, False]) #
print(arr) # 输出: [1 -- 3]
print(arr.data) # 输出: [1 2 3](即使第二个值被屏蔽)
示例 2 ,__array__
方法与 data
属性类似,强制返回标准的 numpy.ndarray
,忽略掩码:
print(arr.__array__()) # 输出: [1 2 3]
示例 3 ,可以直接使用创建numpy.ndarray
或其子类的方法,等价于调用 data
属性,返回原始数据的视图:
print(np.array(arr)) # 输出: [1 2 3]
示例 4 ,getdata
函数与 data
属性一致,但作为独立函数提供,适用于非掩码数组的兼容性处理:
print(ma.getdata(arr)) # 输出: [1 2 3]
示例 5 ,使用 filled
方法可以用 fill_value
替换所有被屏蔽的值:
print(arr.filled()) # 默认填充 999999
print(arr.filled(-1)) # 输出: [1 -1 3](屏蔽值替换为-1)
4.2 访问掩码
访问掩码的两种方式:
- 通过
mask
属性。 - 使用
getmask
和getmaskarray
函数。
示例 1 ,mask
属性直接返回掩码数组的布尔掩码,其中 True
表示无效数据,False
表示有效数据:
arr = ma.array([1, 2, 3], mask=[False, True, False])
print(arr.mask) # 输出: [False True False]
示例 2 ,无掩码信息的数组,mask
属性返回 nomask
常量:
# 创建一个掩码数组
arr = ma.array([1, 2, 3])
print(arr.mask) # 输出: False
print(arr.mask is ma.nomask) # 输出: True( 验证本质是 nomask )
def getmask(a)
函数用于获取数组的掩码信息,返回输入数组 a
的掩码,返回值:
- 掩码数组:形状与
a
相同的布尔数组(True
表示无效数据)。 nomask
:若输入无掩码或非MaskedArray
,返回False
。
def getmaskarray(arr)
也是获取数组的掩码信息,主要区别是若输入无掩码或非 MaskedArray
时,返回与 arr
形状相同的全 False
数组(表示无掩码)。
示例 3 ,无掩码的数组输出区别:
b = ma.masked_array([[1, 2], [3, 4]])
print(ma.getmask(b)) # 输出: False
print(ma.getmaskarray(b)) # 输出: [[False, False], [False, False]]
4.3 检索有效数据
检索有效数据(即未被掩码的条目)有两种常用方法:
- 使用掩码的逆作为索引
- 使用
compressed()
方法
示例 1 ,通过取反掩码,可以将有效数据的索引转换为 True
,从而直接通过布尔索引提取有效值。
# 掩码数组
x = ma.array([[1, 2], [3, 4]], mask=[[False, True], [True, False]])
# ~ mask 取反掩码
valid_data = x[~x.mask] # 等价于 x[]
print(valid_data) # [1 4]
# np.logical_not(x.mask) 取反掩码
valid_data = x[np.logical_not(x.mask)]
print(valid_data) # [1 4]
示例 2 ,compressed()
方法会直接返回一个一维数组,仅包含有效数据,且自动去除掩码信息:
print(x.compressed()) # [1 4]
4.4 标记掩码
标记特定元素为掩码主要有以下两种方式:
- 直接赋值
ma.masked
- 通过多维索引批量标记
示例 1 ,通过将数组元素赋值为特殊值 ma.masked
,可以快速标记条目为无效:
x = ma.array([1, 2, 3])
x[0] = ma.masked # 标记第一个元素为无效
print(x) # [-- 2 3]
示例 2 ,对于多维数组,可以通过元组索引批量标记特定位置的元素为无效:
y = ma.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
y[(0, 1, 2), (1, 2, 0)] = ma.masked # 标记(0,1)、(1,2)、(2,0)位置的元素
print(y)
# [[1 -- 3]
# [4 5 --]
# [-- 8 9]]
4.5 取消掩码
直接赋值新值是取消掩码的最直接方式,NumPy
会自动完成两项操作:
- 更新数据值:将新值写入数组的
data
属性。 - 更新掩码状态:将该位置的掩码从
True
改为False
(即取消掩码)。
示例 1 :
x = ma.array([1, 2, 3], mask=[False, False, True]) # 第三个元素被掩码
print("初始状态:")
print(x) # 输出:[1, 2, --]
x[-1] = 5
print("解掩码后:")
print(x) # 输出:[1, 2, 5]
4.6 索引和切片
由于 MaskedArray
是 numpy.ndarray
的子类,它继承了 numpy.ndarray
的索引和切片机制。
访问单个元素的输出行为取决于该位置掩码的状态,具体规则如下:
- 掩码为
False
(有效数据) → 返回标量值 - 掩码为
True
(无效数据) → 返回特殊值masked
示例 1 :
x = ma.array([1, 2, 3], mask=[False, False, True]) # 第三个元素掩码为 True
print(x[0]) # 输出: 1 (标量int类型)
print(x[-1]) # 输出: --
print(x[-1] is ma.masked) # 输出: True
4.7 运算
掩码数组支持算术和比较操作,通过选择性忽略无效数据来确保数据处理的合理性。但是在特定场景下,掩码数组在操作中可能导致无效数据被意外修改。
示例 1 ,两个掩码数组执行加法运算,系统自动跳过掩码为 True
的位置,仅处理有效数据位置:
a = ma.array([1, 2, 3], mask=[False, True, False])
print(a) # [1 -- 3]
b = ma.array([4, 5, 6], mask=[False, False, True])
print(b) # [4 5 --]
print(a + b) # 输出:[5 -- --]
结果说明:
- 位置
0
(有效+有效)→1+4=5
。 - 位置
1
(无效+有效)→ 无效(--
)。 - 位置
2
(有效+无效)→ 无效(--
)。
示例 2 ,使用 np.log
函数时,无效值可能触发计算异常:
arr = ma.array([1, -1, 4], mask=[False, True, False])
result = np.log(arr) # 未使用ma.log()
print(result)
# RuntimeWarning:
#
# invalid value encountered in log
#
# [0.0 -- 1.3862943611198906]
示例 3 ,绕过掩码接口直接修改数组,导致无效值被覆盖:
z = ma.array([5, 10, 15], mask=[True, False, False])
z.data[0] = 0 # 直接修改被掩码元素
print("底层数据:", z.data)
print("掩码状态:", z.mask)
掩码数组在处理通用函数(ufunc
)时,通过自动传播掩码状态和保护无效操作来确保数据完整性(这里还没有学到通用函数,后续会介绍)。