Быстрая проверка для NaN в NumPy

Я ищу самый быстрый способ проверить нахождение NaN (np.nan) в массиве NumPy X. np.isnan(X) не может быть и речи, так как он создает логический массив формы X.shape, который потенциально гигантский.

Я пробовал np.nan in X, но это, похоже, не работает, потому что np.nan != np.nan. Есть ли быстрый и эффективный с точки зрения памяти способ сделать это вообще?

(Тем, кто спросит "как гигантский": я не могу сказать. Это входная проверка для кода библиотеки.)

Ответ 1

Раствор хорошо. Однако на моей машине примерно в 2,5 раза быстрее использовать numpy.sum вместо numpy.min:

In [13]: %timeit np.isnan(np.min(x))
1000 loops, best of 3: 244 us per loop

In [14]: %timeit np.isnan(np.sum(x))
10000 loops, best of 3: 97.3 us per loop

В отличие от min, sum не требует ветвления, что на современном оборудовании имеет тенденцию быть довольно дорогостоящим. Вероятно, это причина, по которой sum работает быстрее.

edit Вышеуказанный тест был выполнен с одним NaN справа в середине массива.

Интересно отметить, что min медленнее в присутствии NaN, чем в их отсутствие. Он также кажется медленнее, так как NaNs приближаются к началу массива. С другой стороны, пропускная способность sum кажется постоянной независимо от того, существуют ли NaN и где они расположены:

In [40]: x = np.random.rand(100000)

In [41]: %timeit np.isnan(np.min(x))
10000 loops, best of 3: 153 us per loop

In [42]: %timeit np.isnan(np.sum(x))
10000 loops, best of 3: 95.9 us per loop

In [43]: x[50000] = np.nan

In [44]: %timeit np.isnan(np.min(x))
1000 loops, best of 3: 239 us per loop

In [45]: %timeit np.isnan(np.sum(x))
10000 loops, best of 3: 95.8 us per loop

In [46]: x[0] = np.nan

In [47]: %timeit np.isnan(np.min(x))
1000 loops, best of 3: 326 us per loop

In [48]: %timeit np.isnan(np.sum(x))
10000 loops, best of 3: 95.9 us per loop

Ответ 2

Я думаю, что np.isnan(np.min(X)) должен делать то, что вы хотите.

Ответ 3

Даже если есть приемлемый ответ, я хотел бы продемонстрировать следующее (с Python 2.7.2 и Numpy 1.6.0 на Vista):

In []: x= rand(1e5)
In []: %timeit isnan(x.min())
10000 loops, best of 3: 200 us per loop
In []: %timeit isnan(x.sum())
10000 loops, best of 3: 169 us per loop
In []: %timeit isnan(dot(x, x))
10000 loops, best of 3: 134 us per loop

In []: x[5e4]= NaN
In []: %timeit isnan(x.min())
100 loops, best of 3: 4.47 ms per loop
In []: %timeit isnan(x.sum())
100 loops, best of 3: 6.44 ms per loop
In []: %timeit isnan(dot(x, x))
10000 loops, best of 3: 138 us per loop

Таким образом, действительно эффективный способ может сильно зависеть от операционной системы. В любом случае dot(.), по-видимому, является наиболее стабильным.

Ответ 4

Здесь есть два основных подхода:

  • Проверьте каждый элемент массива на nan и возьмите any.
  • Примените некоторую накопительную операцию, которая сохраняет nan (например, sum), и проверьте ее результат.

Хотя первый подход, безусловно, является самым чистым, интенсивная оптимизация некоторых из кумулятивных операций (особенно тех, которые выполняются в BLAS, например, dot), может сделать их довольно быстрыми. Обратите внимание, что dot, как и некоторые другие операции BLAS, является многопоточным при определенных условиях. Это объясняет разницу в скорости между разными машинами.

enter image description here

import numpy
import perfplot


def min(a):
    return numpy.isnan(numpy.min(a))


def sum(a):
    return numpy.isnan(numpy.sum(a))


def dot(a):
    return numpy.isnan(numpy.dot(a, a))


def any(a):
    return numpy.any(numpy.isnan(a))


def einsum(a):
    return numpy.isnan(numpy.einsum("i->", a))


perfplot.show(
    setup=lambda n: numpy.random.rand(n),
    kernels=[min, sum, dot, any, einsum],
    n_range=[2 ** k for k in range(20)],
    logx=True,
    logy=True,
    xlabel="len(a)",
)

Ответ 5

Если вам удобно с он позволяет создать быстрое короткое замыкание (останавливается, как только обнаружено NaN):

import numba as nb
import math

@nb.njit
def anynan(array):
    array = array.ravel()
    for i in range(array.size):
        if math.isnan(array[i]):
            return True
    return False

Если нет NaN, функция может быть медленнее, чем np.min, я думаю, что поскольку np.min использует многопроцессорность для больших массивов:

import numpy as np
array = np.random.random(2000000)

%timeit anynan(array)          # 100 loops, best of 3: 2.21 ms per loop
%timeit np.isnan(array.sum())  # 100 loops, best of 3: 4.45 ms per loop
%timeit np.isnan(array.min())  # 1000 loops, best of 3: 1.64 ms per loop

Но в случае, если в массиве есть NaN, особенно если он находится на низких индексах, то он намного быстрее:

array = np.random.random(2000000)
array[100] = np.nan

%timeit anynan(array)          # 1000000 loops, best of 3: 1.93 µs per loop
%timeit np.isnan(array.sum())  # 100 loops, best of 3: 4.57 ms per loop
%timeit np.isnan(array.min())  # 1000 loops, best of 3: 1.65 ms per loop

Аналогичные результаты могут быть достигнуты с помощью Cython или расширения C, они немного сложнее (или легко доступны как bottleneck.anynan) но ультимативно делать то же самое, что и моя функция anynan.

Ответ 6

  1. используйте .any()

    if numpy.isnan(myarray).any()

  2. numpy.isfinite может быть лучше, чем isnan для проверки

    if not np.isfinite(prop).all()

Ответ 7

В связи с этим возникает вопрос, как найти первое вхождение NaN. Это самый быстрый способ справиться с тем, что я знаю:

index = next((i for (i,n) in enumerate(iterable) if n!=n), None)