Numpy: Entradas únicas y NaN (Trac # 1514)

Creado en 19 oct. 2012  ·  14Comentarios  ·  Fuente: numpy/numpy

_Billete original http://projects.scipy.org/numpy/ticket/1514 el 18/06/2010 por el usuario de trac rspringuel, asignado a unknown._

Cuando unique opera en una matriz con múltiples entradas NaN, su retorno incluye un NaN para cada entrada que era NaN en la matriz original.

Ejemplos:
a = random.randint (5, tamaño = 100) .astype (flotante)

a [12] = nan #add una sola entrada nan
único (a)
matriz ([0., 1., 2., 3., 4., NaN])
a [20] = nan #add un segundo
único (a)
matriz ([0., 1., 2., 3., 4., NaN, NaN])
a [13] = nan
único (a) # y un tercero
matriz ([0., 1., 2., 3., 4., NaN, NaN, NaN])

Esto probablemente se deba al hecho de que x == y se evalúa como Falso si tanto x como y son NaN. Unique debe tener "o (isnan (x) e isnan (y))" agregado al condicional que verifica la presencia de un valor en los valores ya identificados. No sé que eran vidas únicas en numpy y no pude encontrarlo cuando fui a buscar, por lo que no puedo hacer el cambio yo mismo (o incluso estar seguro de cuál debería ser la sintaxis exacta del condicional).

Además, la siguiente función se puede utilizar para parchear el comportamiento.

def nanunique (x):
a = numpy.unique (x)
r = []
porque yo en un:
si yo en r o (numpy.isnan (i) y numpy.any (numpy.isnan (r))):
Seguir
más:
r.append (i)
return numpy.array (r)

00 - Bug Other

Comentario más útil

Hoy me encontré con el mismo problema. El núcleo de la rutina np.unique es calcular una máscara en una matriz ordenada y desenmarañada en numpy / lib / arraysetops.py para encontrar cuándo cambian los valores en esa matriz ordenada:

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

Esto podría ser reemplazado por algo como lo siguiente, que está más o menos en la línea del comentario de jaimefrio de hace unos 5 años, pero evita la llamada 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]

Al ejecutar algunos experimentos de% de tiempo, observé una penalización de tiempo de ejecución de como máximo <10% si la matriz es grande y hay muy pocos NaN (digamos 10 NaN de 1 millón), y para matrices tan grandes, en realidad se ejecuta más rápido si hay muchos de NaN.

Por otro lado, si las matrices son pequeñas (por ejemplo, 10 entradas), hay un impacto significativo en el rendimiento porque la verificación de flotación y NaN es relativamente costosa y el tiempo de ejecución puede aumentar a un múltiplo. Esto incluso se aplica incluso si no hay NaN, ya que la verificación es lenta.

Si la matriz tiene NaN, entonces produce un resultado diferente, combinando los NaN, que es el punto de todo. Entonces, para ese caso, es realmente una cuestión de obtener un resultado deseado (todos los NaN combinados en un solo grupo de valores) un poco más lento que obtener un resultado no deseado (cada NaN en su propio grupo de valores) un poco más rápido.

Finalmente, tenga en cuenta que este parche no solucionaría la búsqueda de valores únicos que involucren objetos compuestos que contengan NaN, como en este ejemplo:

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

que todavía volvería

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

Todos 14 comentarios

_trac usuario rspringuel escribió el 2010-06-18_

Dispara, para usar los bloques de código de arriba. Esto solo afecta realmente al código de parche, así que lo volveré a publicar:

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)

Fijo.

Sigo viendo este problema con el último maestro. ¿Qué compromiso debería haberlo solucionado? A menos que me falte algo, sugiero que vuelva a abrir este problema.

Esto es fácil de arreglar para los flotadores, pero no veo una salida fácil para los tipos d complejos o estructurados. Pondremos un PR rápido juntos y podremos discutir las opciones allí.

@jaimefrio lo tengo arreglado para uso único

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

pero parece que todas las demás operaciones también tienen problemas. Tal vez necesitemos nan_equal, nan_not_equal ufuncs, o tal vez algo en nanfunciones.

¡Sortsearching aux por sí mismo es un truco inteligente! Aunque ordenar la búsqueda de _todo_ es un poco desperdicio, lo ideal sería detectar la primera entrada con un nan, tal vez algo como, después de empaquetar aux y flag como ahora, haciendo :

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

o algo similar después de corregir todos los errores de uno que probablemente haya introducido allí.

Este último enfoque mío funcionaría para tipos flotantes y complejos, pero fallaría para tipos estructurados con campos de punto flotante. Pero sigo pensando que el truco de ordenación de búsquedas, aunque funcionaría para todos los tipos, es un desperdicio. Algunos horarios:

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

Eso será un 40% de rendimiento, lo que puede estar bien para una función nanunique , pero probablemente no para el caso general.

Llamado 2019, el problema OP sigue siendo válido y el código es reproducible.

@jaimefrio, ¿

Quiero decir, este comportamiento es confuso en el mejor de los casos y el desempeño no es una excusa.

@ Demetrio92, aunque aprecio su intento de hacer avanzar este problema, la ironía / sarcasmo en Internet puede ser interpretado de manera diferente por diferentes personas, por favor sea amable. Para algunos de nosotros, el rendimiento es muy importante y no agregamos casualmente código que ralentice las cosas.

El PR # 5487 puede ser un mejor lugar para comentar o hacer sugerencias sobre cómo avanzar.

Editar: arreglar el número de PR

Este problema parece estar abierto durante 8 años, pero solo quiero intervenir con un +1 para hacer que el comportamiento predeterminado de numpy.unique sea ​​correcto en lugar de rápido. Esto rompió mi código y estoy seguro de que otros lo han sufrido / sufrirán. Podemos tener un comportamiento opcional "fast = False" y documentar nan para fast y nans. Me sorprendería que np.unique sea a menudo el cuello de botella de rendimiento en aplicaciones de tiempo crítico.

Hoy me encontré con el mismo problema. El núcleo de la rutina np.unique es calcular una máscara en una matriz ordenada y desenmarañada en numpy / lib / arraysetops.py para encontrar cuándo cambian los valores en esa matriz ordenada:

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

Esto podría ser reemplazado por algo como lo siguiente, que está más o menos en la línea del comentario de jaimefrio de hace unos 5 años, pero evita la llamada 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]

Al ejecutar algunos experimentos de% de tiempo, observé una penalización de tiempo de ejecución de como máximo <10% si la matriz es grande y hay muy pocos NaN (digamos 10 NaN de 1 millón), y para matrices tan grandes, en realidad se ejecuta más rápido si hay muchos de NaN.

Por otro lado, si las matrices son pequeñas (por ejemplo, 10 entradas), hay un impacto significativo en el rendimiento porque la verificación de flotación y NaN es relativamente costosa y el tiempo de ejecución puede aumentar a un múltiplo. Esto incluso se aplica incluso si no hay NaN, ya que la verificación es lenta.

Si la matriz tiene NaN, entonces produce un resultado diferente, combinando los NaN, que es el punto de todo. Entonces, para ese caso, es realmente una cuestión de obtener un resultado deseado (todos los NaN combinados en un solo grupo de valores) un poco más lento que obtener un resultado no deseado (cada NaN en su propio grupo de valores) un poco más rápido.

Finalmente, tenga en cuenta que este parche no solucionaría la búsqueda de valores únicos que involucren objetos compuestos que contengan NaN, como en este ejemplo:

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

que todavía volvería

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

"Si la matriz tiene NaN, entonces produce un resultado diferente, combinando los NaN, que es el objetivo de todo".

+1

Una función que devuelve una lista que contiene elementos repetidos, _ por ejemplo, una lista con más de 1 NaN, no debe llamarse "única". Si se desean elementos repetidos en el caso de NaN, entonces solo debería ser un caso especial que esté deshabilitado por defecto, por ejemplo numpy.unique(..., keep_NaN=False) .

@ufmayer envía un PR!

+1
También apoyaría la devolución de NaN solo una vez

¿Fue útil esta página
0 / 5 - 0 calificaciones