Zum Inhalt
wannacode

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:

PythonDaten: die Tabellen des Shops

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:

PythonDaten: die Tabellen des Shops

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, Äpfel
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
print(units.shape, prices.shape, revenue.shape)
revenue
Schritt für Schritt, in Worten
  1. units ist eine (4, 3)-Tabelle: die Stück für 4 Wochen, eine Spalte pro Produkt.
  2. prices ist eine (3,)-Zeile: 1,20 €, 1,60 €, 2,10 €. Ein Preis pro Produkt.
  3. 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).
  4. Die 1 wird auf 4 gestreckt: Die Zeile mit den Preisen wiederholt sich für jede Woche und wird zu (4, 3).
  5. Jetzt Zelle für Zelle. Die erste Woche: 30 × 1,2 = 36, 22 × 1,6 = 35,2, 14 × 2,1 = 29,4.
  6. revenue ist (4, 3), wie units: 12 Umsätze aus einer Multiplikation, ohne ein einziges for.

Probier es aus

Passen die Formen?

1. units hat die Form (4, 3) und prices die Form (3,). Welche Form hat units * prices?
2. units hat die Form (4, 3) und week_bonus die Form (4,). Was liefert units + week_bonus?
3. Welches Paar von Formen passt?

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:

PythonDaten: die Tabellen des Shops

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]: None an zweiter Stelle fügt eine Größe 1 hinzu;
  • week_mean.reshape(4, 1): dasselbe, mit ausgeschriebener Form.
PythonDaten: die Tabellen des Shops

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.

PythonDaten: die Tabellen des Shops

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:

PythonDaten: die Tabellen des Shops

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

PythonDaten: die Tabellen des Shops

Ü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.

PythonDaten: die Tabellen des Shops

Test

Teste dich

1. Was macht prices * 1.2, wenn prices ein Array aus 25 Preisen ist?
2. week_mean hat die Form (4,) und units die Form (4, 3). Wie ziehst du den Mittelwert jeder Woche von dieser Woche ab?
3. old hat die Form (3,) und new die Form (3, 1). Was liefert new - old?
4. Wie fängst du am schnellsten ein stilles Strecken zu einer 3 × 3-Tabelle ab?

Erstellt von Ivo Gergov im Rahmen der Kampagne von Evo Tech für zugängliche Programmierausbildung ·