nan_policy 的设计规范#
scipy.stats 中的许多函数都有一个名为 nan_policy 的参数,用于确定函数如何处理包含 nan 的数据。在本节中,我们提供了 SciPy 开发者指南,说明如何使用 nan_policy,以确保在将此参数添加到新函数时,我们保持一致的 API。
基本API#
参数 nan_policy 接受三个可能的字符串:'omit'、'raise' 和 'propagate'。它们的含义分别是:
nan_policy='omit': 忽略输入中的nan出现。如果输入包含nan,不会生成警告(除非移除nan值后的等效输入会生成警告)。例如,对于接受单个数组并返回标量的简单函数(暂时忽略axis的可能使用):func([1.0, 3.0, np.nan, 5.0], nan_policy='omit')
应表现与以下相同:
func([1.0, 3.0, 5.0])
更一般地,对于返回标量的函数,
func(a, nan_policy='omit')的行为应与func(a[~np.isnan(a)])相同。对于那些将一个向量转换为相同大小的新向量,并且输出数组中的每个条目不仅仅依赖于输入数组中对应值的函数 [#f1]_(例如
scipy.stats.zscore,scipy.stats.boxcox当lmbda为 None 时),:y = func(a, nan_policy='omit')
应表现与以下相同:
nan_mask = np.isnan(a) y = np.empty(a.shape, dtype=np.float64) y[~nan_mask] = func(a[~nan_mask]) y[nan_mask] = np.nan
(一般来说,
y的 dtype 可能取决于a以及func的预期行为)。换句话说,输入中的 nan 会在输出中产生相应的 nan,但该 nan 的存在不会影响非 nan 值的计算。应使用此属性的单元测试来测试处理
nan_policy的函数。对于返回标量且接受两个或更多参数但这些参数的值不相关的函数(例如
scipy.stats.ansari、scipy.stats.f_oneway),相同的想法适用于每个输入数组。因此:func(a, b, nan_policy='omit')
应表现与以下相同:
func(a[~np.isnan(a)], b[~np.isnan(b)])
对于具有 相关 或 配对 值的输入(例如
scipy.stats.pearsonr,scipy.stats.ttest_rel),推荐的行为是省略所有相关值中包含nan的值。对于具有两个相关数组输入的函数,这意味着:y = func(a, b, nan_policy='omit')
应表现与以下相同:
hasnan = np.isnan(a) | np.isnan(b) # Union of the isnan masks. y = func(a[~hasnan], b[~hasnan])
这种函数的文档字符串应清楚地说明此行为。
nan_policy='raise': 引发一个ValueError。nan_policy='propagate': 将nan值传播到输出。通常,这意味着直接执行函数而不检查nan,但请参阅举个例子,可能会导致意外输出的情况。
nan_policy 结合 axis 参数#
这里没有什么令人惊讶的——当函数有一个 axis 参数时,上述原则仍然适用。例如,假设 func 将一个一维数组缩减为一个标量,并将 n 维数组作为一维数组的集合处理,axis 参数指定沿哪个轴应用缩减。比如说:
func([1, 3, 4]) -> 10.0
func([2, -3, 8, 2]) -> 4.2
func([7, 8]) -> 9.5
func([]) -> -inf
然后:
func([[ 1, nan, 3, 4],
[ 2, -3, 8, 2],
[nan, 7, nan, 8],
[nan, nan, nan, nan]], nan_policy='omit', axis=-1)
必须给出结果:
np.array([10.0, 4.2, 9.5, -inf])
边缘情况#
实现 nan_policy 参数的函数应优雅地处理输入数组中所有值均为 nan 的情况。上述基本原则仍然适用:
func([nan, nan, nan], nan_policy='omit')
应表现与以下相同:
func([])
在实践中,当向现有函数添加 nan_policy 时,发现该函数尚未以定义明确的方式处理这种情况并不罕见,可能需要一些思考和设计来确保其正常工作。正确的行为(无论是返回 nan、返回其他值、引发异常还是其他行为)将根据具体情况确定。
为什么 nan_policy 不适用于 inf?#
尽管我们在小学时学到“无穷大不是一个数字”,浮点值 nan 和 inf 在性质上是不同的。值 inf 和 -inf 比 nan 更像常规的浮点值。
可以将
inf与其他浮点值进行比较,其行为符合预期,例如3 < inf为 True。在大多数情况下,算术运算对
inf的处理是“符合预期”的,例如inf + inf = inf,-2*inf = -inf,1/inf = 0等。许多现有函数对
inf的处理符合预期:np.log(inf) = inf,np.exp(-inf) = 0,np.array([1.0, -1.0, np.inf]).min() = -1.0,等等。
因此,虽然 nan 几乎总是意味着“出了问题”或“缺少某些东西”,但 inf 在许多情况下可以被视为一个有用的浮点值。
与NumPy的``nan``函数保持一致,不忽略``inf``:
>>> np.nanmax([1, 2, 3, np.inf, np.nan])
inf
>>> np.nansum([1, 2, 3, np.inf, np.nan])
inf
>>> np.nanmean([8, -np.inf, 9, 1, np.nan])
-inf
如何 不 实现 nan_policy#
在过去(可能现在也是),一些 stats 函数通过使用掩码数组来处理 nan_policy ,以掩码 nan 值,然后使用 mstats 子包中的函数计算结果。这种方法的问题在于,掩码数组代码可能会将 inf 转换为掩码值,这是我们不希望的(见上文)。这也意味着,如果不小心处理,返回值将是一个掩码数组,如果用户传入的是常规数组,这可能会让他们感到意外。
脚注