Numpy roll en varias dimensiones

Necesito cambiar una matriz 3D por un vector 3D de desplazamiento para un algoritmo. A partir de ahora estoy usando este método (admirablemente muy feo):

shiftedArray = np.roll(np.roll(np.roll(arrayToShift, shift[0], axis=0) , shift[1], axis=1), shift[2], axis=2) 

Lo que funciona, pero significa que estoy llamando 3 rollos! (El 58% de mi tiempo de algoritmo se gasta en estos, de acuerdo con mi perfil)

De los documentos de Numpy.roll:

Parámetros:
cambio: int

eje: int, opcional

No se menciona el parámetro de matriz en el parámetro … ¿Entonces no puedo tener una rotación multidimensional?

Pensé que solo podría llamar a este tipo de función (suena como una cosa de Numpy):

 np.roll(arrayToShift,3DshiftVector,axis=(0,1,2)) 

¿Tal vez con una versión aplanada de mi matriz remodelada? pero entonces, ¿cómo calculo el vector de cambio? ¿Y es este cambio realmente el mismo?

Me sorprende no encontrar una solución fácil para esto, ya que pensé que sería algo muy común (bueno, no es tan común, pero …)

Entonces, ¿cómo podemos, en relación, cambiar eficientemente un ndarray por un vector N-Dimensional?


Nota : esta pregunta se hizo en 2015, cuando el método roll de numpy no era compatible con esta función .

En teoría, usar scipy.ndimage.interpolation.shift como lo describe @Ed Smith debería funcionar, pero debido a un error abierto ( https://github.com/scipy/scipy/issues/1323 ), no da una resultado que es equivalente a múltiples llamadas de np.roll .


ACTUALIZACIÓN : la capacidad “Multi-roll” se agregó a numpy.roll en la versión numpy 1.12.0. Aquí hay un ejemplo bidimensional, en el que el primer eje se desplaza una posición y el segundo eje se desplaza tres posiciones:

 In [7]: x = np.arange(20).reshape(4,5) In [8]: x Out[8]: array([[ 0, 1, 2, 3, 4], [ 5, 6, 7, 8, 9], [10, 11, 12, 13, 14], [15, 16, 17, 18, 19]]) In [9]: numpy.roll(x, [1, 3], axis=(0, 1)) Out[9]: array([[17, 18, 19, 15, 16], [ 2, 3, 4, 0, 1], [ 7, 8, 9, 5, 6], [12, 13, 14, 10, 11]]) 

Esto hace que el siguiente código sea obsoleto. Lo dejaré allí para la posteridad.


El siguiente código define una función a la que llamo multiroll que hace lo que quieres. Aquí hay un ejemplo en el que se aplica a una matriz con forma (500, 500, 500):

 In [64]: x = np.random.randn(500, 500, 500) In [65]: shift = [10, 15, 20] 

Use varias llamadas a np.roll para generar el resultado esperado:

 In [66]: yroll3 = np.roll(np.roll(np.roll(x, shift[0], axis=0), shift[1], axis=1), shift[2], axis=2) 

Generar la matriz desplazada utilizando multiroll :

 In [67]: ymulti = multiroll(x, shift) 

Verifique que obtuvimos el resultado esperado:

 In [68]: np.all(yroll3 == ymulti) Out[68]: True 

Para una matriz de este tamaño, hacer tres llamadas a np.roll es casi tres veces más lento que una llamada a multiroll :

 In [69]: %timeit yroll3 = np.roll(np.roll(np.roll(x, shift[0], axis=0), shift[1], axis=1), shift[2], axis=2) 1 loops, best of 3: 1.34 s per loop In [70]: %timeit ymulti = multiroll(x, shift) 1 loops, best of 3: 474 ms per loop 

Aquí está la definición de multiroll :

 from itertools import product import numpy as np def multiroll(x, shift, axis=None): """Roll an array along each axis. Parameters ---------- x : array_like Array to be rolled. shift : sequence of int Number of indices by which to shift each axis. axis : sequence of int, optional The axes to be rolled. If not given, all axes is assumed, and len(shift) must equal the number of dimensions of x. Returns ------- y : numpy array, with the same type and size as x The rolled array. Notes ----- The length of x along each axis must be positive. The function does not handle arrays that have axes with length 0. See Also -------- numpy.roll Example ------- Here's a two-dimensional array: >>> x = np.arange(20).reshape(4,5) >>> x array([[ 0, 1, 2, 3, 4], [ 5, 6, 7, 8, 9], [10, 11, 12, 13, 14], [15, 16, 17, 18, 19]]) Roll the first axis one step and the second axis three steps: >>> multiroll(x, [1, 3]) array([[17, 18, 19, 15, 16], [ 2, 3, 4, 0, 1], [ 7, 8, 9, 5, 6], [12, 13, 14, 10, 11]]) That's equivalent to: >>> np.roll(np.roll(x, 1, axis=0), 3, axis=1) array([[17, 18, 19, 15, 16], [ 2, 3, 4, 0, 1], [ 7, 8, 9, 5, 6], [12, 13, 14, 10, 11]]) Not all the axes must be rolled. The following uses the `axis` argument to roll just the second axis: >>> multiroll(x, [2], axis=[1]) array([[ 3, 4, 0, 1, 2], [ 8, 9, 5, 6, 7], [13, 14, 10, 11, 12], [18, 19, 15, 16, 17]]) which is equivalent to: >>> np.roll(x, 2, axis=1) array([[ 3, 4, 0, 1, 2], [ 8, 9, 5, 6, 7], [13, 14, 10, 11, 12], [18, 19, 15, 16, 17]]) """ x = np.asarray(x) if axis is None: if len(shift) != x.ndim: raise ValueError("The array has %d axes, but len(shift) is only " "%d. When 'axis' is not given, a shift must be " "provided for all axes." % (x.ndim, len(shift))) axis = range(x.ndim) else: # axis does not have to contain all the axes. Here we append the # missing axes to axis, and for each missing axis, append 0 to shift. missing_axes = set(range(x.ndim)) - set(axis) num_missing = len(missing_axes) axis = tuple(axis) + tuple(missing_axes) shift = tuple(shift) + (0,)*num_missing # Use mod to convert all shifts to be values between 0 and the length # of the corresponding axis. shift = [s % x.shape[ax] for s, ax in zip(shift, axis)] # Reorder the values in shift to correspond to axes 0, 1, ..., x.ndim-1. shift = np.take(shift, np.argsort(axis)) # Create the output array, and copy the shifted blocks from x to y. y = np.empty_like(x) src_slices = [(slice(n-shft, n), slice(0, n-shft)) for shft, n in zip(shift, x.shape)] dst_slices = [(slice(0, shft), slice(shft, n)) for shft, n in zip(shift, x.shape)] src_blks = product(*src_slices) dst_blks = product(*dst_slices) for src_blk, dst_blk in zip(src_blks, dst_blks): y[dst_blk] = x[src_blk] return y 

Creo que scipy.ndimage.interpolation.shift hará lo que quieras, desde los documentos.

Cambio: flotador o secuencia, opcional

El cambio a lo largo de los ejes. Si es un flotador, el desplazamiento es el mismo para cada eje. Si es una secuencia, el desplazamiento debe contener un valor para cada eje.

Lo que significa que puedes hacer lo siguiente,

 from scipy.ndimage.interpolation import shift import numpy as np arrayToShift = np.reshape([i for i in range(27)],(3,3,3)) print('Before shift') print(arrayToShift) shiftVector = (1,2,3) shiftedarray = shift(arrayToShift,shift=shiftVector,mode='wrap') print('After shift') print(shiftedarray) 

Cuyos rendimientos,

 Before shift [[[ 0 1 2] [ 3 4 5] [ 6 7 8]] [[ 9 10 11] [12 13 14] [15 16 17]] [[18 19 20] [21 22 23] [24 25 26]]] After shift [[[16 17 16] [13 14 13] [10 11 10]] [[ 7 8 7] [ 4 5 4] [ 1 2 1]] [[16 17 16] [13 14 13] [10 11 10]]] 

Creo que la roll es lenta porque la matriz enrollada no se puede express como una vista de los datos originales como una rebanada o una operación de remodelación. Así que los datos se copian cada vez. Para obtener más información, consulte: https://scipy-lectures.github.io/advanced/advanced_numpy/#life-of-ndarray

Lo que puede valer la pena intentarlo primero es rellenar su matriz (con el modo ‘envolver’) y luego usar los cortes en la matriz rellenada para obtener shiftedArray : http://docs.scipy.org/doc/numpy/reference/generated/numpy. pad.html

Se puede usar el modo de wrap y creo que no cambia la matriz en la memoria.

Aquí hay una implementación que usa las entradas de @ EdSmith:

 arrayToShift = np.reshape([i for i in range(27)],(3,3,3)) shiftVector = np.array((1,2,3)) ind = 3-shiftVector np.take(np.take(np.take(arrayToShift,range(ind[0],ind[0]+3),axis=0,mode='wrap'),range(ind[1],ind[1]+3),axis=1,mode='wrap'),range(ind[2],ind[2]+3),axis=2,mode='wrap') 

Lo que da lo mismo que los OP’s:

 np.roll(np.roll(np.roll(arrayToShift, shift[0], axis=0) , shift[1], axis=1),shift[2], axis=2) 

da:

 array([[[21, 22, 23], [24, 25, 26], [18, 19, 20]], [[ 3, 4, 5], [ 6, 7, 8], [ 0, 1, 2]], [[12, 13, 14], [15, 16, 17], [ 9, 10, 11]]]) 

np.roll toma en múltiples dimensiones. Solo haz

 np.roll(arrayToShift, (shift[0], shift[1], shift[2]), axis=(0,1,2)) 

No es muy inteligente, por lo que hay que especificar el eje.