Siguiendo con esta pregunta hace años, ¿existe una función canónica de "cambio" en numpy? No veo nada en la documentación .
Aquí hay una versión simple de lo que estoy buscando:
def shift(xs, n):
if n >= 0:
return np.r_[np.full(n, np.nan), xs[:-n]]
else:
return np.r_[xs[-n:], np.full(-n, np.nan)]
Usar esto es como:
In [76]: xs
Out[76]: array([ 0., 1., 2., 3., 4., 5., 6., 7., 8., 9.])
In [77]: shift(xs, 3)
Out[77]: array([ nan, nan, nan, 0., 1., 2., 3., 4., 5., 6.])
In [78]: shift(xs, -3)
Out[78]: array([ 3., 4., 5., 6., 7., 8., 9., nan, nan, nan])
Esta pregunta vino de mi intento de escribir un producto rodante rápido ayer. Necesitaba una forma de "cambiar" un producto acumulativo y todo lo que podía pensar era replicar la lógica np.roll()
.
Entonces np.concatenate()
es mucho más rápido que np.r_[]
. Esta versión de la función funciona mucho mejor:
def shift(xs, n):
if n >= 0:
return np.concatenate((np.full(n, np.nan), xs[:-n]))
else:
return np.concatenate((xs[-n:], np.full(-n, np.nan)))
Una versión aún más rápida simplemente preasigna la matriz:
def shift(xs, n):
e = np.empty_like(xs)
if n >= 0:
e[:n] = np.nan
e[n:] = xs[:-n]
else:
e[n:] = np.nan
e[:n] = xs[-n:]
return e
np.r_[np.full(n, np.nan), xs[:-n]]
podría reemplazarse con lonp.r_[[np.nan]*n, xs[:-n]]
mismo para otra condición, sin la necesidad denp.full
[np.nan]*n
es python simple y, por lo tanto, será más lento quenp.full(n, np.nan)
. No para pequeñosn
, pero np.r_ lo transformará en una matriz numpy, lo que le quita la ventaja.[np.nan]*n
es más rápido quenp.full(n, np.nan)
paran=[10,1000,10000]
. Necesito comprobar sinp.r_
recibe un golpe.Respuestas:
No numpy pero scipy proporciona exactamente la funcionalidad de cambio que desea,
import numpy as np from scipy.ndimage.interpolation import shift xs = np.array([ 0., 1., 2., 3., 4., 5., 6., 7., 8., 9.]) shift(xs, 3, cval=np.NaN)
donde el valor predeterminado es traer un valor constante desde fuera de la matriz con valor
cval
, establecido aquí ennan
. Esto da la salida deseada,array([ nan, nan, nan, 0., 1., 2., 3., 4., 5., 6.])
y el cambio negativo funciona de manera similar,
shift(xs, -3, cval=np.NaN)
Proporciona salida
array([ 3., 4., 5., 6., 7., 8., 9., nan, nan, nan])
fuente
Para aquellos que solo quieren copiar y pegar la implementación más rápida de shift, hay un punto de referencia y una conclusión (ver el final). Además, introduzco el parámetro fill_value y soluciono algunos errores.
Punto de referencia
import numpy as np import timeit # enhanced from IronManMark20 version def shift1(arr, num, fill_value=np.nan): arr = np.roll(arr,num) if num < 0: arr[num:] = fill_value elif num > 0: arr[:num] = fill_value return arr # use np.roll and np.put by IronManMark20 def shift2(arr,num): arr=np.roll(arr,num) if num<0: np.put(arr,range(len(arr)+num,len(arr)),np.nan) elif num > 0: np.put(arr,range(num),np.nan) return arr # use np.pad and slice by me. def shift3(arr, num, fill_value=np.nan): l = len(arr) if num < 0: arr = np.pad(arr, (0, abs(num)), mode='constant', constant_values=(fill_value,))[:-num] elif num > 0: arr = np.pad(arr, (num, 0), mode='constant', constant_values=(fill_value,))[:-num] return arr # use np.concatenate and np.full by chrisaycock def shift4(arr, num, fill_value=np.nan): if num >= 0: return np.concatenate((np.full(num, fill_value), arr[:-num])) else: return np.concatenate((arr[-num:], np.full(-num, fill_value))) # preallocate empty array and assign slice by chrisaycock def shift5(arr, num, fill_value=np.nan): result = np.empty_like(arr) if num > 0: result[:num] = fill_value result[num:] = arr[:-num] elif num < 0: result[num:] = fill_value result[:num] = arr[-num:] else: result[:] = arr return result arr = np.arange(2000).astype(float) def benchmark_shift1(): shift1(arr, 3) def benchmark_shift2(): shift2(arr, 3) def benchmark_shift3(): shift3(arr, 3) def benchmark_shift4(): shift4(arr, 3) def benchmark_shift5(): shift5(arr, 3) benchmark_set = ['benchmark_shift1', 'benchmark_shift2', 'benchmark_shift3', 'benchmark_shift4', 'benchmark_shift5'] for x in benchmark_set: number = 10000 t = timeit.timeit('%s()' % x, 'from __main__ import %s' % x, number=number) print '%s time: %f' % (x, t)
resultado de referencia:
benchmark_shift1 time: 0.265238 benchmark_shift2 time: 0.285175 benchmark_shift3 time: 0.473890 benchmark_shift4 time: 0.099049 benchmark_shift5 time: 0.052836
Conclusión
shift5 es el ganador! Es la tercera solución de OP.
fuente
shift5
es mejor escribir enresult[:] = arr
lugar deresult = arr
, para mantener el comportamiento de la función consistente.type(np.NAN) is float
. Si cambia la matriz de números enteros con estas funciones, debe especificar un valor de relleno entero.No existe una función única que haga lo que quieres. Su definición de cambio es ligeramente diferente de lo que hace la mayoría de la gente. Las formas de cambiar una matriz son más comúnmente en bucle:
>>>xs=np.array([1,2,3,4,5]) >>>shift(xs,3) array([3,4,5,1,2])
Sin embargo, puede hacer lo que quiera con dos funciones.
Considere
a=np.array([ 0., 1., 2., 3., 4., 5., 6., 7., 8., 9.])
:def shift2(arr,num): arr=np.roll(arr,num) if num<0: np.put(arr,range(len(arr)+num,len(arr)),np.nan) elif num > 0: np.put(arr,range(num),np.nan) return arr >>>shift2(a,3) [ nan nan nan 0. 1. 2. 3. 4. 5. 6.] >>>shift2(a,-3) [ 3. 4. 5. 6. 7. 8. 9. nan nan nan]
Después de ejecutar cProfile en su función dada y el código anterior que proporcionó, descubrí que el código que proporcionó hace 42 llamadas a funciones mientras
shift2
realiza 14 llamadas cuando arr es positivo y 16 cuando es negativo.Experimentaré con el tiempo para ver cómo funciona cada uno con datos reales.fuente
np.roll()
; Usé la técnica en los enlaces de mi pregunta. En cuanto a su implementación, ¿hay alguna posibilidad de que pueda hacer que su función funcione para valores de cambio negativos?np.concatenate()
es mucho más rápido quenp.r_[]
. Lo primero es lo quenp.roll()
usa, después de todo.Benchmarks e introducción a Numba
1. Resumen
scipy.ndimage.interpolation.shift
) es la solución más lenta enumerada en esta página.(1) la longitud de sus matrices
(2) la cantidad de turno que necesita hacer.
2. Comparativas detalladas con las mejores opciones
shift4_numba
(definido a continuación) si quiere un buen todoterreno3. Código
3.1
shift4_numba
import numba @numba.njit def shift4_numba(arr, num, fill_value=np.nan): if num >= 0: return np.concatenate((np.full(num, fill_value), arr[:-num])) else: return np.concatenate((arr[-num:], np.full(-num, fill_value)))
3.2.
shift5_numba
import numba @numba.njit def shift5_numba(arr, num, fill_value=np.nan): result = np.empty_like(arr) if num > 0: result[:num] = fill_value result[num:] = arr[:-num] elif num < 0: result[num:] = fill_value result[:num] = arr[-num:] else: result[:] = arr return result
3.3.
shift5
shift5_numba
, simplemente elimine el decorador @ numba.njit.4 Apéndice
4.1 Detalles sobre los métodos utilizados
shift_scipy
:scipy.ndimage.interpolation.shift
(scipy 1.4.1) - La opción de respuesta aceptada, que es claramente la alternativa más lenta .shift1
:np.roll
yout[:num] xnp.nan
por IronManMark20 & gzcshift2
:np.roll
ynp.put
por IronManMark20shift3
:np.pad
yslice
por gzcshift4
:np.concatenate
ynp.full
por chrisaycockshift5
: usando dos vecesresult[slice] = x
por chrisaycockshift#_numba
: @ numba .njit versiones decoradas del anterior.El
shift2
yshift3
funciones contenidas que no fueron apoyados por el numba actual (0.50.1).4.2 Otros resultados de la prueba
4.2.1 Tiempos relativos, todos los métodos
4.2.2 Tiempos brutos, todos los métodos
4.2.3 Tiempos sin procesar, algunos mejores métodos
fuente
Puede convertir
ndarray
aSeries
oDataFrame
con lapandas
primera, a continuación, puede utilizarshift
el método que desee.Ejemplo:
In [1]: from pandas import Series In [2]: data = np.arange(10) In [3]: data Out[3]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) In [4]: data = Series(data) In [5]: data Out[5]: 0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 dtype: int64 In [6]: data = data.shift(3) In [7]: data Out[7]: 0 NaN 1 NaN 2 NaN 3 0.0 4 1.0 5 2.0 6 3.0 7 4.0 8 5.0 9 6.0 dtype: float64 In [8]: data = data.values In [9]: data Out[9]: array([ nan, nan, nan, 0., 1., 2., 3., 4., 5., 6.])
fuente
También puedes hacer esto con Pandas:
Usando una matriz de 2356 de largo:
import numpy as np xs = np.array([...])
Usando scipy:
from scipy.ndimage.interpolation import shift %timeit shift(xs, 1, cval=np.nan) # 956 µs ± 77.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
Usando Pandas:
import pandas as pd %timeit pd.Series(xs).shift(1).values # 377 µs ± 9.42 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
En este ejemplo, usar Pandas fue aproximadamente 8 veces más rápido que Scipy
fuente
Series
técnica nos tomó 146 en mi computadora, mientras que mi enfoque tomó menos de 4.Si quieres una frase única de numpy y no te preocupa demasiado el rendimiento, prueba:
np.sum(np.diag(the_array,1),0)[:-1]
Explicación:
np.diag(the_array,1)
crea una matriz con su matriz una sola vez en la diagonal,np.sum(...,0)
suma la matriz en columnas y...[:-1]
toma los elementos que corresponderían al tamaño de la matriz original. Jugar con los parámetros1
y:-1
as puede darte cambios en diferentes direcciones.fuente
Una forma de hacerlo sin derramar el código en casos
con matriz:
def shift(arr, dx, default_value): result = np.empty_like(arr) get_neg_or_none = lambda s: s if s < 0 else None get_pos_or_none = lambda s: s if s > 0 else None result[get_neg_or_none(dx): get_pos_or_none(dx)] = default_value result[get_pos_or_none(dx): get_neg_or_none(dx)] = arr[get_pos_or_none(-dx): get_neg_or_none(-dx)] return result
con matrix se puede hacer así:
def shift(image, dx, dy, default_value): res = np.full_like(image, default_value) get_neg_or_none = lambda s: s if s < 0 else None get_pos_or_none = lambda s : s if s > 0 else None res[get_pos_or_none(-dy): get_neg_or_none(-dy), get_pos_or_none(-dx): get_neg_or_none(-dx)] = \ image[get_pos_or_none(dy): get_neg_or_none(dy), get_pos_or_none(dx): get_neg_or_none(dx)] return res
fuente