如何检测数据中的异常数值(NaN、None、无穷大等)

本文介绍在数据管道验证中高效识别异常数值(如 nan、none、inf/-inf)的专业方法,涵盖 python 原生与 numpy 双方案,强调 `math.isfinite()` 的核心作用,并指出常见误区与最佳实践。

在构建健壮的数据处理流水线时,仅依赖数值范围校验(如 x ∈ [-10, 10])无法捕获语义非法值——例如 float('nan')、float('inf')、None 或非数字类型(如字符串 'missing')。这些值虽可能通过范围检查(如 nan > 10 返回 False),但参与后续计算将导致静默错误或崩溃。因此,语义合法性验证必须先于范围验证

✅ 推荐方案:使用 math.isfinite()

Python 标准库的 math.isfinite(x) 是最简洁、准确的判断函数:它同时排除 NaN、+inf 和 -inf,且要求 x 为合法数字类型(int/float)。注意:它不接受 None 或字符串,因此需前置类型检查。

import math

def is_good_number(x) -> bool:
    """判断单个值是否为合法有限数(排除 NaN、Inf、None、str 等)"""
    return isinstance(x, (int, float)) and not isinstance(x, bool) and math.isfinite(x)

# 测试用例
test_cases = [1.5, -42, 0, float('nan'), float('inf'), float('-inf'), None, "123", True, False]
for x in test_cases:
    print(f"{repr(x):>12} → {is_good_number(x)}")

输出:

         1.5 → True
        -42 → True
         0 → True
     nan → False
     inf → False
    -inf → False
    None → False
     '123' → False
      True → False   # bool 是 int 子类,需显式排除
     False → False
⚠️ 关键注意:bool 在 Python 中是 int 的子类(isinstance(True, int) == True),但业务中通常不视为“数字”。因此建议显式排除 bool 类型,避免误判。

? 批量校验:高效检查整个数据集

对列表、数组或 Pandas Series 进行批量验证时,推荐以下方式:

纯 Python(小数据集):

def all_good_numbers(data) -> bool:
    return all(is_good_number(x) for x in data)

data = [1.0, -3.14, 0, 999]
print(all_good_numbers(data))  # True

NumPy 加速(大数据集):

import numpy as np

def all_good_numbers_np(arr) -> bool:
    arr = np.asarray(arr)
    # 先转为浮点,再用 isnan/isinf;注意:None 会转为 nan
    if arr.dtype == object:
        # 对 object 数组,先逐元素检查类型和有限性
        return all(is_good_number(x) for x in arr.flat)
    return np.all(np.isfinite(arr))

# 示例
arr = np.array([1.0, 2.5, -np.inf, 0.0])
print(all_good_numbers_np(arr))  # False

❌ 常见误区与替代方案辨析

  • 不要用 x == x 检测 NaN:虽然 nan != nan 成立,但该方式无法区分 None 或字符串,且可读性差;
  • 避免 np.isnan(x) 单独使用:它对非数字类型(如 None、字符串)抛出 TypeError,需额外 try-except;
  • 慎用范围检查替代:-10
  • Pandas 用户注意:pd.isna(x) 可统一检测 NaN、None、pd.NaT,但不识别 inf,需组合 np.isfinite() 使用:
    import pandas as pd
    s = pd.Series([1, np.nan, np.inf, None])
    valid_mask = ~pd.isna(s) & np.isfinite(s)  # 正确:同时排除 NaN/None/Inf

✅ 总结:三步验证法(生产推荐)

  1. 类型安全:isinstance(x, (int, float)) and not isinstance(x, bool)
  2. 数学合法性:math.isfinite(x)(一揽子排除 NaN/Inf/-Inf)
  3. 业务范围校验(可选):min_val

将这三步封装为可复用的验证器,即可为数据管道筑牢第一道质量防线。记住:“好数字”的定义首先是“能参与数学运算而不崩溃”,其次才是“符合业务逻辑范围”。