Вопрос по python, numpy, nan, comparison – Сравнение массивов NumPy так, чтобы NaN сравнивались равными

19

Существует ли идиоматический способ сравнения двух массивов NumPy, которые бы рассматривали NaN как равные друг другу (но не равные чему-либоother чем NaN).

Например, я хочу, чтобы следующие два массива сравнивались одинаково:

np.array([1.0, np.NAN, 2.0])
np.array([1.0, np.NAN, 2.0])

и следующие два массива для сравнения неравны:

np.array([1.0, np.NAN, 2.0])
np.array([1.0, 0.0, 2.0])

Я ищу метод, который бы дал скалярный логический результат.

Следующее сделало бы это:

np.all((a == b) | (np.isnan(a) & np.isnan(b)))

но он неуклюжий и создает все эти промежуточные массивы.

Есть ли способ, который облегчает глаза и лучше использует память?

Постскриптум Если это помогает, известно, что массивы имеют одинаковую форму и тип d.

Если бы это не было для чисел, которые сравниваются равными, но имеют разные двоичные представления (0.0 и -0.0, например), то memoryview (a0) == memoryview (a1) сделает это .. DSM
@DSM: Спасибо за это. Это может действительно соответствовать требованиям для моего варианта использования. Не могли бы вы написать это как ответ? NPE
Если вы используете текущую подсказку git для numpy, естьnumpy.isclose function это занимаетequal_nan ключевое слово аргумент (по умолчаниюFalse для совместимости). Это не очень благоприятно для памяти, хотя. Joe Kington
@DanielRoseman: я это понимаю. У меня есть два способа создания массива NumPy, и мне нужно знать, производили ли они идентичные массивы. NPE
Вы исключили один ответ отthis question; ты исключаешь и двух других? senderle

Ваш Ответ

4   ответа
16

имеете очень большие массивы), тогда вам следует использовать Numberxpr, и вам подойдет следующее выражение:

np.all(numexpr.evaluate('(a==b)|((a!=a)&(b!=b))'))

Я тестировал его на очень больших массивах длиной 3e8, и код на моей машине имел ту же производительность, что и код

np.all(a==b)

и использует тот же объем памяти

(+1) Хорошая идея, спасибо. NPE
8

Disclaimer: I don't recommend this for regular use, and I wouldn't use it myself, but I could imagine rare circumstances under which it might be useful.

Если массивы имеют одинаковую форму и тип d, вы можете рассмотреть возможность использования низкоуровневыхmemoryview:

>>> import numpy as np
>>> 
>>> a0 = np.array([1.0, np.NAN, 2.0])
>>> ac = a0 * (1+0j)
>>> b0 = np.array([1.0, np.NAN, 2.0])
>>> b1 = np.array([1.0, np.NAN, 2.0, np.NAN])
>>> c0 = np.array([1.0, 0.0, 2.0])
>>> 
>>> memoryview(a0)
<memory at 0x85ba1bc>
>>> memoryview(a0) == memoryview(a0)
True
>>> memoryview(a0) == memoryview(ac) # equal but different dtype
False
>>> memoryview(a0) == memoryview(b0) # hooray!
True
>>> memoryview(a0) == memoryview(b1)
False
>>> memoryview(a0) == memoryview(c0)
False

Но остерегайтесь таких тонких проблем, как это:

>>> zp = np.array([0.0])
>>> zm = -1*zp
>>> zp
array([ 0.])
>>> zm
array([-0.])
>>> zp == zm
array([ True], dtype=bool)
>>> memoryview(zp) == memoryview(zm)
False

это происходит потому, что двоичные представления различаются, даже если они сравниваются равными (они, конечно, должны: так он знает, как печатать отрицательный знак)

>>> memoryview(zp)[0]
'\x00\x00\x00\x00\x00\x00\x00\x00'
>>> memoryview(zm)[0]
'\x00\x00\x00\x00\x00\x00\x00\x80'

С другой стороны, он закорачивает то, на что можно надеяться:

In [47]: a0 = np.arange(10**7)*1.0
In [48]: a0[-1] = np.NAN    
In [49]: b0 = np.arange(10**7)*1.0    
In [50]: b0[-1] = np.NAN     
In [51]: timeit memoryview(a0) == memoryview(b0)
10 loops, best of 3: 31.7 ms per loop
In [52]: c0 = np.arange(10**7)*1.0    
In [53]: c0[0] = np.NAN   
In [54]: d0 = np.arange(10**7)*1.0    
In [55]: d0[0] = 0.0    
In [56]: timeit memoryview(c0) == memoryview(d0)
100000 loops, best of 3: 2.51 us per loop

и для сравнения:

In [57]: timeit np.all((a0 == b0) | (np.isnan(a0) & np.isnan(b0)))
1 loops, best of 3: 296 ms per loop
In [58]: timeit np.all((c0 == d0) | (np.isnan(c0) & np.isnan(d0)))
1 loops, best of 3: 284 ms per loop
(+1) Это здорово, спасибо, что нашли время написать это. NPE
@aix: Мне действительно нужно было что-то похожее в прошлом (равное рассмотрение-nans-равное), хотя проблем с производительностью и памятью не было, поэтому я сделал это вручную. Возможно, стоит сделать запрос на функцию.
9

equal_nan ключевое слово дляnp.allclose (https://docs.scipy.org/doc/numpy/reference/generated/numpy.allclose.html).

Так что вы можете сделать сейчас:

In [24]: np.allclose(np.array([1.0, np.NAN, 2.0]), 
                     np.array([1.0, np.NAN, 2.0]), equal_nan=True)
Out[24]: True
Кстати, это не работает со строками. Сравнивая массивы со строками, получим:TypeError("ufunc 'isfinite' not supported for the input types, and the inputs could not be safely coerced to any supported types according to the casting rule ''safe''")
0

import numpy
class FloatOrNaN(numpy.float_):
    def __eq__(self, other):
        return (numpy.isnan(self) and numpy.isnan(other)) or super(FloatOrNaN,self).__eq__(other)

a = [1., np.nan, 2.]
one = numpy.array([FloatOrNaN(val) for val in a], dtype=object)
two = numpy.array([FloatOrNaN(val) for val in a], dtype=object)
print one == two   # yields  array([ True,  True,  True], dtype=bool)

Это толкает безобразие в dtype, за счет создания внутреннего рабочего python вместо c (Cython / etc исправит это). Это, однако, значительно снижает стоимость памяти.

Все еще немного некрасиво, хотя :(

Похожие вопросы