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

120

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

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

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

Фред Фу
источник
не работает ли проверка ввода пользователя в этом сценарии? Как в проверке NaN перед вставкой
Woot4Moo
@ Woot4Moo: нет, библиотека принимает scipy.sparseна вход массивы или матрицы NumPy .
Fred Foo
2
Если вы делаете это много, я слышал хорошие вещи о источнике перегрузок ( pypi.python.org/pypi/Bottleneck )
матовое

Ответы:

161

Решение Рэя хорошее. Однако на моей машине это примерно в 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 происходит медленнее, чем в их отсутствие. Также кажется, что он становится медленнее по мере приближения NaN к началу массива. С другой стороны, 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
NPE
источник
1
np.minработает быстрее, когда массив не содержит NaN, что является моим ожидаемым вводом. Но я решил принять этот в любом случае, потому что она ловит infи neginfкак хорошо.
Fred Foo
2
Это улавливает только infили, -infесли входные данные содержат оба, и возникают проблемы, если входные данные содержат большие, но конечные значения, которые переполняются при сложении.
user2357112 поддерживает Монику,
4
min и max не требуется ветвления для данных с плавающей запятой на чипах x86 с поддержкой sse. Так что с numpy 1,8 мин не будет медленнее, чем sum, на моем amd phenom он даже на 20% быстрее.
jtaylor
1
На моем Intel Core i5 с numpy 1.9.2 на OSX np.sumвсе еще примерно на 30% быстрее, чем np.min.
Мэтью Бретт,
np.isnan(x).any(0)немного быстрее , чем np.sumи np.minна моей машине, хотя там могут быть некоторые нежелательные кэширование.
jsignell
28

Думаю np.isnan(np.min(X))надо делать то, что хочешь.

луч
источник
Хммм ... это всегда O (n), хотя может быть O (1) (для некоторых массивов).
user48956
17

Даже если есть принятый ответ, я хотел бы продемонстрировать следующее (с 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(.)вариант кажется наиболее стабильным.

есть
источник
1
Я подозреваю, что это зависит не столько от ОС, сколько от базовой реализации BLAS и компилятора C. Спасибо, но скалярное произведение чуть более вероятно переполнится, если оно xсодержит большие значения, и я также хочу проверить inf.
Fred Foo
1
Ну, вы всегда можете сделать скалярное произведение с единицами и использовать isfinite(.). Я просто хотел указать на огромный разрыв в производительности. Спасибо
ешь
То же и на моей машине.
kawing-chiu 01
1
Умно, не так ли? Как предполагает Фред Фу , любое повышение эффективности подхода, основанного на скалярном произведении, почти наверняка связано с локальной установкой NumPy, связанной с оптимизированной реализацией BLAS, такой как ATLAS, MKL или OpenBLAS. Так, например, обстоит дело с Anaconda. Учитывая это, этот скалярный продукт будет распараллеливаться по всем доступным ядрам. Чего нельзя сказать о подходах на основе min- или sum-, которые работают только в одном ядре. Ergo, этот разрыв в производительности.
Сесил Карри
16

Здесь есть два общих подхода:

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

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

введите описание изображения здесь

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)",
)
Нико Шлёмер
источник
4
  1. использовать .any ()

    if numpy.isnan(myarray).any()

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

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

woso
источник
3

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

MSeifert
источник
1

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

index = next((i for (i,n) in enumerate(iterable) if n!=n), None)
vitiral
источник