6.10. Broadcasting#
Quando un operatore binario riceve due array le cui forme non corrispondono esattamente, numpy non solleva un errore – esegue il broadcasting. Il broadcasting è un piccolo insieme di regole che decidono se due forme sono compatibili e, in caso affermativo, come la più piccola viene virtualmente estesa per corrispondere alla più grande.
6.10.1. Le regole#
Quando i due operandi hanno forme A e B, numpy le elabora in due passaggi.
Allineare i ranghi. Se un operando ha meno assi dell’altro,
numpyriempie virtualmente la parte iniziale della sua forma con assi di dimensione 1 finché entrambe le forme non hanno lo stesso numero di assi. Un operando 1-D di forma(3,)accoppiato con un operando 2-D di forma(2, 3)diventa(1, 3)contro(2, 3).Verificare ogni asse. Percorrendo le forme ora di pari lunghezza asse per asse, ogni coppia di dimensioni deve soddisfare una di due condizioni: le dimensioni sono uguali, oppure una di esse è 1. Un asse di dimensione 1 viene virtualmente esteso alla dimensione dell’altro lato per l’operazione. La coppia
(1, 3)contro(2, 3)è compatibile perché il primo asse ha un 1 (si estende a 2) e il secondo asse corrisponde (3 == 3); il risultato ha forma(2, 3).
Se una qualsiasi coppia di assi non soddisfa nessuna delle due condizioni, le forme sono incompatibili e l’operatore solleva ValueError.
6.10.2. Esempi#
Scalare contro qualsiasi array. Lo scalare agisce come la forma (1,) e si estende a qualsiasi cosa:
a = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.float)
a + 10 # (2, 3) + scalar -> (2, 3)
Vettore 1-D su una matrice 2-D. La regola 1 antepone un asse di dimensione 1 per trasformare (3,) in (1, 3); la regola 2 estende quindi quella riga lungo ogni colonna di a
row = np.array([100, 200, 300], dtype=np.float)
a + row # (2, 3) + (3,) -> (2, 3)
Due array 1-D di pari lunghezza si sommano elemento per elemento – nessun broadcasting necessario:
np.arange(4) + np.arange(4)
Un vettore colonna contro un vettore riga produce una forma «esterna» 2-D: (4, 1) accoppiato con (3,) diventa (4, 1) contro (1, 3) dopo l’aggiunta di rango, e la regola 2 estende ciascun operando lungo il suo asse di dimensione 1:
x = np.array([1, 2, 3, 4]).reshape((4, 1)) # column
y = np.array([10, 20, 30]) # row
x + y # (4, 3) matrix
Le stesse regole sulle forme valgono per qualsiasi ufunc a due argomenti, inclusa arctan2()
np.arctan2(y, 1.0)
np.arctan2(y, x)
6.10.3. Cosa il broadcasting non alloca#
L’estensione è virtuale. numpy percorre entrambi gli operandi insieme, rileggendo il più piccolo lungo il suo asse di broadcast invece di copiarlo. I dati dell’array più corto non vengono mai replicati in memoria.
Ciò che conta per la memoria è la dimensione dell’array di output. a + row alloca un output della forma di a, non della forma di a più la forma di row. Lunghe catene di broadcasting possono comunque produrre grandi risultati intermedi.
6.10.4. Quando il broadcasting va storto#
Il classico fallimento sono due forme in cui nessuna ha un asse di dimensione 1 da estendere e le dimensioni non concordano – (3, 4) contro (4, 3), ad esempio. La regola 2 non può far corrispondere un 3 a un 4, quindi numpy solleva ValueError.
Un problema più sottile è un broadcasting che riesce, ma non nel modo voluto dall’applicazione. (5,) contro (5, 1) è il caso canonico: l’aggiunta di rango trasforma (5,) in (1, 5), che si combina in broadcast con (5, 1) per produrre una matrice (5, 5) – la combinazione esterna dei due vettori, non il risultato elemento per elemento di lunghezza 5 che l’applicazione probabilmente voleva. Nel dubbio, stampa shape su entrambi i lati e ripercorri le regole prima di ricorrere a reshape() o transpose().