functions - envolviendo rebanadas en Python/numpy
numpy python 3.6 windows (6)
Tengo una matriz numpy, y quiero obtener el "vecindario" del punto i. Por lo general, las matrices que estoy usando son bidimensionales, pero el siguiente ejemplo 1D ilustra lo que estoy buscando. Si
A = numpy.array([0,10,20,30,40,50,60,70,80,90])
Entonces el vecindario (tamaño 5) del elemento 4 es [20,30,40,50,60]
, y esto puede obtenerse fácilmente haciendo A[i-2:i+3]
.
Sin embargo, también necesito que los vecindarios "envuelvan" los bordes de la matriz, de modo que la vecindad del elemento 0 sea [80,90,0,10,20]
y la vecindad del elemento 9 sea [70,80,90,0,10]
. Parece que no puedo encontrar una forma elegante de hacer esto, así que termino teniendo que usar alguna lógica complicada y molesta cada vez que esto aparece (lo cual es muy a menudo para mí). En el caso 2D, el vecindario de un punto sería una matriz rectangular.
Entonces mi pregunta es, ¿hay una forma clara de expresar esta operación de "vecindario integral" en numpy? Preferiría algo que devuelva un corte en lugar de una copia, pero la legibilidad y la velocidad son las consideraciones más importantes.
numpy.take
en ''wrap''
modo ''wrap''
utilizará sus índices módulo la longitud de la matriz.
indices = range(i-2,i+3)
neighbourhood = A.take(indices, mode=''wrap'')
Ver la documentación para más detalles numpy.take
puede usar argumento axis=0
de numpy.take
para nd array.
A = zip(range(0,101,10),range(0,11)) #create 2-d list
A = numpy.array(A) #create 2-d array
indices = range(i-2,i+3)
neightbourhood = A.take(indices,axis=0,mode=''wrap'')
El mismo axis=0
funcionará para n * m dimensiones ...
Puede usar la rutina np.pad de esta manera:
A = np.array([0,10,20,30,40,50,60,70,80,90])
A = np.pad(A, 2, ''wrap'')
print(A)
[80, 90, 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 0, 10]
Digamos que tienes una función de vecino:
def nbf(arr):
return sum(arr)
Para aplicar la función neighbor a cada 5, debe tener cuidado con sus índices de inicio y fin (en el comando range (...)) y la porción relativa que toma de A.
B = [nbf(A[i-2:i+3]) for i in range(2,12)]
print(B)
[200, 150, 100, 150, 200, 250, 300, 350, 300, 250]
numpy.roll puede cambiar la matriz de tal manera que toda la división se encuentre al comienzo de la matriz. A continuación, tome su porción al principio y numpy. Vuelva a girar para volver la matriz a su posición original.
# modify array at index i and nearest two
# locations on each side of i, wrapping
# around the edges
A = np.array([0,10,20,30,40,50,60,70,80,90])
i = 9
neighbors = 2
A=np.roll(A, -i+neighbors)
A[:5] += 1
A=np.roll(A, i-neighbors)
array([ 1, 11, 20, 30, 40, 50, 60, 71, 81, 91])
numpy.roll no funciona bien para mí en arreglos grandes sin embargo.
Nota: Para los casos en que sus vecinos no requieren envolverse, numpy.take
es más lento que simplemente tomar un segmento A[i-2:i+3]
. Es posible que desee ajustar la función de sus vecinos con algunas sentencias condicionales:
def neighbors(a,i,n):
N = a.shape[0]
if i - n < 0 and i + n > 0:
indices = range(i-n,i+n+1)
nbrs = a.take(indices, mode=''wrap'')
elif i-n < N - 1 and i+n > N - 1:
indices = range(i-n,i+n+1)
nbrs = a.take(indices, mode=''wrap'')
else:
nbrs = a[i-n:i+n+1]
return nbrs
Si se ve tomando vecinos mientras itera a través de una matriz, como en una media móvil centrada, encontrará que esto requiere menos tiempo, especialmente para las matrices más largas:
Aquí está la función de promedio móvil que utilicé:
def moving_average(a,n=1):
N = a.shape[0]
ma = np.empty(N)
for i in range(N):
if n*2+1 > N:
ma[i] = a.mean()
else:
ma[i] = neighbors(a,i,n).mean()
return ma
Estoy seguro de que estas funciones se pueden mejorar aún más. Estoy abierto a sugerencias.
Sé que esta pregunta es antigua, pero debería mencionar scipy.ndimage.filter.generic_filter .
Tiene una opción mode=''wrap''
, además maneja la aplicación de la función neighbor.
import scipy.ndimage as nd
A = np.array([0,10,20,30,40,50,60,70,80,90])
Digamos que tienes una función de vecino:
def nbf(arr):
return sum(arr)
Para aplicar la función vecina a cada 5, con valores envueltos en los bordes:
C = nd.generic_filter(A, nbf, 5, mode=''wrap'')
print(C)
[200 150 100 150 200 250 300 350 300 250]