Подтвердить что ты не робот

Сравнивая массивы NumPy так, чтобы NaN сравнивали равные

Существует ли идиоматический способ сравнения двух массивов NumPy, которые будут рассматривать NaN как равные друг другу (но не равные чему-либо, кроме 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)))

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

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

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

4b9b3361

Ответ 1

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

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

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

np.all(a==b)

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

Ответ 2

Отказ от ответственности: я не рекомендую это для регулярного использования, и я бы не использовал его сам, но я мог представить себе редкие обстоятельства, при которых это может быть полезно.

Если массивы имеют одинаковую форму и тип, вы можете использовать низкоуровневый 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

Ответ 4

Не уверен, что это лучше, но мысль...

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 установит это). Тем не менее, это значительно снижает затраты на память.

Тем не менее, уродливый, хотя: (