Python:在存在nan的情况下,sort函数会中断

时间:2010-11-21 20:05:55

标签: python math sorting nan

sorted([2, float('nan'), 1])返回[2, nan, 1]

(至少在Activestate Python 3.1实现上。)

我理解nan是一个奇怪的对象,所以如果它出现在排序结果中的随机位置,我不会感到惊讶。但它也混淆了容器中非纳米数的排序,这真是出乎意料。

我向related question询问了max,基于此我理解为什么sort会像这样工作。但这应该被视为一个错误吗?

文档只是说“返回一个新的排序列表[...]”而没有指定任何细节。

编辑: 我现在同意这不违反IEEE标准。但是,我认为这是任何常识观点的错误。即使微软经常承认自己的错误,也已经认识到这个错误,并将其修复为最新版本: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)

我怀疑它与默认情况下的语言相比会导致性能下降,但至少它会起作用(除非我介绍的任何错误)。

7 个答案:

答案 0 :(得分:12)

以前的答案很有用,但可能不清楚问题的根源。

在任何语言中,sort都应用由比较函数或以其他方式定义的给定排序,而不是输入值的域。例如,当且仅当小于定义输入值的合适排序时,可以使用小于a.k.a。operator <,

但对于浮点值而言,这尤其不适用于: “NaN是无序的:它不等于,大于或小于任何东西,包括它自己。” (来自GNU C手册的清晰散文,但适用于所有现代IEEE754基于浮动点

所以可能的解决方案是:

  
      
  1. 首先删除NaN,使输入域通过&lt;   (或正在使用的其他排序功能)
  2.   
  3. 定义一个自定义比较函数(a.k.a。谓词)   定义NaN的排序,例如小于任何数字或更大   比任何数字都要多。
  4.   

任何一种语言都可以使用。

实际上,考虑到python,如果你不关心最快的性能,或者如果在上下文中删除NaN是一种理想的行为,我宁愿删除NaN。

否则,您可以在较旧的python版本中通过“cmp”使用合适的谓词函数,或通过此functools.cmp_to_key()使用。后者比首先去除NaN更加尴尬。在定义此谓词函数时,需要注意避免更差性能。

答案 1 :(得分:7)

问题是如果列表包含NAN,则没有正确的顺序,因为如果a1&lt; = a2&lt; = a3&lt; = ...,则序列a1,a2,a3,...,a被排序。 &lt; = an。如果这些值中的任何一个是NAN,则排序的属性会中断,因为对于所有a,&lt; = NAN和NAN&lt; = a都是假的。

答案 2 :(得分: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

答案 3 :(得分:3)

IEEE754是在此实例中定义浮点运算的标准。该标准定义了操作数的比较操作,其中至少有一个是NaN,是一个错误。因此,这不是一个错误。在对阵列进行操作之前,您需要处理NaN。

答案 4 :(得分:0)

假设你想保留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']

答案 5 :(得分:0)

不管标准是什么,在许多情况下,用户定义的float和NA值顺序都是有用的。例如,我正在对股票收益进行排序,并希望从最后到NA从高到低(因为这些无关紧要)。有4种可能的组合

  1. 升序浮点值,最后NA个值
  2. 递增浮点值,首先NA个值
  3. 降序浮点值,最后NA个值
  4. 降序浮点值,先NA个值

此功能涵盖了所有情况,方法是将NA的值有条件地替换为+/- inf

import math 

def sort_with_na(x, reverse=False, na_last=True):
    """Intelligently sort iterable with NA values

    For reliable behavior with NA values, we should change the NAs to +/- inf
    to guarantee their order rather than relying on the built-in
    ``sorted(reverse=True)`` which will have no effect. To use the ``reverse``
    parameter or other kwargs, use functools.partial in your lambda i.e.

        sorted(iterable, key=partial(sort_with_na, reverse=True, na_last=False))

    :param x: Element to be sorted
    :param bool na_last: Whether NA values should come last or first
    :param bool reverse: Return ascending if ``False`` else descending
    :return bool:
    """
    if not math.isnan(x):
        return -x if reverse else x
    else:
        return float('inf') if na_last else float('-inf')

分别测试4种组合

from functools import partial

a = [2, float('nan'), 1]
sorted(a, key=sort_with_na)                                         # Default
sorted(a, key=partial(sort_with_na, reverse=False, na_last=True))   # Ascend, NA last
sorted(a, key=partial(sort_with_na, reverse=False, na_last=False))  # Ascend, NA first
sorted(a, key=partial(sort_with_na, reverse=True, na_last=True))    # Descend, NA last
sorted(a, key=partial(sort_with_na, reverse=True, na_last=False))   # Descend, NA first

答案 6 :(得分:0)

弹性排序涉及比较 2 个项目并返回:less、equal、greater。

如果 cmp(a,b) 是“更大”,那么 cmp(b,a) 必须是“更少”。

如果 cmp(a,b) 为“零”,则 cmp(b,a) 必须为“零”。

迄今为止的答案中缺少的是比较 2 个 float 的情况,这些 if isnan(a) if isnan(b) return 0 (or maybe compare payloads/bit patterns) return 1 if isnan(b) return 1 if a > b return 1 if a < b return -1 return 0 都是 NAN 并保留了上述属性。 2 个 NAN 的比较应该相等,或者可能基于对其有效载荷的一些一致解释。

Alternate compare algorithm 把所有的 NAN > +inf

(ns test
(:require [io.pedestal.http :as http]
[io.pedesteal.http.route :as route]))