2010-11-21 1 views
23

sorted([2, float('nan'), 1]) возвращает [2, nan, 1]Python: функция сортировки брейки в присутствии нан

Я понимаю nan это странный объект, так что я не удивлюсь, если (по крайней мере на ActiveState Python 3.1) реализации. он появляется в случайных местах в результате сортировки. Но это также испортило сортировку для не-наном чисел в контейнере, что действительно неожиданно.

Я спросил related question о max, и на основании этого я понимаю, почему sort работает следующим образом. Но следует ли это считать ошибкой?

Документация просто говорит: «Верните новый отсортированный список [...]» без указания каких-либо деталей.

EDIT: Я согласен с тем, что это не нарушает стандарт IEEE. Однако, я думаю, это ошибка с точки зрения здравого смысла. Даже Microsoft, которая, как известно, часто допускает свои ошибки, признала это ошибкой и исправила ее в последней версии: http://connect.microsoft.com/VisualStudio/feedback/details/363379/bug-in-list-double-sort-in-list-which-contains-double-nan.

Во всяком случае, я в конечном итоге следующий @ ответ Хачика:

sorted(list_, key = lambda x : float('-inf') if math.isnan(x) else x) 

Я подозреваю, что это приводит к снижению производительности по сравнению с языком делать, что по умолчанию, но, по крайней мере, он работает (за исключением каких-либо ошибок, которые я представил).

+0

Не номер (NAN) - это недопустимый ввод для численного сортировки или чего-либо ожидающего числа; поэтому я бы не стал считать это ошибкой. – frayser

+0

@Frayser: это не совсем правильно. Это недействительно в Python? Нет, потому что Python не вызывает исключений. Это недействительно в IEEE754? Нет, потому что он обеспечивает очень специфическое поведение (по крайней мере, для тихого «нан»). Недействителен ли он в каком-либо другом стандарте? – max

+1

Хотя понятно, что «нан» случайно окажется где-то в результирующем списке, что труднее понять, так это, по-видимому, правильное поведение, чтобы некорректно упорядочить числовые значения в последнем: sorted ([1.0, 2.0, 3.0, float ('nan'), 4.0, 3.0, 2.0, 1.0]) => [1.0, 2.0, 3.0, nan, 1.0, 2.0, 3.0, 4.0]. См. Http://bugs.python.org/issue12286. – Noah

ответ

11

Предыдущие ответы полезны, но, возможно, неясно относительно корня проблемы.

На любом языке сортировка применяет заданный порядок, определенный функцией сравнения или каким-либо другим способом, над доменом входных значений. Например, меньшее, чем, a.k.a. operator <,, может использоваться повсюду в том и только том случае, если оно меньше, чем подходящее упорядочение по входным значениям.

Но это специально НЕ верно для значений с плавающей запятой и меньше: «NaN неупорядочен: он не равен, больше или меньше чем что-либо, включая его."(Clear проза из GNU C руководства, но относится ко всей современному IEEE754 основы с плавающей точкой)

Так что возможные решения:

  1. удалить пренебрежимо малого первым, что делает домен входа определенная через < (или другая используемая функция сортировки)
  2. Определите пользовательскую функцию сравнения (aka predicate), которая делает , определите заказ для NaN, например, меньше любого числа или больше , чем любое число.

Любой подход может быть использован на любом языке.

Практически, учитывая python, я бы предпочел удалить NaNs, если вам не все равно, о максимальной производительности, или если удаление NaN является желательным поведением в контексте.

В противном случае вы можете использовать подходящую функцию предиката через «cmp» в старых версиях python или через это и functools.cmp_to_key(). Последнее, конечно, немного неудобно, чем удаление NaNs в первую очередь. При определении этой функции предикатов потребуется не более производительности.

+0

IEEE 754 требует, чтобы max (NaN, 1) вернул 1. Было бы неплохо, если бы Python следовал стандарту, но это не так. Если он следует своим собственным правилам, он может по крайней мере иметь некоторые разумные правила, а не случайное нестабильное поведение. – max

+0

Чтобы уточнить, я согласен с вами, что 'float ('nan') <1 или float ('nan')> = 1' должен возвращать False. Похоже, что исключение было сделано в новейшем стандарте IEEE (IEEE 754 = IEEE 754-2008) для функций 'minimum' и' maximum' (которые должны возвращать номер), но не для 'sort' или обычного сравнения. – max

3

IEEE754 - это стандарт, который определяет операции с плавающей запятой в этом экземпляре. Этот стандарт определяет операцию сравнения операндов, по крайней мере один из которых представляет собой NaN, как ошибку. Следовательно, это не ошибка. Вам нужно иметь дело с NaN, прежде чем работать с массивом.

+4

-1 Python не следует за IEEE754, который требует наличия двух NaN: сигнализации и без сигнализации, а также двух операторов сравнения: сигнализации и без сигнализации. Кроме того, IEEE754-2008 специально требует, чтобы 'max' возвращал число по сравнению с' nan'. – max

+0

Если это сигнализация NaN (sNaN), то исключение будет вызвано аппаратным обеспечением. Для тихого NaN (qNaN) аппаратное обеспечение не будет создавать исключение, и было бы слишком обременительно ожидать, чтобы каждая библиотечная процедура обрабатывала значения с плавающей запятой для проверки на qNaN. –

+0

Если вы используете CPython на компьютере, чье оборудование FP основано на IEEE754, то это то, что вы получите. Кроме того, в каком смысле IEEE754 определяет max? –

5

Я не уверен, что ошибка, но обходной путь может быть следующим:

sorted(
    (2, 1, float('nan')), 
    lambda x,y: x is float('nan') and -1 
       or (y is float('nan') and 1 
       or cmp(x,y))) 

что приводит:

('nan', 1, 2) 

или удалить nan с до сортировки или что-нибудь еще.

+1

Я перепишу это для Python 3 и обрабатываю случаи, когда 'nan'' numpy.nan'. – max

7

Проблема заключается в том, что нет никакого правильного порядка, если список содержит NAN, так как последовательность a1, a2, a3, ..., ап сортируется, если a1 = a2 < < = a3 = < ... < = ап. Если какое-либо из этих значений является NAN, тогда сортируемое свойство ломается, так как для всех a a < = NAN и NAN < = a оба являются ложными.

0

Предполагая, что вы хотите сохранить пренебрежимо малые и порядок их как самые низкой «ценность», здесь есть обходной путь работает как с неуникальных нанами, уникального Numpy нан, численных и не являющихся числовых объектов :

def is_nan(x): 
    return (x is np.nan or x != x) 

list_ = [2, float('nan'), 'z', 1, 'a', np.nan, 4, float('nan')] 
sorted(list_, key = lambda x : float('-inf') if is_nan(x) else x) 
# [nan, nan, nan, 1, 2, 4, 'a', 'z']