Сравнивая массивы 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.
Ответы
Ответ 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
Ответ 3
Numpy 1.10 добавила ключевое слово 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
Ответ 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 установит это). Тем не менее, это значительно снижает затраты на память.
Тем не менее, уродливый, хотя: (