Python: функция сортировки прерывается в присутствии nan - PullRequest
28 голосов
/ 21 ноября 2010

sorted([2, float('nan'), 1]) возвращает [2, nan, 1]

(По крайней мере, в реализации Activestate Python 3.1.)

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

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

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

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

В любом случае, я в конечном итоге последовал ответ @ khachik:

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

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

Ответы [ 5 ]

12 голосов
/ 23 августа 2011

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

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

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

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

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

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

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

В противном случае вы можете использовать подходящую функцию предиката через "cmp" в старых версиях Python или через this и functools.cmp_to_key().Последнее немного более неловко, естественно, чем сначала удалить NaN.При определении этой функции предиката необходимо соблюдать осторожность, чтобы избежать повышения производительности на * .

7 голосов
/ 22 ноября 2010

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

5 голосов
/ 21 ноября 2010

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

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 s перед сортировкой или чем-либо еще.

3 голосов
/ 21 ноября 2010

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

0 голосов
/ 24 мая 2017

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

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']
...