Rebanada de una matriz NumPy 2d, o ¿cómo extraigo una submatriz de mxm de una matriz nxn (n> m)?

174

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 el número de filas / columnas), convirtiéndola en una nueva matriz mxm. Para este ejemplo, digamos que la matriz es 4x4 y quiero extraer una matriz de 2x2.

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é sucede si necesito eliminar la primera y tercera línea / fila, 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 tanto para filas como para 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 apenas es legible, 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 indexar matrices con matrices obliga a NumPy a hacer una copia de la matriz deseada, por lo tanto, cuando se trata con matrices grandes, esto podría convertirse en un problema. ¿Por qué es así / cómo funciona este mecanismo?

levesco
fuente

Respuestas:

62

Como mencionó Sven, x[[[0],[2]],[1,3]]devolverá las filas 0 y 2 que coinciden con las columnas 1 y 3, mientras x[[0,2],[1,3]]que devolverá los valores x [0,1] yx [2,3] en una matriz.

Hay una función muy ú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 esos corchetes adicionales.

Justin Peel
fuente
111

Para responder a esta pregunta, tenemos que ver cómo funciona la indexación de una matriz multidimensional en Numpy. Digamos primero que tiene la matriz xde su pregunta. El búfer asignado a xcontendrá 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 realiza calculando el efecto i*x.shape[1]+j(y multiplicándolo con el tamaño de un int para obtener un desplazamiento de memoria real).

Si extrae una submatriz mediante un corte básico como y = x[0:2,0:2], el objeto resultante compartirá el búfer subyacente con x. ¿Pero qué pasa si accedes y[i,j]? NumPy no puede usar i*y.shape[1]+jpara calcular el desplazamiento en la matriz, porque los datos que pertenecen yno son consecutivos en la memoria.

NumPy resuelve este problema introduciendo avances . Al calcular el desplazamiento de memoria para acceder 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 un int):

x.strides
(16, 4)

Cuando yse extrae como la de arriba, NumPy no crea un nuevo búfer, pero hace crear un nuevo objeto de matriz en incluir el mismo tampón (de lo contrario ysólo sería igual a x.) El nuevo objeto de matriz tendrá una forma diferente a continuación, xy tal vez una partida diferente compensado en el búfer, pero compartirá los pasos con x(al menos en este caso):

y.shape
(2,2)
y.strides
(16, 4)

De esta manera, calcular el desplazamiento de memoria para y[i,j]producirá el resultado correcto.

Pero, ¿qué debería hacer NumPy por algo así z=x[[1,3]]? El mecanismo de zancadas no permitirá una indexación correcta si se utiliza el búfer original z. Teóricamente, NumPy podría agregar un mecanismo más sofisticado que los avances, pero esto haría que el acceso a elementos sea relativamente costoso, desafiando de alguna manera la idea de una matriz. Además, una vista ya no sería un objeto realmente liviano.

Esto se cubre en profundidad en la documentación de NumPy sobre indexación .

Ah, y casi me olvido de su pregunta real: a continuación, le mostramos cómo hacer que la indexación con varias listas funcione como se esperaba:

x[[[1],[3]],[1,3]]

Esto se debe a que las matrices de índice se transmiten a una forma común. Por supuesto, para este ejemplo en particular, también puede conformarse con el corte básico:

x[1::2, 1::2]
Sven Marnach
fuente
Debería ser posible subclasificar las matrices para poder tener un objeto de "vista de espacio" que reasignaría los índices a la matriz original. Eso posiblemente podría satisfacer las necesidades del OP
jsbueno
@jsbueno: eso funcionará para el código Python pero no para las rutinas C / Fortran que Scipy / Numpy envuelve. Esas rutinas envueltas son donde reside el poder de Numpy.
Dat Chu el
Entonces, ¿cuál es la diferencia entre x [[[1], [3]], [1,3]] yx [[1,3],:] [:, [1,3]]? Quiero decir, ¿hay una variante que sea mejor para usar que la otra?
Levesque
1
@JC: x[[[1],[3]],[1,3]]crea solo una nueva matriz, mientras que x[[1,3],:][:,[1,3]]copia dos veces, así que use la primera.
Sven Marnach
@JC: O usa el método de la respuesta de Justin.
Sven Marnach
13

No creo que x[[1,3]][:,[1,3]]sea ​​difícil de leer. Si desea ser más claro en su intención, puede hacer lo siguiente:

a[[1,3],:][:,[1,3]]

No soy un experto en segmentación pero, por lo general, si intentas dividir en una matriz y los valores son continuos, obtienes una vista donde se cambia el valor de zancada.

Por ejemplo, en sus entradas 33 y 34, aunque obtiene una matriz de 2x2, el paso 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 funciona bien en el caso de una serie de índices. Por lo tanto, numpy tendrá que hacer la copia. Después de todo, muchas otras funciones matemáticas de matriz dependen del tamaño, el paso y la asignación continua de memoria.

Dat Chu
fuente
10

Si desea omitir todas las filas y 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]])

while z=x[(1,3),:][:,(1,3)]usa 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 no xha cambiado:

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 la división básica. Tendrás que usar indexación avanzada, usando algo como x[rows,:][:,columns], dónde rowsy columnsson secuencias. Esto, por supuesto, le dará una copia, no una vista, de su matriz original. Esto es como uno debería esperar, ya que una matriz numpy usa memoria contigua (con zancadas constantes), y no habría forma de generar una vista con filas y columnas arbitrarias (ya que eso requeriría zancadas no constantes).

unutbu
fuente
5

Con numpy, puede pasar un segmento para cada componente del índice; por lo tanto, su x[0:2,0:2]ejemplo anterior funciona.

Si solo desea omitir columnas o filas de manera uniforme, puede pasar sectores con tres componentes (es decir, inicio, detenció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, parar cuando el índice es igual o mayor que 4, y agregar 2 al índice en cada pasada. Lo mismo para la segunda dimensión. De nuevo: esto solo funciona para pasos constantes.

La sintaxis que tiene que hacer internamente es algo muy diferente: lo que x[[1,3]][:,[1,3]]realmente hace es crear una nueva matriz que incluya solo las filas 1 y 3 de la matriz original (hecha con la x[[1,3]]parte), y luego volver a cortarla, creando una tercera matriz, incluyendo solo columnas 1 y 3 de la matriz anterior.

jsbueno
fuente
1
Esta solución no funciona, ya que es específica de las filas / columnas que estaba tratando de extraer. Imagine lo mismo en una matriz de 50x50, cuando quiero extraer filas / columnas 5,11,12,32,39,45, no hay forma de hacerlo con cortes simples. Lo siento si no estaba claro en mi pregunta.
Levesque
0

No estoy seguro de cuán eficiente es esto, pero puede usar range () para cortar en ambos ejes

 x=np.arange(16).reshape((4,4))
 x[range(1,3), :][:,range(1,3)] 
Valery Marcel
fuente