Broadcasting in NumPy
Broadcasting ist NumPys Regel zum Verknüpfen von Arrays mit unterschiedlichen, aber kompatiblen Shapes. Eine Dimension der Größe eins wird so behandelt, als hätte sie dieselbe Größe wie die entsprechende Dimension des anderen Arrays, ohne die Daten in ein größeres Array zu kopieren.
Bei der Vektorisierung wirkt ein Ausdruck auf ein ganzes Array. NumPy führt die Schleife im Hintergrund als optimierten, kompilierten Code aus. Das ist oft schneller als eine ausdrücklich geschriebene Python-Schleife.
import numpy as np
# Zwei Arrays erstellen
a = np.array([0, 1, 2])
b = np.array([2, 2, 2])
# Vektorisierungsoperation (elementweise)
result = a + b
print(result)
# Ausgabe: [2 3 4]Im Beispiel addiert NumPy die Elemente an den jeweils entsprechenden Positionen. Wir formulieren die Operation einmal, statt jedes Element in einer Python-Schleife einzeln zu indizieren.
Regeln für Broadcasting
Zwei Arrays lassen sich ohne ausdrückliche Schleife verbinden, wenn NumPy ihre Dimensionen selbst ausrichten kann. Um zu entscheiden, ob zwei Shapes kompatibel sind, vergleicht NumPy ihre Dimensionen von rechts nach links und stoppt beim ersten Paar, das sich nicht ausrichten lässt.
Drei Regeln decken jeden Fall ab. Die Tabelle unten nennt für jede Regel die geprüfte Bedingung und das Ergebnis, das NumPy dafür liefert.
| Regel | Bedingung an die Shapes | Was NumPy tut |
|---|---|---|
| Regel | Die beiden Shapes haben eine unterschiedliche Anzahl an Dimensionen | NumPy ergänzt links Dimensionen der Größe beim kürzeren Shape |
| Regel | Ein ausgerichtetes Paar hat gleich große Seiten, oder eine Seite hat die Größe | Gleiche Größen passen bereits. Eine Dimension der Größe eins wird gedanklich wiederverwendet, ohne wiederholte Kopien anzulegen |
| Regel | Ein ausgerichtetes Paar hat unterschiedliche Größen und keine Seite hat die Größe | NumPy löst einen ValueError aus |
Array mit Skalar
Broadcasting gilt auch zwischen einem Array und einer einzelnen Zahl. NumPy dehnt den Skalar auf jedes Element aus, deshalb brauchst du keine Schleife.
import numpy as np
# 1D-Array mit Skalar
a = np.arange(3) # [0, 1, 2]
b = 5
result = a + b
print(f"Array a: {a}")
print(f"Skalar b: {b}")
print(f"Ergebnis a + b: {result}")
# Erklärung der Formen:
# a hat die Form (3,)
# b hat die Form () - Skalar
# Nach dem Broadcasting wird b zu [5, 5, 5]
# Ausgabe: [5 6 7]Zweidimensionales Array mit eindimensionalem Array
Bei Arrays mit unterschiedlicher Dimensionszahl prüft NumPy die Shapes anhand dieser Regeln. So lässt sich beispielsweise dieselbe Operation auf jede Zeile oder jede Spalte einer Matrix anwenden.
import numpy as np
# 2D-Array mit 1D-Array
a = np.ones((3, 3)) # 3x3-Matrix mit Einsen
b = np.arange(3) # [0, 1, 2]
result = a + b
print("Array a (3x3):")
print(a)
print(f"Array b (1D): {b}")
print("Ergebnis a + b:")
print(result)
# Broadcasting findet statt:
# a: Form (3, 3)
# b: Form (3,) -> erweitert zu (1, 3) -> (3, 3)
# b wird zu jeder Zeile von a hinzugefügtFall mit fehlgeschlagenem Broadcasting
Bei den Shapes (2, 3) und (2,) passen die letzten Dimensionen nicht: Ihre Größen sind 3 und 2, und keine ist 1. NumPy löst vor der Addition einen ValueError aus.
import numpy as np
try:
# Arrays mit inkompatiblen Formen
a = np.arange(6).reshape(2, 3) # Form (2, 3)
b = np.arange(2) # Form (2,)
print(f"Form von Array a: {a.shape}")
print(f"Form von Array b: {b.shape}")
# Dies führt zu einem Fehler
result = a + b
except ValueError as e:
print(f"Error: {e}")
print("Die Array-Formen sind für Broadcasting nicht kompatibel")Arithmetische Operationen mit Arrays
Arithmetische NumPy-Operatoren arbeiten elementweise, sofern eine Operation nicht ausdrücklich eine andere Bedeutung besitzt, etwa die Matrixmultiplikation.
Wird ein Array mit einem Skalar verknüpft, wendet Broadcasting den Skalar auf jedes Element an. Eine manuelle Indexschleife ist dafür nicht nötig.
import numpy as np
a = np.array([0, 1, 2, 3, 4])
# Addition mit Skalar
print("Addition:")
print(f"a + 1 = {a + 1}")
# Ausgabe: [1 2 3 4 5]
# Multiplikation mit Skalar
print("Multiplikation:")
a *= 2
print(f"a *= 2: {a}")
# Ausgabe: [0 2 4 6 8]
# Potenz
print("Potenz:")
print(f"2**a = {2**a}")
# Ausgabe: [ 1 4 16 64 256]Operationen zwischen Arrays
import numpy as np
a = np.array([0, 1, 2, 3, 4])
b = np.array([4, 3, 2, 1, 0])
# Elementweise Subtraktion
print("Subtraktion:")
print(f"a - b = {a - b}")
# Ausgabe: [-4 -2 0 2 4]
# Elementweise Multiplikation
print("Elementweise Multiplikation:")
print(f"a * b = {a * b}")
# Ausgabe: [0 3 4 3 0]
# Matrixmultiplikation (Skalarprodukt)
print("Matrixmultiplikation:")
print(f"a @ b = {a @ b}")
# Ausgabe: 10Der Operator * multipliziert Elemente an entsprechenden Positionen. Für die beiden eindimensionalen Arrays oben liefert @ das Skalarprodukt: 0 + 3 + 4 + 3 + 0 ergibt 10. Bei zwei Matrizen gilt die Zeile-mal-Spalte-Regel. Setze np.dot() nicht für alle Array-Formen mit @ gleich. Bei höheren Dimensionen unterscheiden sich ihre Regeln.
Vergleich und Logik
Vergleiche erzeugen boolesche Arrays. Sie können anschließend zum Filtern von Daten oder zum Verknüpfen mehrerer Bedingungen verwendet werden.
import numpy as np
a = np.array([0, 1, 2, 3, 4])
b = np.array([0, 0, 2, 4, 4])
# Vergleichsoperationen
print("Größer-als-Vergleich:")
print(f"a > 2: {a > 2}")
# Ausgabe: [False False False True True]
print("Gleichheitsvergleich:")
print(f"a == b: {a == b}")
# Ausgabe: [ True False True False True]
# Logische Operationen
print("Logische ODER-Verknüpfung:")
print(f"(a > 2) | (a == b): {(a > 2) | (a == b)}")
# Ausgabe: [ True False True True True]Für elementweise boolesche Logik verwendest du ~ für NOT, & für AND und | für OR oder die entsprechenden np.logical_*-Funktionen. Setze jeden Vergleich in Klammern, weil die Operatoren eine andere Priorität als Vergleiche haben. Die skalaren Python-Operatoren not, and und or führen keine elementweise Array-Logik aus.
Statistische Funktionen und Reduktionen
Reduktionsfunktionen fassen viele Elemente zu weniger Werten zusammen. Ohne Angabe einer Achse können sie das gesamte Array auf einen Wert reduzieren. Mit einer Achse reduzieren sie nur diese Dimension. Bei einer Tabelle mit Prüfungsergebnissen kann so etwa ein Mittelwert pro Fach oder pro Person entstehen.
NumPy stellt dafür statistische Funktionen bereit, die entweder auf dem gesamten Array oder entlang ausgewählter Achsen arbeiten.
import numpy as np
# 2D-Beispielarray erstellen
data = np.array([[3, 0, -1, 1],
[2, -1, -2, 4],
[1, 7, 0, 4]])
print("Data array:")
print(data)
# Statistiken zum gesamten Array
print(f"Gesamtsumme: {np.sum(data)}")
print(f"Mittelwert: {np.mean(data):.2f}")
print(f"Kleinster Wert: {np.min(data)}")
print(f"Maximalwert: {np.max(data)}")
print(f"Standardabweichung: {np.std(data):.2f}")
# Ausgabe:
# Gesamtsumme: 18
# Mittelwert: 1.50
# Kleinster Wert: -2
# Maximalwert: 7
# Standardabweichung: 2.50Operationen mit Achsen
Bei einem zweidimensionalen Array reduziert axis=0 die Zeilendimension und liefert einen Wert pro Spalte. axis=1 reduziert die Spaltendimension und liefert einen Wert pro Zeile.
Die Achse legt fest, über welche Richtung eine statistische Funktion rechnet. Bei monatlichen Verkaufsdaten für mehrere Produkte kannst du damit zum Beispiel den Gesamtumsatz pro Produkt oder pro Monat berechnen.
import numpy as np
data = np.array([[3, 0, -1, 1],
[2, -1, -2, 4],
[1, 7, 0, 4]])
# Operationen entlang der Achse=0 (für jede Spalte)
print("Maximum jeder Spalte (Achse=0):")
print(f"max(axis=0): {np.max(data, axis=0)}")
# Ausgabe: [3 7 0 4]
print("Index des Maximums in jeder Spalte:")
print(f"argmax(axis=0): {np.argmax(data, axis=0)}")
# Ausgabe: [0 2 2 1]
# Operationen entlang der Achse=1 (für jede Zeile)
print("Maximum jeder Zeile (Achse=1):")
print(f"max(axis=1): {np.max(data, axis=1)}")
# Ausgabe: [3 4 7]
print("Index des Maximums in jeder Zeile:")
print(f"argmax(axis=1): {np.argmax(data, axis=1)}")
# Ausgabe: [0 3 1]Bearbeitung der Form von Arrays
Shape-Operationen verändern, wie dieselben Elemente indiziert werden. Ein Reshape behält die Anzahl der Elemente bei, ordnet sie aber anderen Dimensionen zu.
Neu erzeugte NumPy-Arrays sind häufig C-kontiguierlich. Dann ändert sich der letzte Index im Speicher am schnellsten. Views aus Slices oder Transpositionen können jedoch andere Strides besitzen, sodass Arrays nicht immer in einem einzigen einfachen Zeilenlayout gespeichert sind. Beim Reshape oder Flatten spielen deshalb die Regeln für order und Strides eine Rolle.
import numpy as np
# 2D-Array erstellen
a = np.array([[0, 1], [2, 3]])
print("2D-Array:")
print(a)
print(f"Form: {a.shape}")
# Sieh, wie es im Speicher gespeichert wird
print(f"Im Speicher gespeichert als: {a.ravel()}")
# Ausgabe: [0 1 2 3]Flatten und Ravel
flatten() und ravel() liefern beide ein eindimensionales Array. flatten() erzeugt immer eine Kopie. ravel() liefert wenn möglich eine View und erstellt eine Kopie, wenn das Speicherlayout es erfordert.
import numpy as np
# Diagonales Array erstellen
a = np.diag([1, 2, 3])
print("Diagonales Array:")
print(a)
# flatten - erstellt eine unabhängige Kopie
b_flatten = a.flatten()
print(f"Ergebnis von flatten: {b_flatten}")
# Das Ändern der Flatten-Werte hat keinen Einfluss auf das ursprüngliche Array
b_flatten[0] = 9
print(f"Nach Änderung der flatten-Kopie: {b_flatten}")
print("Das ursprüngliche Array bleibt gleich:")
print(a)
print()
# ravel - versucht eine View zu erstellen statt Daten immer zu kopieren
b_ravel = a.ravel()
print(f"Ravel-Ergebnis: {b_ravel}")
# Das Ändern der Ravel-Werte wirkt sich auf das ursprüngliche Array aus
b_ravel[0] = 9
print(f"Nach Änderung der ravel-Ansicht: {b_ravel}")
print("Ursprüngliches Array geändert:")
print(a)Umformen und Größe ändern
reshape() ändert den Shape, solange die Anzahl der Elemente gleich bleibt, und liefert wenn möglich eine View. Die unten verwendete Methode ndarray.resize() verändert dagegen das ursprüngliche Array direkt.
import numpy as np
# Diagonales Array erstellen und mit flatten abflachen
a = np.diag([1, 2, 3])
a_flat = a.flatten()
print(f"Flaches Array: {a_flat}")
# reshape - ändert die Form bei gleicher Elementanzahl
b = a_flat.reshape(3, 3)
print("Ergebnis von reshape(3, 3):")
print(b)
# Werte in der reshape-Ansicht ändern
b[0, 0] = 9
print("Nach Wertänderung:")
print(b)
print(f"Ursprüngliches flaches Array: {a_flat}") # Ändert sich, weil reshape eine Ansicht erstellt
# resize - ändert die Form direkt und gibt keinen Wert zurück
a_flat.resize(3, 3)
print("Nach der Größenänderung:")
print(a_flat) # Jetzt ist a_flat 2DTransponieren
Eine Transposition vertauscht die Reihenfolge der Array-Achsen. Bei einem zweidimensionalen Array werden Zeilen und Spalten getauscht. NumPy bietet dafür die Methode transpose() und das kürzere Attribut .T.
import numpy as np
# 2x4-Array erstellen
a = np.linspace(1, 8, 8).reshape(2, 4)
print("Ursprüngliches Array (2x4):")
print(a)
# Mit Methode transponieren
b = a.transpose()
print("Transponierungsergebnis (4x2):")
print(b)
# Mit .T-Attribut transponieren (kürzer)
c = a.T
print("Verwendung von .T:")
print(c)
# Prüfen, ob die Transponierung eine Ansicht ist
print("Ist die Transponierung eine Ansicht?", np.shares_memory(a, b))Datenstandardisierung mit Z Scores
Die Z-Standardisierung zentriert jedes Merkmal auf den Mittelwert und skaliert es auf die Standardabweichung . Sie ist nur für Merkmale mit einer von null verschiedenen Standardabweichung definiert und bringt Merkmale auf vergleichbare numerische Skalen.
Die Formel lautet . Dabei gilt:
- ist die Merkmalsmatrix der Größe
- ist die Anzahl der Beobachtungen, also der Zeilen
- ist die Anzahl der Merkmale, also der Spalten
- ist der Mittelwertvektor der Spalten
- ist der Vektor ihrer Standardabweichungen
import numpy as np
# Beispieldaten erstellen: fünf Beobachtungen mit je drei Merkmalen
rng = np.random.default_rng(42)
X = rng.standard_normal((5, 3)) * 10 + 50 # Stichprobe aus einer Normalverteilung mit Mittelwert 50 und Standardabweichung 10
print("Originaldaten:")
print(X)
print(f"Datenform: {X.shape}")
# Mittelwert und Standardabweichung für jede Spalte berechnen
mu = np.mean(X, axis=0) # Mittelwert jeder Spalte
sigma = np.std(X, axis=0) # Standardabweichung jeder Spalte
print(f"Mittelwert jedes Merkmals: {mu}")
print(f"Standardabweichung jedes Merkmals: {sigma}")
# Z-Standardisierung durchführen
Z = (X - mu) / sigma
print("Daten nach Z-Standardisierung:")
print(Z)
# Ergebnis der Standardisierung prüfen
print("Überprüfung der Standardisierung:")
print(f"Neuer Mittelwert: {np.mean(Z, axis=0)}") # Sollte in der Nähe von 0 liegen
print(f"Neue Standardabweichung: {np.std(Z, axis=0)}") # Sollte in der Nähe von 1 liegen
# Neue Standardabweichungen: [1. 1. 1.]Die Standardisierung verhindert, dass allein unterschiedliche Einheiten große Skalenunterschiede verursachen. Sie garantiert weder gleiche Bedeutung noch gleichen Einfluss, denn beides hängt weiterhin vom Modell und von den Daten ab. Körpergröße in Zentimetern und Gewicht in Kilogramm werden nach der Standardisierung beispielsweise numerisch vergleichbar.