Copia la matriz 2D en la tercera dimensión, N veces (Python)

Quiero copiar una matriz 2D numpy en una tercera dimensión. Por ejemplo, si tuviera tal matriz numpy (2D):

import numpy as np arr = np.array([[1,2],[1,2]]) 

conviértalo en una matriz 3D con N copias de este tipo en una nueva dimensión, como esto para N = 3:

 np.array([[[1,2],[1,2]],[[1,2],[1,2]],[[1,2],[1,2]]]) 

Probablemente la forma más limpia es usar np.repeat :

 a = np.array([[1, 2], [1, 2]]) print(a.shape) # (2, 2) # indexing with np.newaxis inserts a new 3rd dimension, which we then repeat the # array along, (you can achieve the same effect by indexing with None, see below) b = np.repeat(a[:, :, np.newaxis], 3, axis=2) print(b.shape) # (2, 2, 3) print(b[:, :, 0]) # [[1 2] # [1 2]] print(b[:, :, 1]) # [[1 2] # [1 2]] print(b[:, :, 2]) # [[1 2] # [1 2]] 

Dicho esto, a menudo se puede evitar la repetición de sus matrices utilizando la transmisión . Por ejemplo, digamos que quería agregar un vector (3,) :

 c = np.array([1, 2, 3]) 

a a . Podría copiar el contenido de 3 veces en la tercera dimensión, luego copiar el contenido de c dos veces en la primera y la segunda dimensión, de modo que mis dos arreglos fueron (2, 2, 3) y luego calcular su sum. Sin embargo, es mucho más simple y rápido hacer esto:

 d = a[..., None] + c[None, None, :] 

Aquí, a[..., None] tiene forma (2, 2, 1) y c[None, None, :] tiene forma (1, 1, 3) *. Cuando calculo la sum, el resultado se “difunde” a lo largo de las dimensiones del tamaño 1, lo que me da un resultado de forma (2, 2, 3) :

 print(d.shape) # (2, 2, 3) print(d[..., 0]) # a + c[0] # [[2 3] # [2 3]] print(d[..., 1]) # a + c[1] # [[3 4] # [3 4]] print(d[..., 2]) # a + c[2] # [[4 5] # [4 5]] 

La difusión es una técnica muy poderosa porque evita la sobrecarga adicional involucrada en la creación de copias repetidas de sus matrices de entrada en la memoria.


* Aunque los incluí para mayor claridad, los índices None en c no son realmente necesarios; también puede hacer a[..., None] + c , es decir, transmitir una matriz (2, 2, 1) contra un (3,) matriz. Esto se debe a que si una de las matrices tiene menos dimensiones que la otra, solo las cotas posteriores de las dos matrices deben ser compatibles. Para dar un ejemplo más complicado:

 a = np.ones((6, 1, 4, 3, 1)) # 6 x 1 x 4 x 3 x 1 b = np.ones((5, 1, 3, 2)) # 5 x 1 x 3 x 2 result = a + b # 6 x 5 x 4 x 3 x 2 

Otra forma es usar numpy.dstack . Suponiendo que desea repetir la matriz num_repeats veces:

 import numpy as np b = np.dstack([a]*num_repeats) 

El truco es envolver la matriz a en una lista de un solo elemento, luego usar el operador * para duplicar los elementos en esta lista num_repeats veces.

Por ejemplo, si:

 a = np.array([[1, 2], [1, 2]]) num_repeats = 5 

Esto repite la matriz de [1 2; 1 2] [1 2; 1 2] 5 veces en la tercera dimensión. Para verificar (en IPython):

 In [110]: import numpy as np In [111]: num_repeats = 5 In [112]: a = np.array([[1, 2], [1, 2]]) In [113]: b = np.dstack([a]*num_repeats) In [114]: b[:,:,0] Out[114]: array([[1, 2], [1, 2]]) In [115]: b[:,:,1] Out[115]: array([[1, 2], [1, 2]]) In [116]: b[:,:,2] Out[116]: array([[1, 2], [1, 2]]) In [117]: b[:,:,3] Out[117]: array([[1, 2], [1, 2]]) In [118]: b[:,:,4] Out[118]: array([[1, 2], [1, 2]]) In [119]: b.shape Out[119]: (2, 2, 5) 

Al final podemos ver que la forma de la matriz es 2 x 2 , con 5 cortes en la tercera dimensión.

 A=np.array([[1,2],[3,4]]) B=np.asarray([A]*N) 

Edit @ Mr.F, para preservar el orden de las dimensiones:

 B=BT 

Aquí hay un ejemplo de transmisión que hace exactamente lo que se solicitó.

 a = np.array([[1, 2], [1, 2]]) a=a[:,:,None] b=np.array([1]*5)[None,None,:] 

Entonces b*a es el resultado deseado y (b*a)[:,:,0] produce una array([[1, 2],[1, 2]]) , que es la original a , como lo hace (b*a)[:,:,1] , etc.

Use una vista y obtenga tiempo de ejecución gratis! Extienda los arreglos generics de n-dim a n+1-dim

Introducido en NumPy 1.10.0 , podemos aprovechar numpy.broadcast_to para generar simplemente una vista 3D en la matriz de entrada 2D . El beneficio sería una sobrecarga de memoria adicional y un tiempo de ejecución prácticamente libre. Esto sería esencial en los casos donde los arreglos son grandes y estamos bien para trabajar con vistas. Además, esto funcionaría con casos n-dim generics.

Utilizaría la stack palabras en lugar de copy , ya que los lectores podrían confundirla con la copia de matrices que crea copias en memoria.

Astackr a lo largo del primer eje

Si queremos astackr la entrada a lo largo del primer eje, la solución con np.broadcast_to para crear 3D vista 3D sería:

 np.broadcast_to(arr,(3,)+arr.shape) # N = 3 here 

Astackr a lo largo del tercer / último eje

Para astackr la entrada a lo largo del tercer eje, la solución para crear una vista 3D sería:

 np.broadcast_to(arr[...,None],arr.shape+(3,)) 

Si realmente necesitamos una copia de memoria, siempre podemos adjuntar .copy() allí. Por lo tanto, las soluciones serían:

 np.broadcast_to(arr,(3,)+arr.shape).copy() np.broadcast_to(arr[...,None],arr.shape+(3,)).copy() 

A continuación se muestra cómo funciona el astackmiento para los dos casos, que se muestra con la información de su forma para un caso de muestra:

 # Create a sample input array of shape (4,5) In [55]: arr = np.random.rand(4,5) # Stack along first axis In [56]: np.broadcast_to(arr,(3,)+arr.shape).shape Out[56]: (3, 4, 5) # Stack along third axis In [57]: np.broadcast_to(arr[...,None],arr.shape+(3,)).shape Out[57]: (4, 5, 3) 

Las mismas soluciones trabajarían para extender una entrada n-dim a n+1-dim view a lo largo del primer y último eje. Vamos a explorar algunos casos más oscuros –

Caso de entrada 3D:

 In [58]: arr = np.random.rand(4,5,6) # Stack along first axis In [59]: np.broadcast_to(arr,(3,)+arr.shape).shape Out[59]: (3, 4, 5, 6) # Stack along last axis In [60]: np.broadcast_to(arr[...,None],arr.shape+(3,)).shape Out[60]: (4, 5, 6, 3) 

Caso de entrada 4D:

 In [61]: arr = np.random.rand(4,5,6,7) # Stack along first axis In [62]: np.broadcast_to(arr,(3,)+arr.shape).shape Out[62]: (3, 4, 5, 6, 7) # Stack along last axis In [63]: np.broadcast_to(arr[...,None],arr.shape+(3,)).shape Out[63]: (4, 5, 6, 7, 3) 

y así.

Tiempos

Usemos un caso 2D muestra grande y obtengamos los tiempos y verifiquemos que la salida sea una view .

 # Sample input array In [19]: arr = np.random.rand(1000,1000) 

Probemos que la solución propuesta es una vista de hecho. Usaremos el astackmiento a lo largo del primer eje (los resultados serían muy similares para el astackmiento a lo largo del tercer eje) –

 In [22]: np.shares_memory(arr, np.broadcast_to(arr,(3,)+arr.shape)) Out[22]: True 

Vamos a obtener los horarios para demostrar que es prácticamente gratis –

 In [20]: %timeit np.broadcast_to(arr,(3,)+arr.shape) 100000 loops, best of 3: 3.56 µs per loop In [21]: %timeit np.broadcast_to(arr,(3000,)+arr.shape) 100000 loops, best of 3: 3.51 µs per loop 

Al ser una vista, el aumento de N de 3 a 3000 no cambió nada en los tiempos y ambos son insignificantes en las unidades de tiempo. Por lo tanto, eficiente tanto en memoria como en rendimiento!