matrices - recorrer una matriz en python
Cortando una matriz NumPy 2d, o ¿cómo extraigo una submatriz mxm de una matriz nxn(n> m)? (6)
Quiero cortar una matriz NumPy nxn. Quiero extraer una selección arbitraria de m filas y columnas de esa matriz (es decir, sin ningún patrón en los números de filas / columnas), lo que la convierte en una nueva matriz mxm. Para este ejemplo, digamos que la matriz es 4x4 y quiero extraer una matriz 2x2 de ella.
Aquí está nuestra matriz:
from numpy import *
x = range(16)
x = reshape(x,(4,4))
print x
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]
[12 13 14 15]]
La línea y las columnas para eliminar son las mismas. El caso más fácil es cuando quiero extraer una submatriz de 2x2 que está al principio o al final, es decir:
In [33]: x[0:2,0:2]
Out[33]:
array([[0, 1],
[4, 5]])
In [34]: x[2:,2:]
Out[34]:
array([[10, 11],
[14, 15]])
Pero, ¿qué sucede si necesito eliminar otra mezcla de filas / columnas? ¿Qué pasa si necesito eliminar la primera y tercera líneas / filas, extrayendo así la submatriz [[5,7],[13,15]]
? Puede haber cualquier composición de filas / líneas. Leí en alguna parte que solo necesito indexar mi matriz usando matrices / listas de índices para filas y columnas, pero eso no parece funcionar:
In [35]: x[[1,3],[1,3]]
Out[35]: array([ 5, 15])
Encontré una forma, que es:
In [61]: x[[1,3]][:,[1,3]]
Out[61]:
array([[ 5, 7],
[13, 15]])
El primer problema con esto es que es difícil de leer, aunque puedo vivir con eso. Si alguien tiene una mejor solución, ciertamente me gustaría escucharla.
Otra cosa es que leí en un foro que las matrices de indexación con matrices obligan a NumPy a hacer una copia de la matriz deseada, por lo que cuando se trata con matrices grandes, esto podría convertirse en un problema. ¿Por qué es así? ¿Cómo funciona este mecanismo?
Como mencionó Sven, x[[[0],[2]],[1,3]]
devolverá las filas 0 y 2 que coinciden con las columnas 1 y 3, mientras que x[[0,2],[1,3]]
devolverá los valores x [0,1] yx [2,3] en una matriz.
Hay una función útil para hacer el primer ejemplo que di, numpy.ix_
. Puedes hacer lo mismo que mi primer ejemplo con x[numpy.ix_([0,2],[1,3])]
. Esto puede evitar que tenga que ingresar todos los corchetes adicionales.
Con numpy, puede pasar una porción para cada componente del índice, entonces, su ejemplo de x[0:2,0:2]
anterior funciona.
Si solo quiere omitir columnas o filas, puede pasar sectores con tres componentes (es decir, inicio, finalización, paso).
Nuevamente, para su ejemplo anterior:
>>> x[1:4:2, 1:4:2]
array([[ 5, 7],
[13, 15]])
Que es básicamente: cortar en la primera dimensión, con inicio en el índice 1, detener cuando el índice es igual o mayor que 4 y agregar 2 al índice en cada pase. Lo mismo para la segunda dimensión. De nuevo: esto solo funciona para pasos constantes.
La sintaxis que tiene que hacer algo completamente diferente internamente - lo que x[[1,3]][:,[1,3]]
realmente hace es crear una nueva matriz que incluye solo las filas 1 y 3 de la matriz original (hecho con el x[[1,3]]
parte, y luego vuelva a cortar eso - creando una tercera matriz - incluyendo solo las columnas 1 y 3 de la matriz anterior.
No creo que x [[1,3]] [:, [1,3]] sea difícil de leer. Si quiere ser más claro sobre su intención, puede hacer:
a[[1,3],:][:,[1,3]]
No soy un experto en cortar, pero normalmente, si tratas de cortar en una matriz y los valores son continuos, obtienes una vista donde se cambia el valor de la zancada.
Por ejemplo, en sus entradas 33 y 34, aunque obtiene una matriz de 2x2, la zancada es 4. Por lo tanto, cuando indexa la siguiente fila, el puntero se mueve a la posición correcta en la memoria.
Claramente, este mecanismo no se lleva bien en el caso de una matriz de índices. Por lo tanto, Numpy tendrá que hacer la copia. Después de todo, muchas otras funciones matriciales de matriz se basan en el tamaño, la zancada y la asignación de memoria continua.
Para responder a esta pregunta, tenemos que ver cómo funciona indexar una matriz multidimensional en Numpy. Primero digamos que tienes la matriz x
de tu pregunta. El búfer asignado a x
contendrá 16 enteros ascendentes de 0 a 15. Si accede a un elemento, digamos x[i,j]
, NumPy tiene que averiguar la ubicación de la memoria de este elemento en relación con el comienzo del búfer. Esto se hace calculando en efecto i*x.shape[1]+j
(y multiplicando con el tamaño de un int para obtener una compensación de memoria real).
Si extrae un subarreglo mediante división básica como y = x[0:2,0:2]
, el objeto resultante compartirá el almacenamiento intermedio subyacente con x
. Pero, ¿qué sucede si accede a y[i,j]
? NumPy no puede usar i*y.shape[1]+j
para calcular el desplazamiento en la matriz, porque los datos que pertenecen a y
no son consecutivos en la memoria.
NumPy resuelve este problema mediante la introducción de avances . Al calcular la compensación de memoria para acceder a x[i,j]
, lo que realmente se calcula es i*x.strides[0]+j*x.strides[1]
(y esto ya incluye el factor para el tamaño de una int) :
x.strides
(16, 4)
Cuando y
se extrae como arriba, NumPy no crea un nuevo búfer, pero sí crea un nuevo objeto de matriz que hace referencia al mismo búfer (de lo contrario, y
sería igual a x
). El nuevo objeto de matriz tendrá una forma diferente, entonces x
tal vez un desplazamiento inicial diferente en el búfer, pero compartirá los pasos con x
(en este caso al menos):
y.shape
(2,2)
y.strides
(16, 4)
De esta forma, calcular la compensación de memoria para y[i,j]
arrojará el resultado correcto.
Pero, ¿qué debería hacer NumPy por algo como z=x[[1,3]]
? El mecanismo de pasos no permitirá la indexación correcta si el buffer original se usa para z
. NumPy teóricamente podría agregar un mecanismo más sofisticado que los avances, pero esto haría que el acceso a los elementos sea relativamente costoso, desafiando de algún modo la idea de una matriz. Además, una vista ya no sería un objeto realmente liviano.
Esto está cubierto en profundidad en la documentación de NumPy sobre indexación .
Ah, y casi se olvidó de su pregunta real: Aquí es cómo hacer que la indexación con múltiples listas funcione como se esperaba:
x[[[1],[3]],[1,3]]
Esto se debe a que las matrices de índice se broadcasted a una forma común. Por supuesto, para este ejemplo en particular, también puede conformarse con rebanar básico:
x[1::2, 1::2]
Si desea omitir cada dos filas y todas las demás columnas, puede hacerlo con un corte básico:
In [49]: x=np.arange(16).reshape((4,4))
In [50]: x[1:4:2,1:4:2]
Out[50]:
array([[ 5, 7],
[13, 15]])
Esto devuelve una vista, no una copia de su matriz.
In [51]: y=x[1:4:2,1:4:2]
In [52]: y[0,0]=100
In [53]: x # <---- Notice x[1,1] has changed
Out[53]:
array([[ 0, 1, 2, 3],
[ 4, 100, 6, 7],
[ 8, 9, 10, 11],
[ 12, 13, 14, 15]])
mientras que z=x[(1,3),:][:,(1,3)]
utiliza la indexación avanzada y, por lo tanto, devuelve una copia:
In [58]: x=np.arange(16).reshape((4,4))
In [59]: z=x[(1,3),:][:,(1,3)]
In [60]: z
Out[60]:
array([[ 5, 7],
[13, 15]])
In [61]: z[0,0]=0
Tenga en cuenta que x
se modifica:
In [62]: x
Out[62]:
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11],
[12, 13, 14, 15]])
Si desea seleccionar filas y columnas arbitrarias, entonces no puede usar el corte básico. Tendrá que usar la indexación avanzada, usando algo como x[rows,:][:,columns]
, donde las rows
y las columns
son secuencias. Esto, por supuesto, le dará una copia, no una vista, de su matriz original. Esto es lo que uno debería esperar, ya que una matriz numpy usa memoria contigua (con pasos constantes), y no habría forma de generar una vista con filas y columnas arbitrarias (ya que eso requeriría zancadas no constantes).
Tengo una pregunta similar aquí: Escribir en sub-ndarray de un ndarray de la manera más pythoniana. Python 2 .
Siguiendo la solución de la publicación anterior para su caso, la solución se ve así:
columns_to_keep = [1,3]
rows_to_keep = [1,3]
Un uso de ix_:
x[np.ix_(rows_to_keep, columns_to_keep)]
Cual es:
array([[ 5, 7],
[13, 15]])