La forma más eficiente de revertir una matriz numpy

276

Créalo o no, después de perfilar mi código actual, la operación repetitiva de la reversión de matriz numpy se comió una gran parte del tiempo de ejecución. Lo que tengo ahora es el método común basado en la vista:

reversed_arr = arr[::-1]

¿Hay alguna otra manera de hacerlo de manera más eficiente, o es solo una ilusión de mi obsesión con el rendimiento poco realista?

nye17
fuente
27
Er ... arr[::-1]solo devuelve una vista invertida. Es lo más rápido que puede obtener y no depende de la cantidad de elementos en la matriz, ya que solo cambia los pasos. ¿Lo que estás invirtiendo es realmente una matriz numpy?
Joe Kington
sí, de hecho, arres una matriz numpy.
nye17
12
Hmmm ... Bueno, en mi computadora portátil toma alrededor de 670 nanosegundos, independientemente de la longitud de la matriz. Si ese es su cuello de botella, es posible que deba cambiar de idioma ... Estoy bastante seguro de que no encontrará una forma más rápida de revertir una matriz numpy. Buena suerte, en cualquier caso!
Joe Kington
66
Bueno, ¿necesariamente tienes que ejecutarlo dentro de un bucle? En algunos casos, es mejor hacer una matriz numpy con millones de elementos y luego operar en toda la matriz. Incluso si está haciendo un método de diferencia finita o algo similar donde el resultado depende del resultado anterior, a veces puede hacer esto. (Énfasis en algunas veces ...) En cualquier caso, si la velocidad es el objetivo principal, fortran sigue siendo el rey. f2py¡es tu amigo! A menudo vale la pena escribir partes críticas del rendimiento de un algoritmo (especialmente en informática científica) en otro idioma y llamarlo desde python. ¡Buena suerte!
Joe Kington
1
@berto. Es más lento ya que es un contenedor para arr[::-1]: github.com/numpy/numpy/blob/master/numpy/lib/twodim_base.py . Buscar def flipud. La función tiene literalmente cuatro líneas de largo.
Mad Physicist el

Respuestas:

240

Cuando crea reversed_arr, está creando una vista en la matriz original. Luego puede cambiar la matriz original y la vista se actualizará para reflejar los cambios.

¿Vuelve a crear la vista con más frecuencia de la que necesita? Deberías poder hacer algo como esto:

arr = np.array(some_sequence)
reversed_arr = arr[::-1]

do_something(arr)
look_at(reversed_arr)
do_something_else(arr)
look_at(reversed_arr)

No soy un experto numpy, pero parece que sería la forma más rápida de hacer las cosas en numpy. Si esto es lo que ya está haciendo, no creo que pueda mejorarlo.

PD Gran discusión de puntos de vista numpy aquí:

Ver en una matriz numpy?

steveha
fuente
¿Ayuda a crear un objeto de división y luego reutilizarlo en muchas matrices?
endolito
1
En realidad, acabo de probarlo y no veo ninguna diferencia con el objeto de corte creado fuera del bucle. (Oh, espera, es un poco más rápido. Repetiblemente 43.4 ms frente a 44.3 ms para un bucle 1000000)
endolito
¿Qué se look_atsupone que debe hacer la función?
mrgloom
1
@mrgloom Se supone que representa cualquier tarea que observe los datos. El objetivo del ejemplo era mostrar que la vista reversed_arrtodavía es utilizable después de que se modificaron los datos subyacentes. Escribir nuevos valores en la matriz no invalida la vista. En realidad, también podría usar la vista para escribir nuevos valores en la matriz. reversed_arr[0] = 99establecería el último elemento de la matriz en 99, igual que lo arr[-1] = 99haría.
steveha
60

Como se mencionó anteriormente, en a[::-1]realidad solo crea una vista, por lo que es una operación de tiempo constante (y como tal no toma más tiempo a medida que la matriz crece). Si necesita que la matriz sea contigua (por ejemplo, porque está realizando muchas operaciones vectoriales con ella), ascontiguousarrayes casi tan rápido como flipup/ fliplr:

ingrese la descripción de la imagen aquí


Código para generar la trama:

import numpy
import perfplot


perfplot.show(
    setup=lambda n: numpy.random.randint(0, 1000, n),
    kernels=[
        lambda a: a[::-1],
        lambda a: numpy.ascontiguousarray(a[::-1]),
        lambda a: numpy.fliplr([a])[0],
    ],
    labels=["a[::-1]", "ascontiguousarray(a[::-1])", "fliplr"],
    n_range=[2 ** k for k in range(25)],
    xlabel="len(a)",
    logx=True,
    logy=True,
)
Nico Schlömer
fuente
perfplot requiere al menos Python 3.6 porque usa f-strings (interpolación de cadenas literales)
cinco de
42

Porque esto parece no estar marcado como respondido aún ... La respuesta de Thomas Arildsen debería ser la correcta: solo use

np.flipud(your_array) 

si es una matriz 1d (matriz de columnas).

Con matrices hacer

fliplr(matrix)

si desea invertir las filas y flipud(matrix)si desea voltear las columnas. No es necesario hacer que su matriz de columnas 1d sea una matriz de filas bidimensional (matriz con una capa None) y luego voltearla.

zauberfein
fuente
38

np.fliplr() Voltea la matriz de izquierda a derecha.

Tenga en cuenta que para las matrices 1d, debe engañarlo un poco:

arr1d = np.array(some_sequence)
reversed_arr = np.fliplr([arr1d])[0]
tooty44
fuente
34
reversed_arr = np.flipud(arr1d)Parece funcionar directamente.
Thomas Arildsen
3

Ampliaré la respuesta anterior sobre np.fliplr(). Aquí hay un código que demuestra la construcción de una matriz 1d, transformándola en una matriz 2d, volteándola y luego convirtiéndola nuevamente en una matriz 1d. time.clock()se usará para mantener el tiempo, que se presenta en términos de segundos.

import time
import numpy as np

start = time.clock()
x = np.array(range(3))
#transform to 2d
x = np.atleast_2d(x)
#flip array
x = np.fliplr(x)
#take first (and only) element
x = x[0]
#print x
end = time.clock()
print end-start

Con la declaración de impresión sin comentar:

[2 1 0]
0.00203907123594

Con la declaración impresa comentada:

5.59799927506e-05

Entonces, en términos de eficiencia, creo que es decente. Para aquellos de ustedes que aman hacerlo en una línea, aquí está esa forma.

np.fliplr(np.atleast_2d(np.array(range(3))))[0]
M. Murphy
fuente
3
Sincronizar algo con una matriz tan pequeña es bastante inútil. Si desea comparar cosas, sería mejor usar algo que lleve un tiempo, como 3000 o tal vez incluso más elementos.
Barabas
0

Ampliando lo que otros han dicho, daré un breve ejemplo.

Si tienes una matriz 1D ...

>>> import numpy as np
>>> x = np.arange(4) # array([0, 1, 2, 3])
>>> x[::-1] # returns a view
Out[1]: 
array([3, 2, 1, 0])

Pero si está trabajando con una matriz 2D ...

>>> x = np.arange(10).reshape(2, 5)
>>> x
Out[2]:
array([[0, 1, 2, 3, 4],
       [5, 6, 7, 8, 9]])

>>> x[::-1] # returns a view:
Out[3]: array([[5, 6, 7, 8, 9],
               [0, 1, 2, 3, 4]])

Esto en realidad no revierte la Matriz.

Debería usar np.flip para invertir los elementos

>>> np.flip(x)
Out[4]: array([[9, 8, 7, 6, 5],
               [4, 3, 2, 1, 0]])

Si desea imprimir los elementos de una matriz uno por uno, use plano junto con flip

>>> for el in np.flip(x).flat:
>>>     print(el, end = ' ')
9 8 7 6 5 4 3 2 1 0
John Mil.
fuente
-1

Para que funcione con números negativos y una larga lista, puede hacer lo siguiente:

b = numpy.flipud(numpy.array(a.split(),float))

Donde flipud es para 1d arra

Boris Stoyanov
fuente