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)])
的行为相同。对于将向量转换为相同大小的新向量的函数,并且输出数组中的每个条目都依赖于输入数组中不仅仅是对应的值 [1](例如
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
将 1-d 数组简化为标量,并将 n-d 数组处理为 1-d 数组的集合,其中 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
值来处理 nan_policy
,然后使用 mstats
子包中的函数计算结果。这种方法的问题在于,掩码数组代码可能会将 inf
转换为掩码值,这是我们不想做的(见上文)。这也意味着,如果不小心,返回值将是一个掩码数组,如果用户传入常规数组,这可能会让用户感到惊讶。
脚注