6.6. Indexação e fatiamento

Um ndarray é endereçado de quatro formas: índices simples, fatias, máscaras booleanas e as formas de atribuição de cada uma.

6.6.1. Elementos individuais

A indexação com parênteses retos devolve o valor na posição indicada:

a = np.arange(10, dtype=np.uint8)
print(a[0], a[-1])      # 0 9
print(a[1], a[-2])      # 1 8

Índices negativos contam a partir do fim, da mesma forma que numa list Python. Um índice fora do intervalo lança IndexError.

Para arrays de maior rank, cada eixo recebe um índice. Os índices ficam dentro de um único conjunto de parênteses, separados por vírgulas:

m = np.arange(9, dtype=np.uint8).reshape((3, 3))
print(m[1, 1])          # 4
print(m[2, 0])          # 6

Quando são fornecidos menos índices do que eixos, os eixos não indexados ficam intactos. O resultado é uma vista de rank reduzido da fonte:

print(m[0])             # the first row, as a 1-D view of m

6.6.2. Fatias

Uma fatia start:stop:step devolve uma vista do array. A vista partilha o buffer de dados subjacente com a fonte; escrever através da vista escreve na fonte:

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)

Quando é necessário um buffer independente, copy() produz um explicitamente.

O fatiamento estende-se naturalmente a dimensões superiores. Cada eixo recebe a sua própria fatia:

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

É permitido misturar um inteiro (índice simples, remove o eixo) e uma fatia (mantém o eixo), sendo esta a forma habitual de aceder a uma única linha ou coluna.

6.6.3. Máscaras booleanas

Um array booleano com a mesma forma da fonte seleciona os elementos onde a máscara é True. A indexação booleana funciona atualmente em arrays 1-D; entradas de maior rank lançam NotImplementedError

a = np.arange(9, dtype=np.float)
mask = a < 5
print(a[mask])

Saída:

array([0.0, 1.0, 2.0, 3.0, 4.0], dtype=float)

A máscara é um ndarray bool comum, por isso qualquer expressão que produza um funciona:

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])

A indexação booleana devolve uma cópia. Os elementos selecionados encontram-se em posições arbitrárias onde a máscara é True – não a um passo regular pela fonte – por isso não existe nenhum descritor que uma vista pudesse usar para os endereçar, e o resultado é materializado no seu próprio buffer.

6.6.4. Indexação por array de inteiros

Passar uma lista ou array de índices entre parênteses retos seleciona esses elementos num único passo:

a = np.array([10, 20, 30, 40, 50], dtype=np.uint8)
a[[0, 2, 4]]
# array([10, 30, 50], dtype=uint8)

O resultado é uma cópia; os elementos selecionados deixam de partilhar armazenamento com a fonte. A mesma forma funciona no lado esquerdo de uma atribuição:

a[[0, 2, 4]] = 0
# array([0, 20, 0, 40, 0], dtype=uint8)

take() (abordada em Seleção e reordenação) é a forma funcional da mesma operação e aceita a palavra-chave out= para uso sem alocação num ciclo de processamento contínuo.

6.6.5. Atribuição por fatia

Fatias e máscaras aparecem no lado esquerdo de uma atribuição, bem como no direito. O lado direito pode ser um escalar, outro array ou uma 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

Máscaras booleanas no lado esquerdo substituem os elementos que satisfazem a condição:

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 que a atribuição por fatia importa numa câmara

A atribuição por fatia escreve num array que já existe. Não é alocado nenhum novo array. Esta é a diferença entre:

out = a + b              # makes a new array the size of a
out = out * 2            # makes another new array

e:

out[:] = a               # writes into the existing out
out   += b               # in place
out   *= 2               # in place

A primeira versão pede à câmara dois novos arrays de RAM; a segunda versão não pede nada. Num microcontrolador com RAM limitada, essa diferença é frequentemente a diferença entre um script que corre confortavelmente e um que fica sem memória.

Desempenho aborda o padrão em detalhe. A regra importante para já é que a atribuição por fatia, os operadores aritméticos in-place (+=, *=, …) e a palavra-chave out= nas funções universais são as três ferramentas que tornam possíveis as atualizações sem alocação.