python - Seleccionando mĂșltiples cortes de una matriz numpy a la vez
slice (6)
Estoy buscando una manera de seleccionar múltiples rebanadas de una matriz numpy a la vez. Digamos que tenemos una matriz de datos 1D y queremos extraer tres porciones de ella como a continuación:
data_extractions = []
for start_index in range(0, 3):
data_extractions.append(data[start_index: start_index + 5])
Posteriormente
data_extractions
será:
data_extractions = [
data[0:5],
data[1:6],
data[2:7]
]
¿Hay alguna forma de realizar la operación anterior sin el bucle for? ¿Algún tipo de esquema de indexación en numpy que me permita seleccionar múltiples cortes de una matriz y devolverlos como esa cantidad de matrices, digamos en una matriz dimensional n + 1?
Pensé que tal vez podría replicar mis datos y luego seleccionar un intervalo de cada fila, pero el siguiente código arroja un IndexError
replicated_data = np.vstack([data] * 3)
data_extractions = replicated_data[[range(3)], [slice(0, 5), slice(1, 6), slice(2, 7)]
En el caso general, debe realizar algún tipo de iteración, y concatenación, ya sea al construir los índices o al recopilar los resultados.
Solo cuando el patrón de corte es regular en sí mismo, puede usar un corte generalizado a través de
as_strided
.
La respuesta aceptada construye una matriz de indexación, una fila por segmento.
Entonces eso es iterar sobre los sectores, y un
arange
sí mismo es una iteración (rápida).
Y
np.array
concatena en un nuevo eje (
np.stack
generaliza esto).
In [264]: np.array([np.arange(0,5), np.arange(1,6), np.arange(2,7)])
Out[264]:
array([[0, 1, 2, 3, 4],
[1, 2, 3, 4, 5],
[2, 3, 4, 5, 6]])
métodos de conveniencia
indexing_tricks
para hacer lo mismo:
In [265]: np.r_[0:5, 1:6, 2:7]
Out[265]: array([0, 1, 2, 3, 4, 1, 2, 3, 4, 5, 2, 3, 4, 5, 6])
Esto toma la notación de corte, la expande con un
arange
y concatena.
Incluso me permite ampliar y concatenar en 2d
In [269]: np.r_[''0,2'',0:5, 1:6, 2:7]
Out[269]:
array([[0, 1, 2, 3, 4],
[1, 2, 3, 4, 5],
[2, 3, 4, 5, 6]])
In [270]: data=np.array(list(''abcdefghijk''))
In [272]: data[np.r_[''0,2'',0:5, 1:6, 2:7]]
Out[272]:
array([[''a'', ''b'', ''c'', ''d'', ''e''],
[''b'', ''c'', ''d'', ''e'', ''f''],
[''c'', ''d'', ''e'', ''f'', ''g'']],
dtype=''<U1'')
In [273]: data[np.r_[0:5, 1:6, 2:7]]
Out[273]:
array([''a'', ''b'', ''c'', ''d'', ''e'', ''b'', ''c'', ''d'', ''e'', ''f'', ''c'', ''d'', ''e'',
''f'', ''g''],
dtype=''<U1'')
Los resultados de concatenación después de la indexación también funcionan.
In [274]: np.stack([data[0:5],data[1:6],data[2:7]])
Mi memoria de otras preguntas SO es que los tiempos relativos están en el mismo orden de magnitud. Puede variar, por ejemplo, con el número de cortes en función de su longitud. En general, el número de valores que deben copiarse de origen a destino será el mismo.
Si las secciones varían en longitud, tendrías que usar la indexación plana.
En esta publicación hay un enfoque con
strided-indexing scheme
np.lib.stride_tricks.as_strided
que usa
np.lib.stride_tricks.as_strided
que básicamente crea una vista en la matriz de entrada y, como tal, es bastante eficiente para la creación y la vista ocupa un espacio de memoria similar.
Además, esto funciona para ndarrays con un número genérico de dimensiones.
Aquí está la implementación:
def strided_axis0(a, L):
# Store the shape and strides info
shp = a.shape
s = a.strides
# Compute length of output array along the first axis
nd0 = shp[0]-L+1
# Setup shape and strides for use with np.lib.stride_tricks.as_strided
# and get (n+1) dim output array
shp_in = (nd0,L)+shp[1:]
strd_in = (s[0],) + s
return np.lib.stride_tricks.as_strided(a, shape=shp_in, strides=strd_in)
Ejecución de muestra para un caso de matriz
4D
:
In [44]: a = np.random.randint(11,99,(10,4,2,3)) # Array
In [45]: L = 5 # Window length along the first axis
In [46]: out = strided_axis0(a, L)
In [47]: np.allclose(a[0:L], out[0]) # Verify outputs
Out[47]: True
In [48]: np.allclose(a[1:L+1], out[1])
Out[48]: True
In [49]: np.allclose(a[2:L+2], out[2])
Out[49]: True
Podemos usar la comprensión de la lista para esto
data=np.array([1,2,3,4,5,6,7,8,9,10])
data_extractions=[data[b:b+5] for b in [1,2,3,4,5]]
data_extractions
Resultados
[array([2, 3, 4, 5, 6]), array([3, 4, 5, 6, 7]), array([4, 5, 6, 7, 8]), array([5, 6, 7, 8, 9]), array([ 6, 7, 8, 9, 10])]
Puede cortar su matriz con una matriz de corte preparada
a = np.array(list(''abcdefg''))
b = np.array([
[0, 1, 2, 3, 4],
[1, 2, 3, 4, 5],
[2, 3, 4, 5, 6]
])
a[b]
Sin embargo,
b
no tiene que generarse a mano de esta manera.
Puede ser más dinámico con
b = np.arange(5) + np.arange(3)[:, None]
Puede usar los índices para seleccionar las filas que desee en la forma adecuada. Por ejemplo:
data = np.random.normal(size=(100,2,2,2))
# Creating an array of row-indexes
indexes = np.array([np.arange(0,5), np.arange(1,6), np.arange(2,7)])
# data[indexes] will return an element of shape (3,5,2,2,2). Converting
# to list happens along axis 0
data_extractions = list(data[indexes])
np.all(data_extractions[1] == s[1:6])
True
stride_tricks
puede hacer eso
a = np.arange(10)
b = np.lib.stride_tricks.as_strided(a, (3, 5), 2 * a.strides)
b
# array([[0, 1, 2, 3, 4],
# [1, 2, 3, 4, 5],
# [2, 3, 4, 5, 6]])
Tenga en cuenta que
b
referencia a la misma memoria que
a
, de hecho varias veces (por ejemplo,
b[0, 1]
b[1, 0]
son la misma dirección de memoria).
Por lo tanto, es más seguro hacer una copia antes de trabajar con la nueva estructura.
nd se puede hacer de manera similar, por ejemplo 2d -> 4d
a = np.arange(16).reshape(4, 4)
b = np.lib.stride_tricks.as_strided(a, (3,3,2,2), 2*a.strides)
b.reshape(9,2,2) # this forces a copy
# array([[[ 0, 1],
# [ 4, 5]],
# [[ 1, 2],
# [ 5, 6]],
# [[ 2, 3],
# [ 6, 7]],
# [[ 4, 5],
# [ 8, 9]],
# [[ 5, 6],
# [ 9, 10]],
# [[ 6, 7],
# [10, 11]],
# [[ 8, 9],
# [12, 13]],
# [[ 9, 10],
# [13, 14]],
# [[10, 11],
# [14, 15]]])