Python: a função de classificação quebra na presença de NAN
Pergunta
sorted([2, float('nan'), 1])
retorna [2, nan, 1]
(Pelo menos na implementação do Activestate Python 3.1.)
Eu entendo nan
é um objeto estranho, então eu não ficaria surpreso se aparecesse em lugares aleatórios no resultado de classificação. Mas também atrapalha o tipo para os números não-NAN no contêiner, o que é realmente inesperado.
Eu perguntei a Pergunta relacionada cerca de max
, e com base nisso eu entendo porque sort
Funciona assim. Mas isso deve ser considerado um bug?
A documentação apenas diz "Retorne uma nova lista classificada [...] sem especificar detalhes.
EDIT: Agora concordo que isso não viola o padrão IEEE. No entanto, é um bug de qualquer ponto de vista do senso comum, eu acho. Até a Microsoft, que não é conhecida por admitir seus erros com frequência, reconheceu este como um bug e o corrigiu na versão mais recente: http://connect.microsoft.com/visualstudio/feedback/details/363379/bug-ass-list-double-sort-list-list-which-contains-double-nan.
Enfim, acabei seguindo a resposta de @khachik:
sorted(list_, key = lambda x : float('-inf') if math.isnan(x) else x)
Eu suspeito que isso resulta em um desempenho de desempenho em comparação com o idioma que faz isso por padrão, mas pelo menos funciona (exceto os bugs que eu introduzi).
Solução
As respostas anteriores são úteis, mas talvez não sejam claras sobre a raiz do problema.
Em qualquer idioma, a classificação aplica uma determinada ordem, definida por uma função de comparação ou de alguma outra maneira, sobre o domínio dos valores de entrada. Por exemplo, menos do que, também conhecido operator <,
pode ser usado por toda parte se e somente se definir uma ordem adequada sobre os valores de entrada.
Mas isso não é especificamente verdadeiro para valores de ponto flutuante e menos do que: "A NAN não é ordenada: não é igual a, maior que ou menor que qualquer coisa, inclusive a si mesma". (Prosa clara do manual GNU c, mas se aplica a todos os modernos IEEE754
Sediada ponto flutuante)
Portanto, as soluções possíveis são:
- Remova os Nans primeiro, tornando o domínio de entrada bem definido via <(ou a outra função de classificação sendo usada)
- Defina uma função de comparação personalizada (também conhecida como predicada) que define uma ordem para a NAN, como menos de qualquer número, ou maior que qualquer número.
Qualquer abordagem pode ser usada, em qualquer idioma.
Praticamente, considerando o python, eu preferiria remover os Nans se você não se importar com o desempenho mais rápido ou se a remoção de Nans é um comportamento desejado no contexto.
Caso contrário, você poderá usar uma função predicada adequada via "cmp" nas versões python mais antigas, ou por meio disso e functools.cmp_to_key()
. Este último é um pouco mais estranho, naturalmente, do que remover os Nans primeiro. E cuidar será necessário para evitar pior desempenho, ao definir essa função de predicado.
Outras dicas
O problema é que não há ordem correta se a lista contiver uma nan, pois uma sequência A1, A2, A3, ..., e é classificada se A1 <= a2 <= a3 <= ... <= an. Se algum desses valores A for uma NAN, a propriedade classificada quebra, pois para todos a, a <= nan e nan <= a são falsos.
Não tenho certeza sobre o bug, mas a solução alternativa pode ser a seguinte:
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)))
o que resulta em:
('nan', 1, 2)
Ou remova nan
s antes de classificar ou qualquer outra coisa.
IEEE754 é o padrão que define operações de ponto flutuante nesta instância. Este padrão define a operação de comparação dos operandos, pelo menos um dos quais é uma nan, para ser um erro. Portanto, este não é um bug. Você precisa lidar com os NANs antes de operar em sua matriz.
Supondo Nan não único, Numpy Numpy exclusivo, numérico e não numérico objetos:
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']