6.12. Selezione e riorganizzazione

Le riduzioni contraggono un array fino a uno scalare o a un risultato di rango inferiore. La selezione copre le operazioni che scelgono quali elementi sopravvivono e dove finiscono: scelta condizionale, clipping, ordinamento, ricerca di indici, riordino lungo un asse.

6.12.1. Scelta condizionale

where() restituisce un array che prende gli elementi da x dove la condizione è vera e da y altrimenti. I tre operandi vengono propagati insieme tramite broadcasting:

a = np.array([1, 2, 3, 4, 5], dtype=np.float)
np.where(a < 3, a, 0.0)
# array([1.0, 2.0, 0.0, 0.0, 0.0])

È lo strumento giusto per un «if/else per elemento» senza scrivere un ciclo Python.

clip() è una scorciatoia per maximum(lo, minimum(a, hi)) – satura i valori entro un intervallo:

np.clip(a, 2.0, 4.0)
# array([2.0, 2.0, 3.0, 4.0, 4.0])

maximum() e minimum() prendono due operandi e restituiscono, elemento per elemento, il maggiore / minore:

np.maximum(a, 3.0)
np.minimum(a, np.array([5, 4, 3, 2, 1]))

6.12.2. Trovare gli indici

nonzero() restituisce le coordinate di ogni elemento diverso da zero, suddivise in un array di indici per dimensione. Per un input 2-D il risultato è una tupla di due array: il primo contiene gli indici di riga, il secondo contiene gli indici di colonna. Accoppiandoli colonna per colonna si ottiene il (row, col) di ogni posizione diversa da zero:

m = np.array([[0, 2, 0],
              [3, 0, 0]], dtype=np.float)
np.nonzero(m)
# (array([0, 1], dtype=uint16), array([1, 0], dtype=uint16))

Le voci diverse da zero in m sono m[0, 1] = 2 e m[1, 0] = 3. Il primo array restituito [0, 1] fornisce i loro indici di riga; il secondo [1, 0] fornisce i loro indici di colonna. Leggendo i due array affiancati si recuperano le posizioni (0, 1) e (1, 0).

Anche due riduzioni producono indici:

  • argmin() / argmax() – indice dell’elemento più piccolo / più grande.

  • argsort() – un array di interi che ordinerebbe l’input lungo l’asse indicato (per impostazione predefinita l’ultimo):

    a = np.array([40, 10, 30, 20], dtype=np.uint8)
    idx = np.argsort(a)             # array([1, 3, 2, 0], dtype=uint16)
    a[idx]                          # array([10, 20, 30, 40])
    

    argsort restituisce sempre uint16; l’array da ordinare non deve quindi avere più di 65.535 elementi sull’asse ordinato.

bincount() conta le occorrenze di ogni intero non negativo in un input 1-D uint8 / uint16

histogram = np.bincount(np.array([0, 1, 1, 2, 2, 2], dtype=np.uint8))
# array([1, 2, 3], dtype=uint16)

Utile per costruire istogrammi di valori di pixel a interi piccoli senza scrivere un ciclo Python.

6.12.3. Ordinamento e riordino

sort() restituisce una copia ordinata dell’array lungo l’asse indicato (l’ultimo per impostazione predefinita). Usa sort() direttamente sull’array per una versione in-place:

np.sort(np.array([3, 1, 2], dtype=np.float))
# array([1.0, 2.0, 3.0])

flip() inverte l’ordine lungo l’asse indicato (ogni asse quando non viene passato alcun axis):

np.flip(np.array([1, 2, 3, 4]))
# array([4, 3, 2, 1])

roll() sposta ciclicamente gli elementi del conteggio indicato. Utile per implementare uno shift register in stile ring buffer:

np.roll(np.array([1, 2, 3, 4]), 1)
# array([4, 1, 2, 3])

take() è la forma esplicita del fancy indexing – seleziona elementi a indici arbitrari:

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

6.12.4. Filtraggio e modifiche strutturali

compress() è la forma esplicita dell’indicizzazione booleana – restituisce le porzioni di a selezionate dalla condizione booleana:

a = np.array([10, 20, 30, 40], dtype=np.uint8)
np.compress(a > 15, a)
# array([20, 30, 40], dtype=uint8)

delete() restituisce una copia con le voci agli indici indicati rimosse:

a = np.array([10, 20, 30, 40, 50], dtype=np.uint8)
np.delete(a, [1, 3])
# array([10, 30, 50], dtype=uint8)

diff() restituisce la differenza in avanti discreta n-esima dell’array lungo un asse. Usata per calcolare le variazioni del primo ordine tra campioni adiacenti:

samples = np.array([1, 3, 6, 10, 15], dtype=np.float)
np.diff(samples)
# array([2.0, 3.0, 4.0, 5.0])

6.12.5. Quanto costa ciascuna operazione

Quasi ogni funzione in questa pagina restituisce un array appena allocato. Due eccezioni:

  • sort() ordina in-place; la funzione libera sort() restituisce una copia ordinata.

  • take() accetta una parola chiave out= per scrivere in un buffer già esistente.

In un ciclo che viene eseguito molte volte al secondo, preferisci la versione in-place sort() e riutilizza ovunque buffer pre-allocati. Le maschere booleane stesse vengono allocate ogni volta che viene eseguito il confronto – costruisci una maschera una sola volta e riutilizzala tra le operazioni invece di ricostruirla a ogni iterazione.