6.6. Indexación y rebanado¶
Un ndarray se direcciona de cuatro maneras: índices simples, rebanadas, máscaras booleanas y las formas de asignación de cada una.
6.6.1. Elementos individuales¶
La indexación con corchetes devuelve el valor en la posición dada:
a = np.arange(10, dtype=np.uint8)
print(a[0], a[-1]) # 0 9
print(a[1], a[-2]) # 1 8
Los índices negativos cuentan desde el final, igual que en una list de Python. Un índice fuera de rango genera un IndexError.
Para arreglos de rango superior, cada eje toma un índice. Los índices van dentro de un solo conjunto de corchetes, separados por comas:
m = np.arange(9, dtype=np.uint8).reshape((3, 3))
print(m[1, 1]) # 4
print(m[2, 0]) # 6
Cuando se proporcionan menos índices que ejes, los ejes no indexados se dejan intactos. El resultado es una vista de rango reducido del origen:
print(m[0]) # the first row, as a 1-D view of m
6.6.2. Rebanadas¶
Una rebanada start:stop:step devuelve una vista del arreglo. La vista comparte el búfer de datos subyacente con el origen; escribir a través de la vista escribe en el origen:
a = np.arange(10, dtype=np.uint8)
v = a[::2] # array([0, 2, 4, 6, 8], dtype=uint8)
v[0] = 99
print(a)
# array([99, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=uint8)
Cuando se necesita un búfer independiente, copy() produce uno de forma explícita.
El rebanado se extiende de forma natural a dimensiones superiores. Cada eje toma su propia rebanada:
m = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]], dtype=np.uint8)
m[0] # first row
m[0, :2] # first two elements of row 0
m[:, 0] # column 0 (still 2-D in ulab)
m[-1] # last row
m[::2, ::2] # every other row, every other column
Mezclar un entero (índice simple, elimina el eje) y una rebanada (conserva el eje) está permitido y es la forma habitual de escribir el acceso a una sola fila o a una sola columna.
6.6.3. Máscaras booleanas¶
Un arreglo booleano con la misma forma que el origen selecciona los elementos donde la máscara es True. La indexación booleana actualmente funciona con arreglos de 1-D; las entradas de rango superior generan un NotImplementedError:
a = np.arange(9, dtype=np.float)
mask = a < 5
print(a[mask])
Salida:
array([0.0, 1.0, 2.0, 3.0, 4.0], dtype=float)
La máscara es un ndarray ordinario de tipo bool, por lo que funciona cualquier expresión que produzca uno:
b = np.array([4, 4, 4, 3, 3, 3, 13, 13, 13], dtype=np.uint8)
a = np.arange(9, dtype=np.uint8)
print(a[a * a > np.sin(b) * 100.0])
La indexación booleana devuelve una copia. Los elementos seleccionados se encuentran en cualquier posición donde la máscara sea True – no en un paso regular a través del origen – por lo que no existe ningún descriptor que una vista pueda usar para direccionarlos, y el resultado se materializa en su propio búfer.
6.6.4. Indexación con arreglos de enteros¶
Pasar una lista o un arreglo de índices entre corchetes selecciona esos elementos en un solo paso:
a = np.array([10, 20, 30, 40, 50], dtype=np.uint8)
a[[0, 2, 4]]
# array([10, 30, 50], dtype=uint8)
El resultado es una copia; los elementos seleccionados ya no comparten almacenamiento con el origen. La misma forma funciona en el lado izquierdo de una asignación:
a[[0, 2, 4]] = 0
# array([0, 20, 0, 40, 0], dtype=uint8)
take() (descrita en Selección y reordenación) es la forma de función de la misma operación y acepta una palabra clave out= para un uso sin asignación de memoria en un bucle de transmisión.
6.6.5. Asignación por rebanadas¶
Las rebanadas y las máscaras aparecen en el lado izquierdo de una asignación además del derecho. El lado derecho puede ser un escalar, otro arreglo o una vista:
m = np.zeros((3, 3), dtype=np.uint8)
m[0] = 1 # whole row 0 set to 1
m[:, 2] = 3 # whole column 2 set to 3
m[1, 1:3] = [7, 8] # row 1, columns 1 and 2
Las máscaras booleanas a la izquierda reemplazan los elementos que satisfacen la condición:
a = np.arange(9, dtype=np.uint8)
a[a < 3] = 99
# array([99, 99, 99, 3, 4, 5, 6, 7, 8], dtype=uint8)
a = np.arange(9, dtype=np.uint8)
b = np.array(range(9)) + 12
a[b < 15] = b[b < 15]
# array([12, 13, 14, 3, 4, 5, 6, 7, 8], dtype=uint8)
6.6.6. Por qué importa la asignación por rebanadas en una cámara¶
La asignación por rebanadas escribe a través de un arreglo que ya existe. No se asigna ningún arreglo nuevo. Esa es la diferencia entre:
out = a + b # makes a new array the size of a
out = out * 2 # makes another new array
y:
out[:] = a # writes into the existing out
out += b # in place
out *= 2 # in place
La primera versión solicita a la cámara RAM equivalente a dos arreglos nuevos; la segunda versión no solicita nada. En un microcontrolador con RAM limitada, esa diferencia suele ser la diferencia entre un script que se ejecuta con holgura y otro que se queda sin memoria.
Rendimiento cubre el patrón en detalle. La regla importante por ahora es que la asignación por rebanadas, los operadores aritméticos in situ (+=, *=, …) y la palabra clave out= de las funciones universales son las tres herramientas que hacen posibles las actualizaciones sin asignación de memoria.