Numpy: entradas exclusivas e NaN (Trac # 1514)

Criado em 19 out. 2012  ·  14Comentários  ·  Fonte: numpy/numpy

_Tíquete original http://projects.scipy.org/numpy/ticket/1514 em 2010-06-18 pelo usuário trac rspringuel, atribuído a desconhecido._

Quando exclusivo opera em uma matriz com várias entradas NaN, seu retorno inclui um NaN para cada entrada que era NaN na matriz original.

Exemplos:
a = random.randint (5, size = 100) .astype (float)

a [12] = nan #adicionar uma única entrada nan
único (a)
matriz ([0., 1., 2., 3., 4., NaN])
a [20] = nan #adicionar um segundo
único (a)
matriz ([0., 1., 2., 3., 4., NaN, NaN])
a [13] = nan
único (a) # e um terceiro
matriz ([0., 1., 2., 3., 4., NaN, NaN, NaN])

Isso provavelmente se deve ao fato de que x == y é avaliado como False se x e y são NaN. As necessidades únicas devem ter "or (isnan (x) e isnan (y))" adicionado à condicional que verifica a presença de um valor nos valores já identificados. Não sei se existiam vidas únicas no numpy e não consegui encontrar quando fui procurar, então não posso fazer a mudança sozinho (ou mesmo ter certeza de qual deve ser a sintaxe exata da condicional).

Além disso, a função a seguir pode ser usada para corrigir o comportamento.

def nanunique (x):
a = numpy.unique (x)
r = []
para eu em um:
se i em r ou (numpy.isnan (i) e numpy.any (numpy.isnan (r))):
continuar
outro:
r.append (i)
return numpy.array (r)

00 - Bug Other

Comentários muito úteis

Corri para o mesmo problema hoje. O núcleo da rotina np.unique é calcular uma máscara em uma matriz classificada não emaranhada em numpy / lib / arraysetops.py para descobrir quando os valores mudam nessa matriz classificada:

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

Isso poderia ser substituído por algo como o seguinte, que se parece muito com o comentário de jaimefrio de cerca de 5 anos atrás, mas evita a chamada 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]

Executando alguns experimentos de% de tempo, observei uma penalidade de no máximo <10% no tempo de execução se o array for grande e houver muito poucos NaN (digamos, 10 NaN em 1 milhão), e para esses grandes arrays ele realmente roda mais rápido se houver muitos de NaN.

Por outro lado, se os arrays forem pequenos (por exemplo, 10 entradas), haverá um impacto significativo no desempenho porque a verificação de flutuação e NaN é relativamente cara e o tempo de execução pode chegar a um múltiplo. Isso se aplica mesmo se não houver NaN, pois a verificação é lenta.

Se a matriz tiver NaNs, ela produzirá um resultado diferente, combinando os NaNs, que é o objetivo de tudo. Então, para esse caso, é realmente uma questão de obter um resultado desejado (todos os NaN combinados em um único grupo de valor) um pouco mais lento versus obter um resultado indesejado (cada NaN em seu próprio grupo de valor) um pouco mais rápido.

Por fim, observe que este patch não corrige a localização de valores únicos envolvendo objetos compostos contendo NaNs, como neste exemplo:

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

que ainda voltaria

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

Todos 14 comentários

_trac usuário rspringuel escreveu em 2010-06-18_

Atire, temos que usar os blocos de código acima. Isso realmente afeta apenas o código do patch-over, então irei postá-lo novamente:

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)

Fixo.

Ainda estou tendo esse problema com o mestre mais recente. Qual commit deveria ter corrigido isso? A menos que esteja faltando alguma coisa, sugiro reabrir esta edição.

Isso é fácil de corrigir para flutuadores, mas não vejo uma saída fácil para dtypes complexos ou estruturados. Faremos um RP rápido e podemos discutir as opções lá.

@jaimefrio Eu

    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]))

mas parece que todas as outras operações também apresentam problemas. Talvez precisemos de nan_equal, nan_not_equal ufuncs, ou talvez algo em nanfunções.

Sortsearching aux para si mesmo é um truque inteligente! Embora a busca de tipo _tudo_ seja um desperdício, idealmente gostaríamos de localizar a primeira entrada com um nan, talvez algo parecido com, depois de criar aux e flag como agora, fazendo :

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

ou algo semelhante depois de corrigir todos os erros por um que provavelmente introduzi lá.

Esta última abordagem funcionaria para tipos flutuantes e complexos, mas falharia para dtypes estruturados com campos de ponto flutuante. Mas ainda acho que o truque de searchsorting, embora funcionasse para todos os tipos, é um desperdício demais. Alguns horários:

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

Isso será um impacto de 40% no desempenho, o que pode ser bom para uma função nanunique , mas provavelmente não para o caso geral.

2019 chamado, o problema OP ainda é válido e o código é reproduzível.

@jaimefrio porque não podemos ter a opção de ser falso por padrão?

Quero dizer, esse comportamento é, na melhor das hipóteses, confuso, e o desempenho não é uma desculpa.

@ Demetrio92 embora eu aprecie sua tentativa de resolver esse problema, ironia / sarcasmo na internet pode ser interpretado de forma diferente por pessoas diferentes, por favor, seja gentil. Para alguns de nós, o desempenho é muito importante e não adicionamos códigos que tornem as coisas mais lentas.

PR # 5487 pode ser um lugar melhor para comentar ou fazer sugestões sobre como seguir em frente.

Editar: corrigir o número PR

Este problema parece estar aberto há 8 anos, mas eu só quero gritar com um +1 por fazer com que o comportamento padrão de numpy.unique seja correto em vez de rápido. Isso quebrou meu código e tenho certeza que outros sofreram / sofrerão com isso. Podemos ter um "fast = False" opcional e documentar o comportamento nan para fast e nans. Eu ficaria surpreso se np.unique costuma ser o gargalo de desempenho em aplicativos de tempo crítico.

Corri para o mesmo problema hoje. O núcleo da rotina np.unique é calcular uma máscara em uma matriz classificada não emaranhada em numpy / lib / arraysetops.py para descobrir quando os valores mudam nessa matriz classificada:

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

Isso poderia ser substituído por algo como o seguinte, que se parece muito com o comentário de jaimefrio de cerca de 5 anos atrás, mas evita a chamada 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]

Executando alguns experimentos de% de tempo, observei uma penalidade de no máximo <10% no tempo de execução se o array for grande e houver muito poucos NaN (digamos, 10 NaN em 1 milhão), e para esses grandes arrays ele realmente roda mais rápido se houver muitos de NaN.

Por outro lado, se os arrays forem pequenos (por exemplo, 10 entradas), haverá um impacto significativo no desempenho porque a verificação de flutuação e NaN é relativamente cara e o tempo de execução pode chegar a um múltiplo. Isso se aplica mesmo se não houver NaN, pois a verificação é lenta.

Se a matriz tiver NaNs, ela produzirá um resultado diferente, combinando os NaNs, que é o objetivo de tudo. Então, para esse caso, é realmente uma questão de obter um resultado desejado (todos os NaN combinados em um único grupo de valor) um pouco mais lento versus obter um resultado indesejado (cada NaN em seu próprio grupo de valor) um pouco mais rápido.

Por fim, observe que este patch não corrige a localização de valores únicos envolvendo objetos compostos contendo NaNs, como neste exemplo:

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

que ainda voltaria

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

"Se a matriz tiver NaNs, ela produzirá um resultado diferente, combinando os NaNs, que é o objetivo de tudo."

+1

Uma função que retorna uma lista contendo elementos repetidos, _por exemplo, uma lista com mais de 1 NaN, não deve ser chamada de "única". Se elementos repetidos no caso de NaN forem desejados, então deve ser apenas um caso especial que está desabilitado por padrão, por exemplo numpy.unique(..., keep_NaN=False) .

@ufmayer envie um PR!

+1
Eu também apoiaria o retorno de NaN apenas uma vez

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

MorBilly picture MorBilly  ·  4Comentários

dmvianna picture dmvianna  ·  4Comentários

marcocaccin picture marcocaccin  ·  4Comentários

kevinzhai80 picture kevinzhai80  ·  4Comentários

keithbriggs picture keithbriggs  ·  3Comentários