NumPy 2.x 完全指南【二十六】掩码数组

1. 概述

numpy.ma.MaskedArrayndarray 的子类,专门用于处理含无效数据的数值数组,

类定义:

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 < v1x > 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不处理语法简洁的场景
getdatandarray不处理兼容性需求
filledndarray替换无效值安全计算或输出完整数据

示例 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 属性。
  • 使用 getmaskgetmaskarray 函数。

示例 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 索引和切片

由于 MaskedArraynumpy.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)时,通过自动传播掩码状态和保护无效操作来确保数据完整性(这里还没有学到通用函数,后续会介绍)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

墨 禹

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值