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().