Broadcasting: die Zahl, die Zeile und die Spalte
Kurz gesagt: Broadcasting ist die Regel, nach der NumPy mit Arrays unterschiedlicher Form rechnet: Eine Zahl, eine Zeile oder eine Spalte wird auf die Form der Tabelle gestreckt, wenn die Größen passen.
Problem
„Wie viel hat jedes Produkt in jeder Woche eingebracht?“
Der Chef gibt dir die verkauften Stück der letzten 4 Wochen für drei Produkte: Weißbrot, Frischmilch und Äpfel. Eine Tabelle mit 4 Zeilen (den Wochen) und 3 Spalten (den Produkten). Die Preise sind 1,20 €, 1,60 € und 2,10 €.
Er will den Umsatz in einer Tabelle derselben Art: Woche mal Produkt. Mit Python-Listen sind das zwei verschachtelte for-Schleifen, Zeile für Zeile und Zelle für Zelle.
In NumPy wirken Rechnungen auf das ganze Array. Hier gibt es aber zwei Arrays mit verschiedenen Formen: eine 4 × 3-Tabelle und eine Zeile mit 3 Preisen. Wie multiplizierst du das eine mit dem anderen?
Die Idee
Die Zahl und das Array
Fang mit dem einfachsten Fall an. Der Lieferant erhöht jeden Preis um 20 %. products['price'].to_numpy() liefert die Preisspalte als NumPy-Array, und multipliziert wird mit einer einzigen Zahl:
25 Preise mal eine Zahl ergeben 25 neue Preise: 1,20 € wird zu 1,44 €, und das Messerset für 39 € wird zu 46,80 €. Die Zahl wird auf die Form des Arrays „gestreckt“: als stünde neben jedem Preis eine 1.2.
Dieses Strecken heißt Broadcasting. Die Zahl ist nur der erste Fall. Dasselbe passiert mit einer Zeile und einer Tabelle.
Die Idee
Die Zeile und die Tabelle
Die Stückzahlen sind eine (4, 3)-Tabelle, die Preise eine (3,)-Zeile. Multipliziere sie direkt:
Die Zeile der Preise wird für jede der 4 Wochen wiederholt, und jede Zelle wird mit dem Preis ihrer Spalte multipliziert. Die erste Woche: 30 Brote zu 1,20 € sind 36 €, 22 Milch zu 1,60 € sind 35,20 €, 14 kg Äpfel zu 2,10 € sind 29,40 €. Kein einziges for.
Wann streckt NumPy, und wann hält es an? Es gibt eine Regel:
- Formen werden von rechts nach links verglichen. (4, 3) und (3,): zuerst 3 und 3, dann 4 und die leere Stelle.
- Die Größen müssen gleich sein, oder eine davon muss 1 sein. Eine leere Stelle zählt als 1.
- Die 1 wird auf die andere Größe gestreckt. Hier wird die (3,)-Zeile zu (1, 3) und auf (4, 3) gestreckt.
Genauer, für Neugierige
NumPy kopiert die Zeile nicht viermal im Speicher. Es liest dieselben 3 Preise für jede Zeile noch einmal. Deshalb ist Broadcasting schnell und schont den Speicher, auch wenn die Tabelle Millionen Zeilen hat.
Die Zahl aus der vorigen Karte ist ein Array der Form (), ohne Größen. Davor werden Einsen gesetzt, bis es (1,) oder (1, 1) ist, und dann wird es gestreckt. Die Regel ist dieselbe.
Sieh es dir an
Wie die Zeile in die Tabelle passt
Hier ist dieselbe Rechnung langsam: die 4 × 3-Tabelle, die Zeile mit 3 Preisen, das Strecken nach unten und die Multiplikation Zelle für Zelle.
import numpy as np
# Stück in 4 Wochen: Weißbrot, Frischmilch, Äpfelunits = np.array([[30, 22, 14], [26, 25, 18], [34, 20, 12], [28, 27, 20]])prices = np.array([1.2, 1.6, 2.1])
revenue = units * pricesprint(units.shape, prices.shape, revenue.shape)revenueSchritt für Schritt, in Worten
- units ist eine (4, 3)-Tabelle: die Stück für 4 Wochen, eine Spalte pro Produkt.
- prices ist eine (3,)-Zeile: 1,20 €, 1,60 €, 2,10 €. Ein Preis pro Produkt.
- Die Formen werden von rechts verglichen: 3 und 3 sind gleich. Links von (3,) steht nichts, das zählt als 1, und die Zeile wird zu (1, 3).
- Die 1 wird auf 4 gestreckt: Die Zeile mit den Preisen wiederholt sich für jede Woche und wird zu (4, 3).
- Jetzt Zelle für Zelle. Die erste Woche: 30 × 1,2 = 36, 22 × 1,6 = 35,2, 14 × 2,1 = 29,4.
- revenue ist (4, 3), wie units: 12 Umsätze aus einer Multiplikation, ohne ein einziges for.
Probier es aus
Passen die Formen?
Die Idee
Wenn Formen nicht passen
Jetzt fragt der Chef etwas anderes: welches Produkt in welcher Woche über dem Wochendurchschnitt verkauft hat. Du musst von jeder Zeile den Mittelwert genau dieser Zeile abziehen.
units.mean(axis=1) liefert die mittlere Stückzahl für jede Woche (axis kennst du aus der vorigen Lektion: die Größe, die verschwindet). Das sind 4 Zahlen. Ziehe sie von der Tabelle ab:
Die Mittelwerte sind 22, 23, 22 und 25, mit der Form (4,). NumPy vergleicht von rechts: 3 aus der Tabelle gegen 4 aus den Mittelwerten. Sie sind nicht gleich, und keine ist 1, also hält es an: operands could not be broadcast together with shapes (4,3) (4,).
Der Fehler ist nützlich. Eine Zeile aus 4 Zahlen kann wirklich nicht über 3 Spalten liegen.
Die Idee
Die Spalte und die Tabelle
Die 4 Mittelwerte müssen eine Spalte sein: eine Zahl pro Zeile, nach rechts über die Produkte gestreckt. Eine Spalte hat die Form (4, 1). Es gibt zwei Wege dorthin:
week_mean[:, None]:Nonean zweiter Stelle fügt eine Größe 1 hinzu;week_mean.reshape(4, 1): dasselbe, mit ausgeschriebener Form.
Rechts stehen jetzt 3 und 1 (die 1 wird auf 3 gestreckt), dann 4 und 4. Das Ergebnis ist eine (4, 3)-Tabelle: In der dritten Woche liegt das Brot 12 Stück über dem Durchschnitt, und die Äpfel liegen 10 darunter.
In der zweiten Woche war alles um 10 % reduziert, in der vierten um 20 %. discount enthält den Rabatt jeder Woche. Lege in after den Umsatz nach dem Rabatt ab, eine 4 × 3-Tabelle.
Lösung
import numpy as np
units = np.array([[30, 22, 14],
[26, 25, 18],
[34, 20, 12],
[28, 27, 20]])
prices = np.array([1.2, 1.6, 2.1])
revenue = units * prices
discount = np.array([0, 0.1, 0, 0.2])
after = revenue * (1 - discount[:, None])
afterafter = revenue * (1 - discount[:, None]). Der Rabatt gilt pro Woche, also muss er eine (4, 1)-Spalte sein. Ohne [:, None] vergleicht NumPy 3 mit 4 und löst einen Fehler aus. Die zweite Woche wird zu 28,08 €, 36 € und 34,02 €, die vierte zu 26,88 €, 34,56 € und 33,60 €. Wochen ohne Rabatt bleiben gleich. Mit revenue - discount[:, None] ziehst du von jeder Zelle 10 Cent ab, nicht 10 %.
Die Idee
Die stille Falle: 3 × 3 statt 3
Ein Fehler von NumPy fällt sofort auf. Gefährlicher ist es, wenn die Formen passen und etwas anderes herauskommt.
Ein Kollege schickt dir die neuen Preise, aus einer Spalte in Excel kopiert. Das Array ist eine (3, 1)-Spalte, keine (3,)-Zeile. Du ziehst die alten Preise ab, um zu sehen, wie viel sie gestiegen sind:
Du erwartest 3 Differenzen und bekommst 9. Die (3, 1)-Spalte wurde nach rechts gestreckt, die (3,)-Zeile nach unten, und jeder neue Preis wurde von jedem alten abgezogen. Nur die Diagonale (0.1, 0 und 0.2) ist die Differenz, die du willst. Die anderen Zahlen bedeuten nichts, und NumPy sagt nichts: Die Formen passen zur Regel.
Gib deshalb nach jeder Rechnung mit verschiedenen Formen .shape aus. Erwartest du (3,) und siehst (3, 3), hat sich etwas gestreckt, ohne dass du es verlangt hast.
Probier es aus
Wie viel sind die Preise gestiegen
new_prices ist eine (3, 1)-Spalte mit den neuen Preisen. Lege in change ab, um wie viel der Preis jedes Produkts gestiegen ist: 3 Zahlen, Form (3,).
Lösung
import numpy as np
prices = np.array([1.2, 1.6, 2.1])
new_prices = np.array([[1.3], [1.6], [2.3]])
change = new_prices.reshape(3) - prices
changechange = new_prices.reshape(3) - prices. reshape(3) macht aus der Spalte eine (3,)-Zeile, und Zeile minus Zeile ergibt 3 Differenzen: Brot steigt um 0,10 €, Milch bleibt bei 1,60 €, und Äpfel steigen um 0,20 €. new_prices - prices läuft ohne Fehler und ergibt eine 3 × 3-Tabelle: Das ist die Falle.
Übung
Wie viel mehr mit den neuen Preisen
Zurück zum Chef. Er will wissen, wie viel mehr jedes Produkt in jeder Woche eingebracht hätte, wenn die neuen Preise schon gegolten hätten.
units sind die Stück (4, 3), prices die alten Preise (3,), new_prices die neuen Preise als (3, 1)-Spalte. Lege in extra ab, um wie viel höher der Umsatz gewesen wäre: eine 4 × 3-Tabelle, Woche mal Produkt.
Lösung
import numpy as np
units = np.array([[30, 22, 14],
[26, 25, 18],
[34, 20, 12],
[28, 27, 20]])
prices = np.array([1.2, 1.6, 2.1])
new_prices = np.array([[1.3], [1.6], [2.3]])
extra = units * (new_prices.reshape(3) - prices)
extraextra = units * (new_prices.reshape(3) - prices). Die Preisdifferenz ist eine (3,)-Zeile und wird wie die Preise in der Szene über die 4 Wochen gestreckt. Die erste Woche ergibt 3 € beim Brot, 0 bei der Milch und 2,80 € bei den Äpfeln. Über alle vier Wochen zusammen sind es 24,60 €.
Mit units * (new_prices - prices) wird die Differenz zu einer 3 × 3-Tabelle (die stille Falle), und dann passt (4, 3) mal (3, 3) nicht mehr, und NumPy löst einen Fehler aus. Diesmal fängt sich die Falle selbst, aber das ist nicht immer so.
Test
Teste dich
Verwandte Lektionen
Abschnitt „Verwandte Lektionen“- Baut auf: Die erste Tabelle · Das NumPy-Array · Vektorisierung · Zusammenfassungen und fehlende Werte
- Geht weiter in: Ehrliche Diagramme · Stichprobe und Grundgesamtheit
- Aufgaben zum Thema: NumPy