6.6. Indexação e fatiamento

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

6.6.1. Elementos únicos

A indexação com colchetes retorna o valor na posição dada:

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 em uma list Python. Um índice fora do intervalo gera IndexError.

Para arrays de posto mais alto, cada eixo recebe um índice. Os índices vão dentro de um único conjunto de colchetes, 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 permanecem intactos. O resultado é um view de posto reduzido da origem:

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

6.6.2. Fatias

Uma fatia start:stop:step retorna um view do array. O view compartilha o buffer de dados subjacente com a origem; escrever através do view escreve na origem:

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 um buffer independente é necessário, copy() produz um explicitamente.

O fatiamento se estende naturalmente a dimensões mais altas. Cada eixo recebe 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

Misturar um inteiro (índice único, descarta o eixo) e uma fatia (mantém o eixo) é permitido e é a forma usual de escrever o acesso a uma única linha / coluna.

6.6.3. Máscaras booleanas

Um array booleano com o mesmo formato da origem seleciona os elementos onde a máscara é True. A indexação booleana atualmente funciona em arrays 1-D; entradas de posto mais alto geram 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, então 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 retorna uma cópia. Os elementos selecionados ficam em quaisquer posições onde a máscara é True – não em um passo regular através da origem – então não há descritor que um view pudesse usar para endereçá-los, e o resultado é materializado em seu próprio buffer.

6.6.4. Indexação por array de inteiros

Passar uma lista ou array de índices entre colchetes seleciona esses elementos em uma única etapa:

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 já não compartilham armazenamento com a origem. A mesma forma funciona à esquerda 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 de função da mesma operação e aceita uma palavra-chave out= para uso sem alocação em um laço de streaming.

6.6.5. Atribuição por fatia

Fatias e máscaras aparecem à esquerda de uma atribuição além de à direita. O lado direito pode ser um escalar, outro array ou um view:

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 à esquerda 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 em uma câmera

A atribuição por fatia escreve através de um array que já existe. Nenhum array novo é alocado. Essa é 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âmera RAM equivalente a dois arrays novos; a segunda versão não pede nada. Em um microcontrolador com RAM limitada, essa diferença é frequentemente a diferença entre um script que roda confortavelmente e outro que fica sem memória.

Desempenho aborda o padrão em detalhe. A regra importante por ora é 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 atualizações sem alocação.