numpy NaN并不总是被认可

Bas*_*aan 5 python numpy nan

这让我很困惑:

```
a=np.array([1,2,np.nan,3])    # an array with a nan
print(np.isnan(a)[2])         # it truly is a nan
print(a[2])                   # it quacks like a nan
print(np.nan is np.nan)       # nan's can be compared
print(a[2] is np.nan)         # But then, this isn't a nan after all!!??

>>> True
>>> nan
>>> True
>>> False
```
Run Code Online (Sandbox Code Playgroud)

我知道我们不允许比较nan的==,但is应该被允许吗?毕竟它在比较nan与自身时有效吗?

感谢您对此处发生的任何提示.

hpa*_*ulj 6

这不是关于Python is运算符的问题,而是关于数组元素的索引或取消装箱的问题:

In [363]: a=np.array([1,2,np.nan,3])
In [364]: a[2]
Out[364]: nan
In [365]: type(a[2])
Out[365]: numpy.float64
In [366]: a[2] is a[2]
Out[366]: False
Run Code Online (Sandbox Code Playgroud)

a[2]不仅仅是回归nan.它返回一个np.float64值为的对象np.nan.另一个a[2]将产生另一个np.float64对象.在这个is意义上,两个这样的对象不匹配.对于任何数组元素都是如此,而不仅仅是nan值.

由于==不起作用nan,我们坚持使用该np.isnan功能.

np.nan是一个唯一的float对象(在此会话中),但未a[2]设置为该对象.

如果数组被定义为对象类型:

In [376]: b=np.array([1,2,np.nan,3], object)
In [377]: b[2] is np.nan
Out[377]: True
Run Code Online (Sandbox Code Playgroud)

这里is是True - 因为b包含指向已经存在于内存中的np.nan对象的指针,包括对象.对于像这样构造的列表也是如此.


aba*_*ert 5

首先,至少在 NumPy 1.15 中,它np.nan恰好是一个特殊的单例,这意味着每当 NumPy 必须给你一个 NaN 类型的值时float,它都会尝试给你相同的np.nan值。

但这在任何地方都没有记录,或者保证跨版本都是正确的。

作为实现细节,这适合更大类的值,这些值可能是也可能不是单例。

作为一般规则,如果您的代码依赖于相同或不相同的不可变类型的两个相等值,则您的代码是错误的。

以下是 CPython 3.7 默认构建的一些示例:

>>> a, b = 200, 201
>>> a is b-1
True
>>> a, b = 300, 301
>>> a is b-1
False
>>> 301-1 is 300
True
>>> math.nan is math.nan
True
>>> float('nan') is math.nan
False
>>> float('nan') is float('nan')
False
Run Code Online (Sandbox Code Playgroud)

可以了解使所有这些事情以这种方式出现的所有规则,但它们都可以在不同的 Python 实现中更改,或者在版本 3.8 中,甚至在使用自定义配置选项构建的 3.7 中。所以,只是从不1math.nannp.nan''is; 仅将它用于专门记录为单例的对象(None当然,例如— 或您自己类型的实例)。


其次,当你索引一个 numpy 数组时,它必须通过构造一个适合数组的dtype. 对于dtype=float64数组,它构造的标量值是 a np.float64

所以,a[2]保证是一个np.float64.

np.nan不是np.float64,而是float

因此,np.nan当您要求a[2]. 相反,它为您提供np.float64了一个 NaN 值。


好的,这就是为什么a[2] is np.nan总是 False的原因。但为什么a[2] is a[2]通常也是假的?

正如我上面提到的,NumPy 会np.nan在需要给你一个floatNaN时尝试给你。但是——至少在 1.15 中——它没有任何特殊的单例值可以在需要给你一个np.float64NaN 时提供。没有理由不能,但没有人愿意编写这样的代码,因为这对于任何正确编写的应用程序来说都无关紧要。

因此,每次将值拆箱a[2]为 scalar 时np.float64,它都会为您提供一个新的 NaN 值np.float64

但为什么这不一样301-1 is 300?嗯,有效的原因是允许编译器将已知的不可变类型的常量折叠为相等的值,而 CPython 在每个编译单元中就简单的情况做到了这一点。但是两个 NaN 值不相等;NaN 值甚至不等于自身。所以,它不能被恒定折叠。

(如果你想知道如果你用 int dtype 创建一个数组并在其中存储小值并检查它们是否合并到 small-int 单例中会发生什么 - 试试看。)


当然,这就是isnan首先存在的原因。你不能用相等来测试 NaN(因为 NaN 值不等于任何东西,甚至它们自己),你不能用身份测试 NaN(出于上述所有原因),所以你需要一个函数来测试他们。