Швидка перевірка наявності 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 як вхідні дані.
Фред Фоо

2
Якщо ви цим багато займаєтесь, я чув хороші речі про Bottleneck ( pypi.python.org/pypi/Bottleneck )
мат.

Відповіді:


160

Рішення Рея добре. Однак, на моїй машині це близько 2.5x швидше використовувати 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швидше.

редагування Вищевказаний тест проводили з одним 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

1
np.minшвидше, коли масив не містить NaN, що є моїм очікуваним входом. Але я вирішив прийняти цей в будь-якому випадку, тому що вона ловить infі neginfяк добре.
Фред Фоо

2
Це тільки ловить infабо -infякщо вхід містить обидва, і у нього виникають проблеми, якщо вхід містить великі, але кінцеві значення, які переповнюються, коли додаються разом.
user2357112 підтримує Моніку

4
min та max не потрібно розгалужувати дані з плаваючою комою на чіпах x86 x86. Так що з numpy 1,8 хв не буде повільніше, ніж сума, на мій amd fenom його навіть на 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містить великі значення, і я також хочу перевірити наявність інф.
Фред Фоо

1
Ну, ви завжди можете робити точковий виріб з ними та використовувати isfinite(.). Я просто хотів вказати на величезний розрив у роботі. Спасибі
їжте

Те саме на моїй машині.
kawing-chiu

1
Розумний, ні? Як пропонує Фред Фоо , будь-яке підвищення ефективності точкового підходу, що базується на продуктах, майже напевно, завдяки локальній установці NumPy, пов'язаній з оптимізованою реалізацією BLAS, як ATLAS, MKL або OpenBLAS. Це стосується, наприклад, Анаконда. Враховуючи це, цей точковий продукт буде паралельний між усіма наявними ядрами. Те ж саме не можна сказати для підходів min- або на sumоснові, які обмежені одним ядром. Ерго, цей розрив у виконанні.
Сесіль Карі

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()


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функція.


1

З цим пов'язано питання, як знайти перше виникнення NaN. Це найшвидший спосіб впоратися з тим, що я знаю:

index = next((i for (i,n) in enumerate(iterable) if n!=n), None)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.