gotchas donde Numpy se diferencia de python recta?

Gente

¿Existe una colección de errores en la que Numpy se diferencie de python, puntos que hayan desconcertado y hayan costado tiempo?

“¡El horror de ese momento que nunca olvidaré!”
“Lo harás, sin embargo”, dijo la reina, “si no haces un memorándum de eso”.

Por ejemplo, los NaN siempre son problemas, en cualquier lugar. Si puedes explicar esto sin ejecutarlo, date un punto:

from numpy import array, NaN, isnan pynan = float("nan") print pynan is pynan, pynan is NaN, NaN is NaN a = (0, pynan) print a, a[1] is pynan, any([aa is pynan for aa in a]) a = array(( 0, NaN )) print a, a[1] is NaN, isnan( a[1] ) 

(No estoy dando muchas vueltas, hay mucho buen trabajo allí, solo creo que sería útil una FAQ o Wiki de errores).

Edit: esperaba coleccionar media docena de errores (sorpresas para las personas que aprenden Numpy).
Entonces, si hay errores comunes o, mejor, explicaciones comunes, podríamos hablar sobre agregarlos a una comunidad Wiki (¿dónde?) No parece que tengamos suficiente hasta ahora.

Lo que más me gustó fue que casi todos los operadores estándar están sobrecargados para distribuir en todo el arreglo.

Definir una lista y una matriz.

 >>> l = range(10) >>> l [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> import numpy >>> a = numpy.array(l) >>> a array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) 

La multiplicación duplica la lista de python, pero se distribuye sobre la matriz numpy

 >>> l * 2 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> a * 2 array([ 0, 2, 4, 6, 8, 10, 12, 14, 16, 18]) 

La adición y la división no están definidas en las listas de python

 >>> l + 2 Traceback (most recent call last): File "", line 1, in  TypeError: can only concatenate list (not "int") to list >>> a + 2 array([ 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]) >>> l / 2.0 Traceback (most recent call last): File "", line 1, in  TypeError: unsupported operand type(s) for /: 'list' and 'float' >>> a / 2.0 array([ 0. , 0.5, 1. , 1.5, 2. , 2.5, 3. , 3.5, 4. , 4.5]) 

Numpy sobrecargas para tratar listas como matrices a veces

 >>> a + a array([ 0, 2, 4, 6, 8, 10, 12, 14, 16, 18]) >>> a + l array([ 0, 2, 4, 6, 8, 10, 12, 14, 16, 18]) 

Debido a que __eq__ no devuelve un valor bool, el uso de matrices numpy en cualquier tipo de contenedores evita que se realicen pruebas de igualdad sin una solución específica del contenedor.

Ejemplo:

 >>> import numpy >>> a = numpy.array(range(3)) >>> b = numpy.array(range(3)) >>> a == b array([ True, True, True], dtype=bool) >>> x = (a, 'banana') >>> y = (b, 'banana') >>> x == y Traceback (most recent call last): File "", line 1, in  ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all() 

Este es un problema horrible. Por ejemplo, no puede escribir TestCase.assertEqual() para contenedores que usan TestCase.assertEqual() y, en cambio, debe escribir funciones de comparación personalizadas. Supongamos que escribimos una función de work-around special_eq_for_numpy_and_tuples . Ahora podemos hacer esto en una prueba de unidad:

 x = (array1, 'deserialized') y = (array2, 'deserialized') self.failUnless( special_eq_for_numpy_and_tuples(x, y) ) 

Ahora debemos hacer esto para cada tipo de contenedor que podamos usar para almacenar matrices numpy. Además, __eq__ podría devolver un bool en lugar de una matriz de bools:

 >>> a = numpy.array(range(3)) >>> b = numpy.array(range(5)) >>> a == b False 

Ahora, cada una de nuestras funciones de comparación de igualdad de contenedores específicos también debe manejar ese caso especial.

Tal vez podamos parchear esta verruga con una subclase?

 >>> class SaneEqualityArray (numpy.ndarray): ... def __eq__(self, other): ... return isinstance(other, SaneEqualityArray) and self.shape == other.shape and (numpy.ndarray.__eq__(self, other)).all() ... >>> a = SaneEqualityArray( (2, 3) ) >>> a.fill(7) >>> b = SaneEqualityArray( (2, 3) ) >>> b.fill(7) >>> a == b True >>> x = (a, 'banana') >>> y = (b, 'banana') >>> x == y True >>> c = SaneEqualityArray( (7, 7) ) >>> c.fill(7) >>> a == c False 

Eso parece hacer lo correcto. La clase también debe exportar explícitamente la comparación elemental, ya que a menudo es útil.

Creo que este es gracioso:

 >>> import numpy as n >>> a = n.array([[1,2],[3,4]]) >>> a[1], a[0] = a[0], a[1] >>> a array([[1, 2], [1, 2]]) 

Para las listas de Python, por otro lado, esto funciona como se pretende:

 >>> b = [[1,2],[3,4]] >>> b[1], b[0] = b[0], b[1] >>> b [[3, 4], [1, 2]] 

Nota graciosa: el mismo numpy tenía un error en la función de shuffle , porque usaba esa notación 🙂 (ver aquí ).

La razón es que en el primer caso estamos tratando con vistas de la matriz, por lo que los valores se sobrescriben en el lugar.

NaN no es un Singleton como None , por lo que realmente no se puede usar el cheque. Lo que lo hace un poco complicado es que NaN == NaN es False como lo requiere IEEE-754. Es por eso que necesita usar la función numpy.isnan() para verificar si un flotador no es un número. O la biblioteca estándar math.isnan() si está usando Python 2.6+.

Cortar crea vistas, no copias.

 >>> l = [1, 2, 3, 4] >>> s = l[2:3] >>> s[0] = 5 >>> l [1, 2, 3, 4] >>> a = array([1, 2, 3, 4]) >>> s = a[2:3] >>> s[0] = 5 >>> a array([1, 2, 5, 4]) 
 In [1]: bool([]) Out[1]: False In [2]: bool(array([])) Out[2]: False In [3]: bool([0]) Out[3]: True In [4]: bool(array([0])) Out[4]: False 

Así que no pruebe el vacío de una matriz comprobando su valor de verdad. Utilice size(array()) .

Y no uses len(array()) , tampoco:

 In [1]: size(array([])) Out[1]: 0 In [2]: len(array([])) Out[2]: 0 In [3]: size(array([0])) Out[3]: 1 In [4]: len(array([0])) Out[4]: 1 In [5]: size(array(0)) Out[5]: 1 In [6]: len(array(0)) --------------------------------------------------------------------------- TypeError Traceback (most recent call last)  in () ----> 1 len(array(0)) TypeError: len() of unsized object 

El valor de verdad de una matriz Numpy difiere del de un tipo de secuencia de python, donde cualquier secuencia no vacía es verdadera.

 >>> import numpy as np >>> l = [0,1,2,3] >>> a = np.arange(4) >>> if l: print "Im true" ... Im true >>> if a: print "Im true" ... Traceback (most recent call last): File "", line 1, in  ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all() >>> 

Los tipos numéricos son verdaderos cuando no son cero y como una colección de números, la matriz numpy hereda esta definición. Pero con una colección de números, la verdad podría significar razonablemente que “todos los elementos son distintos de cero” o “al menos un elemento es distinto de cero”. Numpy se niega a adivinar qué definición significa y plantea la excepción anterior. El uso de los .any() y .all() permite especificar el significado de verdadero.

 >>> if a.any(): print "Im true" ... Im true >>> if a.all(): print "Im true" ... >>> 

(Relacionado, pero un número NumPy vs. SciPy gotcha, en lugar de NumPy vs Python)


Cortar más allá del tamaño real de una matriz funciona de manera diferente:

 >>> import numpy, scipy.sparse >>> m = numpy.random.rand(2, 5) # create a 2x5 dense matrix >>> print m[:3, :] # works like list slicing in Python: clips to real size [[ 0.12245393 0.20642799 0.98128601 0.06102106 0.74091038] [ 0.0527411 0.9131837 0.6475907 0.27900378 0.22396443]] >>> s = scipy.sparse.lil_matrix(m) # same for csr_matrix and other sparse formats >>> print s[:3, :] # doesn't clip! IndexError: row index out of bounds 

Por lo tanto, al scipy.sparse matrices scipy.sparse , debe asegurarse manualmente de que sus límites de scipy.sparse estén dentro del rango. Esto difiere de cómo funcionan tanto NumPy como Python simple.

 print pynan is pynan, pynan is NaN, NaN is NaN 

Esto prueba la identidad, es decir, si es el mismo objeto. El resultado debería ser obviamente verdadero, falso, verdadero, porque cuando haces flotar (lo que sea) estás creando un nuevo objeto flotante.

 a = (0, pynan) print a, a[1] is pynan, any([aa is pynan for aa in a]) 

No sé qué es lo que encuentras sorprendente con esto.

 a = array(( 0, NaN )) print a, a[1] is NaN, isnan( a[1] ) 

Esto tuve que correr. 🙂 Cuando se pega NaN en una matriz, se convierte en un objeto numpy.float64, por lo que un [1] es NaN falla.

Todo esto me parece bastante sorprendente. Pero entonces realmente no sé mucho acerca de NumPy. 🙂

Nadie parece haber mencionado esto hasta ahora:

 >>> all(False for i in range(3)) False >>> from numpy import all >>> all(False for i in range(3)) True >>> any(False for i in range(3)) False >>> from numpy import any >>> any(False for i in range(3)) True 

Numpy es any y no juega bien con los generadores, y no genera ningún error que le advierta que no lo hacen.

de Neil Martinsen-Burrell en una gran discusión el 7 de septiembre –

El tipo ndarray disponible en Numpy no es conceptualmente una extensión de los iterables de Python. Si desea ayudar a otros usuarios de Numpy con este problema, puede editar la documentación en el editor de documentación en línea en numpy-docs

Encontré el hecho de que multiplicar las listas de elementos simplemente crea una vista de los elementos que me sorprendieron.

 >>> a=[0]*5 >>>a [0,0,0,0,0] >>>a[2] = 1 >>>a [0,0,1,0,0] >>>b = [np.ones(3)]*5 >>>b [array([ 1., 1., 1.]), array([ 1., 1., 1.]), array([ 1., 1., 1.]), array([ 1., 1., 1.]), array([ 1., 1., 1.])] >>>b[2][1] = 2 >>>b [array([ 1., 2., 1.]), array([ 1., 2., 1.]), array([ 1., 2., 1.]), array([ 1., 2., 1.]), array([ 1., 2., 1.])] 

Entonces, si creas una lista de elementos como este y pretendes realizar diferentes operaciones en ellos, estás sorprendido …

Una solución sencilla es crear de forma iterativa cada uno de los arreglos (usando un ‘bucle for’ o una lista de comprensión) o usar un arreglo dimensional superior (donde, por ejemplo, cada uno de estos arreglos 1D es una fila en su arreglo 2D, que generalmente es más rápido).

No es un gran problema: con el corte booleano, a veces desearía poder hacerlo

  x[ 3 <= y < 7 ] 

Como la doble comparación de Python. En cambio, tengo que escribir

  x[ np.logical_and(3<=y, y<7) ] 

(A menos que sepas algo mejor?)

Además, np.logical_and y np.logical_or solo toman dos argumentos cada uno, me gustaría que tomen un número variable, o una lista, para poder incluir más de dos cláusulas lógicas.

(Numpy 1.3, quizás todo esto haya cambiado en versiones posteriores).

Una sorpresa con la asignación *= en combinación con numpy.array :

 >>> from numpy import array >>> a = array([1, 2, 3]) >>> a *= 1.1 >>> print(a) [1 2 3] # not quite what we expect or would like to see >>> print(a.dtype) int64 # and this is why >>> a = 1.1 * a # here, a new array is created >>> print(a, a.dtype) [ 1.1 2.2 3.3] float64 # with the expected outcome 

Sorprendente, molesta, pero comprensible. El operador *= no cambiará el tipo de datos de la array , por lo que la multiplicación de una array int por un float fallará en el significado convencional de esta multiplicación. La versión de Python a = 1; a *= 1.1 a = 1; a *= 1.1 en la otra parte funciona como se espera.

Una matriz de 0 días de Ninguno se parece a Ninguna pero no es lo mismo:

 In [1]: print None None In [2]: import numpy In [3]: print numpy.array(None) None In [4]: numpy.array(None) is None Out[4]: False In [5]: numpy.array(None) == None Out[5]: False In [6]: print repr(numpy.array(None)) array(None, dtype=object)