NumPy NaN не всегда признается - PullRequest
       11

NumPy NaN не всегда признается

0 голосов
/ 01 сентября 2018

Это озадачивает меня:

```
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 с самим собой?

Спасибо за любые намеки на то, что здесь происходит.

Ответы [ 3 ]

0 голосов
/ 01 сентября 2018

Вопрос не столько в операторе 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. То же самое будет верно для списка, построенного таким образом.

0 голосов
/ 01 сентября 2018

Во-первых, по крайней мере в 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

Вы можете изучить все правила, которые делают все эти вещи такими, но все они могут измениться в другой реализации Python, или в версии 3.8, или даже в 3.7, созданной с помощью пользовательских настроить параметры. Так что просто никогда 1 или math.nan, или np.nan, или '' с is; используйте его только для объектов, которые задокументированы как одиночные (например, None - или экземпляры ваших собственных типов, конечно).


Во-вторых, когда вы индексируете массив numpy, он должен «распаковать» значение, создав скаляр типа, соответствующего массиву dtype. Для массива dtype=float64 его скалярное значение равно np.float64.

Итак, a[2] гарантированно будет np.float64.

Но np.nan это не np.float64, это float.

Итак, NumPy не может дать вам np.nan, когда вы попросите a[2]. Вместо этого он дает np.float64 со значением NaN.


ОК, поэтому a[2] is np.nan всегда ложно. Но почему a[2] is a[2] также обычно ложно?

Как я упоминал выше, NumPy пытается дать вам np.nan всякий раз, когда ему нужно дать вам float NaN. Но - по крайней мере, в 1.15 - он не имеет какого-либо специального одноэлементного значения, чтобы предоставить его, когда ему нужно дать np.float64 NaN. Нет причины, по которой не смог бы , но никто не удосужился написать такой код, потому что это не должно иметь никакого значения для любого правильно написанного приложения.

Итак, каждый раз, когда вы распаковываете значение в a[2] в скаляр np.float64, он дает вам новое значение NaN np.float64.

Но почему это не то же самое, что 301-1 is 300? Хорошо, причина, по которой это работает, заключается в том, что компилятору разрешено складывать константы известного неизменяемого типа с равными значениями, а CPython делает именно это, для простых случаев, внутри каждой единицы компиляции. Но два значения NaN не равны; значение NaN даже не равно самому себе. Таким образом, он не может быть сложен постоянно.

(Если вам интересно, что произойдет, если вы создадите массив с типом int d, сохраните в нем небольшие значения и проверите, объединяются ли они в синглоны small-int, попробуйте и посмотрите.)


И, конечно, именно поэтому isnan существует в первую очередь. Вы не можете проверить NaN с равенством (потому что значения NaN не равны ни с чем, даже сами по себе), вы не можете проверить NaN с идентичностью (по всем причинам, описанным выше), поэтому вам нужна функция для проверки им.

0 голосов
/ 01 сентября 2018

Проверьте это:

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

Они не одинаковы

...