Numpy: уникальные записи и записи NaN (Trac # 1514)

Созданный на 19 окт. 2012  ·  14Комментарии  ·  Источник: numpy/numpy

_Оригинальный билет http://projects.scipy.org/numpy/ticket/1514 от 18.06.2010 пользователем trac rspringuel, назначен unknown._

Когда unique работает с массивом с несколькими записями NaN, его возврат включает NaN для каждой записи, которая была NaN в исходном массиве.

Примеры:
a = random.randint (5, размер = 100) .astype (float)

a [12] = nan # добавить одну запись nan
уникальный (а)
массив ([0., 1., 2., 3., 4., NaN])
a [20] = nan # добавить секунду
уникальный (а)
массив ([0., 1., 2., 3., 4., NaN, NaN])
a [13] = нан
unique (a) # и третий
массив ([0., 1., 2., 3., 4., NaN, NaN, NaN])

Вероятно, это связано с тем, что x == y оценивается как False, если и x, и y равны NaN. В Unique необходимо добавить «or (isnan (x) and isnan (y))» к условному выражению, которое проверяет наличие значения в уже определенных значениях. Я не знаю, были ли уникальные жизни в numpy, и не мог найти их, когда искал, поэтому я не могу внести изменения сам (или даже быть уверенным, каким должен быть точный синтаксис условного выражения).

Кроме того, для исправления поведения можно использовать следующую функцию.

def nanunique (x):
а = numpy.unique (x)
r = []
для я в:
если я в r или (numpy.isnan (i) и numpy.any (numpy.isnan (r))):
Продолжить
еще:
r.append (я)
вернуть numpy.array (r)

00 - Bug Other

Самый полезный комментарий

Сегодня я столкнулся с той же проблемой. Ядро подпрограммы np.unique - это вычисление маски на нераскрытом отсортированном массиве в numpy / lib / arraysetops.py, чтобы найти, когда значения изменяются в этом отсортированном массиве:

mask = np.empty(aux.shape, dtype=np.bool_)
mask[:1] = True
mask[1:] = aux[1:] != aux[:-1]

Это можно было бы заменить чем-то вроде следующего, что в значительной степени похоже на комментарий Хаймефрио около 5 лет назад, но позволяет избежать вызова argmin:

mask = np.empty(aux.shape, dtype=np.bool_)
mask[:1] = True
if (aux.shape[0] > 0 and isinstance(aux[-1], (float, np.float16,
                                              np.float32, np.float64))
    and np.isnan(aux[-1])):
    aux_firstnan = np.searchsorted(aux, np.nan, side='left')
    mask[1:aux_firstnan] = (aux[1:aux_firstnan] != aux[:aux_firstnan-1])
    mask[aux_firstnan] = True
    mask[aux_firstnan+1:] = False
else:
    mask[1:] = aux[1:] != aux[:-1]

Проведя несколько экспериментов с% timeit, я заметил, что время выполнения не превышает <10%, если массив большой и очень мало NaN (скажем, 10 NaN из 1 миллиона), а для таких больших массивов он действительно работает быстрее, если их много. NaN.

С другой стороны, если массивы небольшие (например, 10 записей), это значительно снижает производительность, потому что проверка на float и NaN относительно дорога, а время выполнения может увеличиваться до нескольких значений. Это даже применимо, даже если нет NaN, поскольку это медленная проверка.

Если в массиве есть NaN, тогда он дает другой результат, объединяя NaN, что является сутью всего этого. Таким образом, в этом случае на самом деле вопрос заключается в том, чтобы получить желаемый результат (все NaN, объединенные в одну группу значений) немного медленнее, чем получение нежелательного результата (каждый NaN в своей собственной группе значений) немного быстрее.

Наконец, обратите внимание, что этот патч не исправит поиск уникальных значений, включающих составные объекты, содержащие NaN, например, в этом примере:

a = np.array([[0,1],[np.nan, 1], [np.nan, 1]])
np.unique(a, axis=0)

который все равно вернется

array([[ 0.,  1.],
       [nan,  1.],
       [nan,  1.]])

Все 14 Комментарий

Пользователь _trac rspringuel написал 18.06.2010_

Блин, чтобы использовать блоки кода выше. Это действительно влияет только на код исправления, поэтому я просто перепубликую это:

def nanunique(x):
    a = numpy.unique(x)
    r = []
    for i in a:
        if i in r or (numpy.isnan(i) and numpy.any(numpy.isnan(r))):
            continue
        else:
            r.append(i)
    return numpy.array(r)

Исправлена.

Я все еще вижу эту проблему с последним мастером. Какой коммит должен был исправить это? Если я чего-то не упускаю, я предлагаю повторно открыть эту проблему.

Это легко исправить для поплавков, но я не вижу простого выхода для сложных или структурированных типов данных. Составим быстрый PR и обсудим варианты там.

@jaimefrio Я исправил для уникального использования

    if issubclass(aux.dtype.type, np.inexact):
        # nans always compare unequal, so encode as integers
        tmp = aux.searchsorted(aux)
    else:
        tmp = aux
    flag = np.concatenate(([True], tmp[1:] != tmp[:-1]))

но похоже, что все остальные операции тоже имеют проблемы. Может нам понадобится nan_equal, nan_not_equal ufuncs, а может быть что-то в nanfuntions.

Сортировка aux для себя - хитрый трюк! Хотя поиск по сортировке _ всего_ немного расточительно, в идеале мы хотели бы обнаружить первую запись с nan, возможно, что-то вроде, после создания aux и flag как сейчас, делая :

if not aux[-1] == aux[-1]:
    nanidx = np.argmin(aux == aux)
    nanaux = aux[nanidx:].searchsorted(aux[nanidx:])
    flag[nanidx+1:] = nanaux[1:] != nanaux[:-1]

или что-то подобное после исправления всех ошибок off by one, которые я, вероятно, там внес.

Этот последний мой подход будет работать для типов с плавающей запятой и сложных типов, но не работает для структурированных типов с полями с плавающей запятой. Но я все еще считаю, что трюк с поисковой сортировкой, даже если он работает для всех типов, слишком расточителен. Некоторые тайминги:

In [10]: a = np.random.randn(1000)

In [11]: %timeit np.unique(a)
10000 loops, best of 3: 69.5 us per loop

In [12]: b = np.sort(a)

In [13]: %timeit b.searchsorted(b)
10000 loops, best of 3: 28.1 us per loop

Это будет снижение производительности на 40%, что может быть нормальным для функции nanunique , но, вероятно, не для общего случая.

Вызывается 2019, проблема OP все еще актуальна, и код воспроизводится.

@jaimefrio, почему по умолчанию мы не можем установить значение false?

Я имею в виду, что такое поведение в лучшем случае сбивает с толку, а производительность - не оправдание.

@ Demetrio92, хотя я ценю вашу попытку сдвинуть этот вопрос с мертвой точки, ирония / сарказм в Интернете могут быть интерпретированы по-разному разными людьми, пожалуйста, будьте добры. Для некоторых из нас очень важна производительность, и мы не добавляем случайно код, который замедляет работу.

PR # 5487 может быть лучшим местом для комментариев или предложений, как двигаться дальше.

Изменить: исправить номер PR

Эта проблема, кажется, открыта уже 8 лет, но я просто хочу сказать +1, чтобы сделать поведение по умолчанию для numpy.unique правильным, а не быстрым. Это нарушило мой код, и я уверен, что другие пострадают от этого. Мы можем иметь необязательный параметр «fast = False» и документировать поведение nan для fast и nans. Я был бы удивлен, если np.unique очень часто является узким местом производительности в критичных ко времени приложениях.

Сегодня я столкнулся с той же проблемой. Ядро подпрограммы np.unique - это вычисление маски на нераскрытом отсортированном массиве в numpy / lib / arraysetops.py, чтобы найти, когда значения изменяются в этом отсортированном массиве:

mask = np.empty(aux.shape, dtype=np.bool_)
mask[:1] = True
mask[1:] = aux[1:] != aux[:-1]

Это можно было бы заменить чем-то вроде следующего, что в значительной степени похоже на комментарий Хаймефрио около 5 лет назад, но позволяет избежать вызова argmin:

mask = np.empty(aux.shape, dtype=np.bool_)
mask[:1] = True
if (aux.shape[0] > 0 and isinstance(aux[-1], (float, np.float16,
                                              np.float32, np.float64))
    and np.isnan(aux[-1])):
    aux_firstnan = np.searchsorted(aux, np.nan, side='left')
    mask[1:aux_firstnan] = (aux[1:aux_firstnan] != aux[:aux_firstnan-1])
    mask[aux_firstnan] = True
    mask[aux_firstnan+1:] = False
else:
    mask[1:] = aux[1:] != aux[:-1]

Проведя несколько экспериментов с% timeit, я заметил, что время выполнения не превышает <10%, если массив большой и очень мало NaN (скажем, 10 NaN из 1 миллиона), а для таких больших массивов он действительно работает быстрее, если их много. NaN.

С другой стороны, если массивы небольшие (например, 10 записей), это значительно снижает производительность, потому что проверка на float и NaN относительно дорога, а время выполнения может увеличиваться до нескольких значений. Это даже применимо, даже если нет NaN, поскольку это медленная проверка.

Если в массиве есть NaN, тогда он дает другой результат, объединяя NaN, что является сутью всего этого. Таким образом, в этом случае на самом деле вопрос заключается в том, чтобы получить желаемый результат (все NaN, объединенные в одну группу значений) немного медленнее, чем получение нежелательного результата (каждый NaN в своей собственной группе значений) немного быстрее.

Наконец, обратите внимание, что этот патч не исправит поиск уникальных значений, включающих составные объекты, содержащие NaN, например, в этом примере:

a = np.array([[0,1],[np.nan, 1], [np.nan, 1]])
np.unique(a, axis=0)

который все равно вернется

array([[ 0.,  1.],
       [nan,  1.],
       [nan,  1.]])

«Если в массиве есть NaN, он дает другой результат, комбинируя NaN, что является сутью всего этого».

+1

Функцию, которая возвращает список, содержащий повторяющиеся элементы, например, список с более чем 1 NaN, не следует называть "уникальной". Если желательны повторяющиеся элементы в случае NaN, то это должен быть только особый случай, который по умолчанию отключен, например numpy.unique(..., keep_NaN=False) .

@ufmayer отправить PR!

+1
Я бы также поддержал возврат NaN только один раз

Была ли эта страница полезной?
0 / 5 - 0 рейтинги

Смежные вопросы

marcocaccin picture marcocaccin  ·  4Комментарии

manuels picture manuels  ·  3Комментарии

navytux picture navytux  ·  4Комментарии

Foadsf picture Foadsf  ·  3Комментарии

keithbriggs picture keithbriggs  ·  3Комментарии