Szybkie sprawdzenie NaN w NumPy

120

Szukam najszybszego sposobu, aby sprawdzić wystąpienie NaN ( np.nan) w tablicy NumPy X. np.isnan(X)nie wchodzi w rachubę, ponieważ buduje boolowską tablicę kształtów X.shape, która jest potencjalnie gigantyczna.

Próbowałem np.nan in X, ale to nie działa, ponieważ np.nan != np.nan. Czy w ogóle istnieje szybki i oszczędzający pamięć sposób, aby to zrobić?

(Do tych, którzy pytają „jak gigantyczne”: nie mogę powiedzieć. To jest walidacja danych wejściowych dla kodu biblioteki).

Fred Foo
źródło
czy sprawdzanie poprawności danych wejściowych użytkownika nie działa w tym scenariuszu? Jak w przypadku NaN przed wstawką
Woot4Moo
@ Woot4Moo: nie, biblioteka przyjmuje tablice lub scipy.sparsemacierze NumPy jako dane wejściowe.
Fred Foo
2
Jeśli często to robisz, słyszałem dobre rzeczy o wąskim gardle ( pypi.python.org/pypi/Bottleneck )
mat.

Odpowiedzi:

160

Rozwiązanie Raya jest dobre. Jednak na moim komputerze jest około 2,5x szybszy w użyciu numpy.sumzamiast 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

W przeciwieństwie min, sumnie wymaga rozgałęzienia, które na nowoczesnym sprzęcie wydaje się być dość kosztowne. To jest prawdopodobnie powód, dla którego sumjest szybszy.

edit Powyższy test został przeprowadzony z pojedynczym NaN w środku tablicy.

Warto zauważyć, że minjest wolniejszy w obecności NaN niż w przypadku ich braku. Wydaje się również, że działa wolniej, gdy NaN zbliżają się do początku tablicy. Z drugiej strony sumprzepustowość wydaje się stała, niezależnie od tego, czy istnieją NaN i gdzie się znajdują:

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
źródło
1
np.minjest szybszy, gdy tablica nie zawiera NaN, co jest moim oczekiwanym wejściem. Ale i tak zdecydowałem się to zaakceptować, ponieważ łapie infi neginfteż.
Fred Foo
2
To tylko wychwytuje influb -infjeśli dane wejściowe zawierają oba, i występują problemy, jeśli dane wejściowe zawierają duże, ale skończone wartości, które po dodaniu przepełniają się.
user2357112 obsługuje Monikę
4
min i max nie muszą rozgałęziać się na dane zmiennoprzecinkowe na chipach x86 obsługujących sse. Tak więc od numpy 1.8 min nie będzie wolniejsze niż suma, na moim amd fenomen jest nawet 20% szybsze.
jtaylor
1
Na moim Intel Core i5, z numpy 1.9.2 na OSX, np.sumjest nadal około 30% szybszy niż np.min.
Matthew Brett
np.isnan(x).any(0)jest nieco szybszy niż np.sumi np.minna moim komputerze, chociaż może występować niechciane buforowanie.
jsignell
28

Myślę, że np.isnan(np.min(X))powinienem robić, co chcesz.

Promień
źródło
Hmmm ... to zawsze jest O (n), kiedy może być O (1) (dla niektórych tablic).
user48956
17

Nawet jeśli istnieje akceptowana odpowiedź, chciałbym zademonstrować co następuje (w Pythonie 2.7.2 i Numpy 1.6.0 w systemie 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

Tak więc naprawdę skuteczny sposób może w dużym stopniu zależeć od systemu operacyjnego. W każdym razie dot(.)oparty wydaje się być najbardziej stabilny.

jeść
źródło
1
Podejrzewam, że zależy to nie tyle od systemu operacyjnego, ile od podstawowej implementacji BLAS i kompilatora C. Dzięki, ale iloczyn skalarny jest tylko odrobinę bardziej skłonny do przepełnienia, gdy xzawiera duże wartości, i chcę również sprawdzić inf.
Fred Foo,
1
Cóż, zawsze możesz zrobić iloczyn skalarny z jedynkami i użyć isfinite(.). Chciałem tylko zwrócić uwagę na ogromną różnicę w wydajności. Dzięki
zjedz
To samo na moim komputerze.
kawing-chiu
1
Sprytne, nie? Jak sugeruje Fred Foo , każdy wzrost wydajności podejścia opartego na produkcie kropkowym jest prawie na pewno zasługą lokalnej instalacji NumPy połączonej ze zoptymalizowaną implementacją BLAS, taką jak ATLAS, MKL lub OpenBLAS. Tak jest na przykład w przypadku Anacondy. Biorąc to pod uwagę, ten iloczyn skalarny będzie równoległy do wszystkich dostępnych rdzeni. Tego samego nie można powiedzieć o podejściach opartych na min- lub sum, które są ograniczone do jednego rdzenia. Ergo, ta różnica w wydajności.
Cecil Curry
16

Istnieją dwa ogólne podejścia:

  • Sprawdź każdy element tablicy nani weź any.
  • Zastosuj jakąś łączną operację, która zachowuje nans (like sum) i sprawdź jej wynik.

Chociaż pierwsze podejście jest z pewnością najczystsze, ciężka optymalizacja niektórych skumulowanych operacji (szczególnie tych, które są wykonywane w BLAS, jak np. dot) Może sprawić, że będą one dość szybkie. Zauważ, że dotpodobnie jak niektóre inne operacje BLAS, w pewnych warunkach są wielowątkowe. To wyjaśnia różnicę w prędkości między różnymi maszynami.

wprowadź opis obrazu tutaj

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)",
)
Nico Schlömer
źródło
4
  1. use .any ()

    if numpy.isnan(myarray).any()

  2. numpy.isfinite może lepiej niż isnan do sprawdzenia

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

woso
źródło
3

Jeśli czujesz się komfortowo z pozwala na stworzenie szybkiego zwarcia (zatrzymuje się po znalezieniu NaN) funkcja:

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

Jeśli nie ma NaNfunkcji, może być wolniejsza niż np.min, myślę, że to dlatego, że np.minużywa przetwarzania wieloprocesowego dla dużych tablic:

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

Ale jeśli w tablicy znajduje się NaN, zwłaszcza jeśli jego pozycja znajduje się na niskich indeksach, jest to znacznie szybsze:

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

Podobne wyniki można osiągnąć z Cythonem lub rozszerzeniem C, są one nieco bardziej skomplikowane (lub łatwo dostępne bottleneck.anynan), ale ostatecznie robią to samo, co moja anynanfunkcja.

MSeifert
źródło
1

Wiąże się z tym pytanie, jak znaleźć pierwsze wystąpienie NaN. To najszybszy sposób radzenia sobie z tym, o czym wiem:

index = next((i for (i,n) in enumerate(iterable) if n!=n), None)
witirusowy
źródło