【问题标题】:numpy NaN not always recognizednumpy NaN 并不总是被识别
【发布时间】:2019-02-06 23:37:37
【问题描述】:

这让我很困惑:

```
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
```

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

感谢您对这里发生的事情的任何提示。

【问题讨论】:

  • 我也不完全理解,但这个观察可能会有所帮助:Python floats 实际上也可以是 nan。您可以通过float("nan") 构建一个。但它似乎创造了一个新的对象。 float("nan") is float("nan") 返回False
  • 一般来说is==是完全不相关的操作。 Nan不是单身人士
  • is 应该被允许。它只允许用于明确定义为单例的事物(如None),或者用于您手动创建的您自己的类型的实例,以便您知道它们是否相同。这里没有一个是真的。对于两个单独的 NaN 值,x is y 为真或为假是完全有效的;您的代码不能依赖任何一个。对于不同的情况(取决于您的 numpy 和 Python 版本)了解为什么它是正确的和不是正确的可能是有趣,但除了了解 Python 之外它没有有用内部更好。

标签: python numpy nan


【解决方案1】:

看看这个:

In [1]: type(a[2])
Out[1]: numpy.float64
In [2]: type(numpy.nan)
Out[2]: float

还有

In [3]: id(a[2])
Out[3]: 4419858888
In [4]: id(np.nan)
Out[4]: 4326468200

他们不一样

【讨论】:

  • 我认为这很明显。 id != id 几乎是 is 的同义词,即 False。问题是为什么索引会产生不同的对象。
【解决方案2】:

这与其说是关于 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

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

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

【讨论】:

    【解决方案3】:

    首先,至少在 NumPy 1.15 中,np.nan 恰好是一个特殊的单例,这意味着每当 NumPy 必须为您提供 float 类型的 NaN 值时,它都会尝试为您提供相同的 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
    

    可以了解所有这些东西的所有规则,但是它们都可以在不同的 Python 实现中改变,或者在 3.8 版中,甚至在 3.7 中构建自定义配置选项。所以,永远不要将1math.nannp.nan''is;仅将它用于专门记录为单例的对象(例如 None,当然也可以是您自己类型的实例)。


    其次,当你索引一个 numpy 数组时,它必须通过构造一个适合数组dtype 类型的标量来“拆箱”该值。对于dtype=float64 数组,它构造的标量值是np.float64

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

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

    因此,当您请求 a[2] 时,NumPy 无法为您提供 np.nan。相反,它会为您提供一个带有 NaN 值的 np.float64


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

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

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

    但是为什么这和301-1 is 300 不一样呢?好吧,工作的原因是允许编译器折叠具有相等值的已知不可变类型的常量,而对于简单的情况,CPython 在每个编译单元中都这样做。但是两个 NaN 值不相等; NaN 值甚至不等于它自己。所以,它不能是恒定折叠的。

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


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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-01
      • 1970-01-01
      相关资源
      最近更新 更多