Wykryć, czy tablica NumPy zawiera co najmniej jedną wartość nienumeryczną?

103

Muszę napisać funkcję, która będzie wykrywać, czy dane wejściowe zawierają co najmniej jedną wartość nienumeryczną. Jeśli zostanie znaleziona wartość nieliczbowa, zgłoszę błąd (ponieważ obliczenia powinny zwracać tylko wartość liczbową). Liczba wymiarów tablicy wejściowej nie jest z góry znana - funkcja powinna podawać poprawną wartość niezależnie od ndim. Dodatkową komplikacją może być pojedyncza liczba zmiennoprzecinkowa numpy.float64lub nawet coś dziwnego, jak tablica zerowymiarowa.

Oczywistym sposobem rozwiązania tego problemu jest napisanie funkcji rekurencyjnej, która wykonuje iterację po każdym iterowalnym obiekcie w tablicy, dopóki nie znajdzie elementu, który nie jest iterabem. Zastosuje numpy.isnan()funkcję do każdego obiektu, którego nie można iterować. Jeśli zostanie znaleziona co najmniej jedna wartość nieliczbowa, funkcja natychmiast zwróci False. W przeciwnym razie, jeśli wszystkie wartości w iterowalnej są liczbowe, ostatecznie zwróci True.

Działa to dobrze, ale jest dość powolne i spodziewam się, że NumPy ma o wiele lepszy sposób na zrobienie tego. Jaka jest alternatywa, która jest szybsza i bardziej otępiała?

Oto moja makieta:

def contains_nan( myarray ):
    """
    @param myarray : An n-dimensional array or a single float
    @type myarray : numpy.ndarray, numpy.array, float
    @returns: bool
    Returns true if myarray is numeric or only contains numeric values.
    Returns false if at least one non-numeric value exists
    Not-A-Number is given by the numpy.isnan() function.
    """
    return True
Salim Fadhley
źródło
3
Twój opis dla contains_nanwygląda podejrzanie: „Zwraca fałsz, jeśli istnieje co najmniej jedna wartość nieliczbowa”. Spodziewałbym contains_nansię powrotu, Truejeśli tablica zawiera NaN.
Samuel Tardieu
A co z danymi wejściowymi, takimi jak array(['None', 'None'], dtype=object)? Czy takie wejście powinno powodować tylko wyjątek?
Finn Årup Nielsen
NIE używaj float('nan') in x. To nie działa.
Charlie Parker

Odpowiedzi:

183

Powinno to być szybsze niż iterowanie i będzie działać niezależnie od kształtu.

numpy.isnan(myarray).any()

Edycja: 30x szybciej:

import timeit
s = 'import numpy;a = numpy.arange(10000.).reshape((100,100));a[10,10]=numpy.nan'
ms = [
    'numpy.isnan(a).any()',
    'any(numpy.isnan(x) for x in a.flatten())']
for m in ms:
    print "  %.2f s" % timeit.Timer(m, s).timeit(1000), m

Wyniki:

  0.11 s numpy.isnan(a).any()
  3.75 s any(numpy.isnan(x) for x in a.flatten())

Bonus: działa dobrze dla typów NumPy nie będących tablicami:

>>> a = numpy.float64(42.)
>>> numpy.isnan(a).any()
False
>>> a = numpy.float64(numpy.nan)
>>> numpy.isnan(a).any()
True
Paweł
źródło
1
z numpy 1.7 wersja flatten () jest tylko dwa razy szybsza niż pierwsza
Christian Geier
Dlaczego coś takiego float('nan') in xnie działa? Wypróbowałem to i python wraca Falsegdzie x = [1,2,3,float('nan')].
Charlie Parker
1
@CharlieParker z tego samego powodu, dla którego float ('nan') == float ('nan') zwróci wartość False. NaN nie jest równe NaN. Tutaj więcej informacji: stackoverflow.com/questions/10034149/…
Muppet
1
@mab: To dlatego, że wywołanie numpy.anygenexp po prostu zwraca genexp; w rzeczywistości nie wykonujesz obliczeń, o których myślisz, że jesteś. Nigdy nie wzywaj numpy.anyGenexp.
user2357112 obsługuje Monikę
W prawdziwym scenariuszu debugowania zalecałbym również przyjrzenie się np.isfinitezamiast np.isnanwykrywania przepełnień liczbowych, niestabilności itp.
Ben Usman
18

Jeśli nieskończoność jest możliwą wartością, użyłbym numpy.isfinite

numpy.isfinite(myarray).all()

Jeśli wynikiem powyższego jest True, to myarraynie zawiera wartości numpy.nan, numpy.influb -numpy.inf.

numpy.nanbędzie OK z numpy.infwartościami, na przykład:

In [11]: import numpy as np

In [12]: b = np.array([[4, np.inf],[np.nan, -np.inf]])

In [13]: np.isnan(b)
Out[13]: 
array([[False, False],
       [ True, False]], dtype=bool)

In [14]: np.isfinite(b)
Out[14]: 
array([[ True, False],
       [False, False]], dtype=bool)
Akavall
źródło
Dlaczego coś takiego float('nan') in xnie działa? Wypróbowałem to i python wraca Falsegdzie x = [1,2,3,float('nan')].
Charlie Parker
1
@CharlieParker, ponieważ dwa nans nie są uważane za równe sobie. Spróbuj float('nan') == float('nan').
Akavall
ciekawy. Dlaczego nie są uważani za równych?
Charlie Parker
1
@CharlieParker, nie sądzę, żebym mógł udzielić tutaj bardzo dobrej odpowiedzi. Może właśnie tego szukasz: stackoverflow.com/questions/1565164/…
Akavall
4

Pfft! Mikrosekundy! Nigdy nie rozwiązuj problemu w mikrosekundach, który można rozwiązać w nanosekundach.

Zwróć uwagę, że zaakceptowana odpowiedź:

  • iteruje po całych danych, niezależnie od tego, czy znaleziono nan
  • tworzy tymczasową tablicę o rozmiarze N, która jest zbędna.

Lepszym rozwiązaniem jest zwrócenie True natychmiast po znalezieniu NAN:

import numba
import numpy as np

NAN = float("nan")

@numba.njit(nogil=True)
def _any_nans(a):
    for x in a:
        if np.isnan(x): return True
    return False

@numba.jit
def any_nans(a):
    if not a.dtype.kind=='f': return False
    return _any_nans(a.flat)

array1M = np.random.rand(1000000)
assert any_nans(array1M)==False
%timeit any_nans(array1M)  # 573us

array1M[0] = NAN
assert any_nans(array1M)==True
%timeit any_nans(array1M)  # 774ns  (!nanoseconds)

i działa dla n-wymiarów:

array1M_nd = array1M.reshape((len(array1M)/2, 2))
assert any_nans(array1M_nd)==True
%timeit any_nans(array1M_nd)  # 774ns

Porównaj to z rozwiązaniem numpy natywnym:

def any_nans(a):
    if not a.dtype.kind=='f': return False
    return np.isnan(a).any()

array1M = np.random.rand(1000000)
assert any_nans(array1M)==False
%timeit any_nans(array1M)  # 456us

array1M[0] = NAN
assert any_nans(array1M)==True
%timeit any_nans(array1M)  # 470us

%timeit np.isnan(array1M).any()  # 532us

Metoda wczesnego wyjścia polega na przyspieszeniu o 3 rzędy lub wielkości (w niektórych przypadkach). Nie jest zbyt odrapany, jak na prostą adnotację.

user48956
źródło
3

Za pomocą numpy 1.3 lub svn możesz to zrobić

In [1]: a = arange(10000.).reshape(100,100)

In [3]: isnan(a.max())
Out[3]: False

In [4]: a[50,50] = nan

In [5]: isnan(a.max())
Out[5]: True

In [6]: timeit isnan(a.max())
10000 loops, best of 3: 66.3 µs per loop

Sposób traktowania nans w porównaniach nie był spójny we wcześniejszych wersjach.


źródło
Dlaczego coś takiego float('nan') in xnie działa? Wypróbowałem to i python wraca Falsegdzie x = [1,2,3,float('nan')].
Charlie Parker
@CharlieParker ... ponieważ porównanie z NAN nie daje tego, czego oczekujesz. NAN jest traktowany jak logiczna wartość NULL (= nie wiem). float("nan")==float("nan")dać False(choć jest to wykonalne, powinno prawdopodobnie zwrócić NAN lub None). Podobnie dziwność z NAN i boolen NULL jest prawdą w wielu językach, w tym w SQL (gdzie NULL = NULL nigdy nie jest prawdą).
user48956
2

(np.where(np.isnan(A)))[0].shape[0]będzie większa niż 0gdyby Azawierała co najmniej jeden element nan, Amoże być n x mmacierzą.

Przykład:

import numpy as np

A = np.array([1,2,4,np.nan])

if (np.where(np.isnan(A)))[0].shape[0]: 
    print "A contains nan"
else:
    print "A does not contain nan"
Ting On Chan
źródło