© Der/die Autor(en), exklusiv lizenziert an Springer Fachmedien Wiesbaden GmbH, ein Teil von Springer Nature 2022
L. ClassenMit Jupyter durchs Physikpraktikumessentialshttps://doi.org/10.1007/978-3-658-37723-6_3

3. Rechnen mit Python: numpy

Lew Classen1  
(1)
Institut für Kernphysik, WWU Münster, Münster, Deutschland
 
 
Lew Classen

In diesem Kapitel lernen Sie ...

  • wie Sie Daten aus einer Textdatei einlesen,

  • was numpy-Arrays sind und wie man sie für die Arbeit mit Messreihen verwendet,

  • wie Sie numpy für Berechnungen nutzen,

  • wie Sie Daten in Textdateien speichern.

Stellen wir uns folgende Situation in einem hypothetischen Praktikum vor: Sie untersuchen in einem Experiment die Eigenschaften von idealen Gases. Dazu erwärmen Sie Luft in einem abgeschlossenen Behälter und notieren zu verschiedenen Zeitpunkten die aktuelle Temperatur (in ) und den Druck (in ). Sie haben also ein Messreihe p(T) vorliegen. Ihr Ziel ist es nun (unter der Annahme, dass sich Luft als ideales Gas verhält) z. B. den absoluten Temperaturnullpunkt auf der Celsius-Skala zu bestimmen. Dieses Szenario wird uns durch den Rest des Buches begleiten.

Daten
Die Messwerte Ihrer hypothetischen Messung haben Sie in zwei Spalten in einer Textdatei abgespeichert1. Die Spalten sind durch Tabulatoren getrennt. Üblich sind auch eine oder mehrere Kopfzeilen mit Kommentaren und erläuternden Informationen, die header genannt werden. Hier notiert man am besten die gemessenen Größen, ihre Einheiten sowie weitere Informationen für das Verständnis der Messung. In unserem Fall sieht die Datei so aus:
Hier markiert die Raute , in der üblichen Python-Konvention (siehe Abschn. 2.​2), die header-Zeilen als Kommentar. An dieser Stelle stehen auch die Messunsicherheiten unserer Geräte. Diese werden später in den Kap. 5 und 6 eine Rolle spielen.

3.1 Einlesen von Daten

Damit Sie mit den Daten arbeiten können, müssen Sie diese zunächst aus der Datei ins das Notebook einlesen. Das Einlesen von Daten ist keine grundlegende Funktion von Python. Diese Fähigkeit (neben vielen anderen) ist in spezialisierten Paketen oder Modulen enthalten. Um sie zu nutzen, muss das entsprechende Paket installiert sein und importiert werden. Das Python-Modul für diese Aufgabe heißt numpy. In Anaconda ist es standardmäßig vorinstalliert. Üblicherweise wird das Modul mit

importiert. Nun können Sie alle numpy-Funktionen (und Konstanten) über ein vorangestelltes np. aufrufen und verwenden. Hier kann Ihnen die Autovervollständigung, die wir in Abschn. 2.​2 kennen gelernt haben, wertvolle Dienste leisten: Drücken Sie nach dem Eintippen des Paketkürzels Tab um die Namen aller darin enthaltenen Funktionen zu sehen. Durch Anfangsbuchstaben nach np. wird die Auswahl eingeschränkt. Wenn Sie den Namen einer Funktion ungefähr kennen, können Sie so Tippfehler vermeiden.

Für das Einlesen von Daten verwenden wir die Funktion np.loadtxt(). Für den einfachsten Aufruf benötigen Sie nur den Namen Ihrer Datei, hier "temperatur_druck.txt", als Argument2:
Nach dem Ausführen der Code-Zelle in Ihrem Jupyter-Notebook erscheinen die eingelesen Daten direkt als Output auf dem Bildschirm, wie in Abb. 3.1 dargestellt. Außerdem werden Sie informiert, dass es sich bei den zurückgegebenen Werten um ein numpy-Array handelt. Das ist ein neuer Datentyp den das Modul numpy mitbringt. Den Output von np.loadtxt() können wir auch in einer Variable speichern:
Abb. 3.1

Output im Jupyter-Notebook nach dem Ausführen der Funktion np.loadtxt() in einer Code-Zelle

In diesem Fall erscheinen die eingelesenen Werte nicht mehr als Output. Wenn Sie den Inhalt von data sehen möchten, verwenden Sie print(data) oder einfach data. Standardmäßig liest np.loadtxt() eine Datei zeilenweise ein. Das ist allerdings nicht das von uns gewünschte Verhalten. Mit dem Parameter upack=True werden statt dessen Spalten eingelesen.

Aber wie erfährt man von diesen Parametern? Praktischerweise bringen alle Funktionen in Python und seinen Paketen eine Kurzdokumentation (den docstring) mit, die den Zweck der Funktion und ihre Synax erläutert. Insbesondere werden hier die wichtigsten Argumente erklärt. Oft enthalten die Anleitungen auch nützliche Code-Beispiele. Um die Kurzanleitung einer Funktion direkt im Notebook aufzurufen, platzieren Sie den Cursor in die Zeile mit dem Funktionsnamen und drücken Shift + Tab. Es öffnet sich ein Fenster, das anschließend mit Buttons vergrößert oder geschlossen werden kann. Alternativ finden Sie alle Dokumentionen auch durch eine einfache Online-Suche im Internet.

Mit zusätzlichen Parametern kann man das Verhalten von np.loadtxt() weiter anpassen: So können wir beispielsweise den Dezimaltrenner (Standard "."), den Spaltentrenner (Standardmäßig Leerzeichen oder Tabulatoren) und auch das Kommentarsymbol (Standard "#") selbst festlegen. Wir können die Funktion über den Parameter skiprows=n auch anweisen n Zeilen am Beginn der Datei zu ignorieren. Das bietet sich an, wenn der header der Datei nicht mit entsprechenden Symbolen auskommentiert ist.

Um den Code übersichtlicher und nachvollziehbarer zu machen können wir den Output von np.loadtxt() auch in mehreren Variablen ablegen. In unserem Fall, geben wir dazu einfach zwei Variablennamen, getrennt durch ein Komma, für den Output an und Python übernimmt den Rest:

Statt eines zweidimensionalen Arrays erhalten wir nun zwei eindimensionale Arrays mit aussagekräftigen Namen. Herzlichen Glückwunsch! Sie haben soeben erfolgreich Ihre ersten Daten in Python eingelesen.

Zum Abschluss noch ein komplizierteres Beispiel: Die Ergebnisse einer automatischen Magnetfeldmessung über einen bestimmten Zeitraum liegen in der Datei magnetfeld.csv in folgender Form vor:
Nun sind wir nur an den Messzeitpunkten (1. Spalte) und dem Betrag der magnetischen Flussdichte (5. Spalte) interessiert. Dazu übergeben Sie diese Spaltennummern über den Parameter usecols an die Funktion:

Wie generell in Python, beginnt auch hier die Zählung mit 0.

Arrays
Werfen wir nun einen genaueren Blick auf numpy-Arrays. Arrays können wir uns anschaulich als Vektoren oder Matrizen vorstellen. Ähnlich wie Listen in Python (siehe Abschn. 2.​2), bestehen Arrays aus mehreren Einträgen die man über einen Index (oder mehrere Indizes) ansprechen kann. So liefert z. B.
den ersten Eintrag des Temperaturarrays. Und auch das slicing, also die gleichzeitige Auswahl mehrerer Arrayeinträge, funktioniert genau so wie bei Listen in Abschn. 2.​2. Um den ersten (Index 0) bis dritten Eintrag (Index 2) des Arrays temperatur auszuwählen lauter der Befehl:
Hinter dem Stichwort conditional slicing verbirgt sich noch eine nützliche Besonderheit: Wir können einem Array statt der Indices auch eine Bedingung übergeben, die die gesuchten Einträge erfüllen müssen. Die Auswahl der Indices passiert dann im Hintergrund. So erhält man z. B. alle Temperaturwerte über durch den Befehl

Die Syntax ist dabei im Prinzip die gleiche geblieben.

Praktischerweise muss sich die Bedingung dabei nicht zwingend auf das Array beziehen, auf das sie angewandt wird. Wenn wir uns z. B. für die Drücke interessieren, die zu den obigen Temperaturen gehören – die also die Bedingung temperatur > 30 erfüllen – können wir das direkt so in die Bedingung schreiben

um den gesuchten Bereich zu erhalten. Damit das funktioniert müssen beide Arrays nur gleich lang sein.

Im nächsten Abschnitt wird es nun darum gehen weitere Features von numpy kennen zu lernen, die Sie für Ihre Berechnungen verwenden können. Wenn Sie Ihre Messreihe direkt in einem Diagramm darstellen möchten, können Sie zu Abschn. 4.​1 weiterblättern.

3.2 Berechnungen mit numpy

Inzwischen sind wir in der Lage Messdaten aus einer Textdatei einzulesen und in numpy-Arrays abzulegen. Das Paket numpy kann aber natürlich noch mehr.

Jupyter als Taschenrechner

Der Name numpy steht für numeric python und das Paket bietet Werkzeuge für numerisches Rechnen. Darunter sind viele wichtige mathematische Funktionen und auch vordefinierte Konstanten. Diese werden, wie wir es im letzen Abschnitt bei loadtxt() kennen gelernt haben, über den Paketnamen, oder über das Kürzel, das wir beim Import vergeben haben, aufgerufen. Einige Beispiele sind:

Im letzten Beispiel sieht man, dass die Sinusfunktion als Argument einen Winkel im Bogenmaß (Radiant) erwartet. Auch für die Umrechnung zwischen Grad und Radiant gibt es praktischerweise numpy-Funktionen:

Damit hat Ihr Jupyter-Notebook schon den gleichen Funktionsumfang wie ein wissenschaftlicher Taschenrechner, mit dem Vorteil, dass alle Ergebnisse und Rechenwege Ihnen beliebig lange zur Verfügung stehen.

Rechnen mit Arrays

Wir können aber nicht nur mit einzelnen Werten oder Variablen rechnen, sondern mit kompletten Arrays. Grundlegende mathematische Operationen (wie Addition, Subtraktion, Multiplikation und Division) werden dabei der Reihe nach auf alle Einträge von Arrays angewendet. Arrays werden also ähnlich wie Vektoren in der Physik behandelt. Schauen wir uns dieses Verhalten an Beispiel von zwei neuen Arrays a und b an, die wir mit folgendem Befehl angelen3:

Wie Sie in Abb. 3.2 sehen, werden die Arrays, Eintrag für Eintrag, miteinander verrechnet. Ganz analog zu Vektoren in der Physik lassen sich Arrays auch mit einem Skalar multiplizieren.
Abb. 3.2

Einfache Operationen mit numpy-Arrays

Genauso verhält es sich mit Arrays und numpy-Funktionen:

Auch hier werden die Funktionen auf alle Einträge des Arrays angewendet und Sie erhalten ein Array mit Ergebnissen (siehe Abb. 3.3).

Und auch für Ihre eigenen Funktionen (solange diese nur aus mathematischen Operatoren und numpy-Funktionen bestehen) gilt dieses Prinzip. Schauen wir uns als Beispiel eine Funktion an, die die magnetische Flussdichte B in die magnetische Feldstärke H umrechnet:

Wie man sieht, ist der Aufruf der Funktion identisch, egal ob es sich um einen einzelnen Wert oder ein Array handelt.
Abb. 3.3

Anwendung der numpy-Exponentialfunktion auf ein Array

Eine Übersicht aller mathematischen numpy-Funktionen finden Sie unter https://​numpy.​org/​doc/​stable/​reference/​routines.​math.​html#.

Praktische Array-Funktionen
Neben den oben erwähnten numpy-Funktionen mit der Syntax
denen man einzelne Werte und auch ganze Arrays übergeben kann, haben Arrays auch eingebaute Funktionen bzw. Methoden. Dieses Feature hatte wir schon in Abschn. 2.​2 bei Listen kennen gelernt. Array-Funktionen rufen Sie folgendermaßen auf:

Hier können Sie wieder die Autoergänzung nutzen: Drücken Sie, nachdem Sie den Arraynamen ausgeschrieben haben, Tab um eine Liste aller verfügbaren Array-Funktionen zu erhalten. Einige Beispiele für eingebaute Array-Funktion sind:

Hier finden Sie eine übersicht aller Array-Methoden https://​numpy.​org/​doc/​stable/​reference/​generated/​numpy.​ndarray.​html.

Viele Array-Funktionen gibt es auch als eigenständige numpy-Funktion. Welche Variante man verwendet ist Geschmackssache. So führen z. B. diese beiden Ausdrücke zum gleichen Ergebnis:

Solche Funktionen ermöglichen es Ihnen Eigenschaften einer Messreihe z. B. die Streuung einer statistischen Verteilung zu untersuchen wie in Abb. 3.4 dargestellt.
Abb. 3.4

Bestimmung statistischer Parameter der Messreihe B_tot mit Array-Methoden

Spezielle Arrays

Für manche Anwendungen, z. B. für die Darstellung von Funktionskurven, braucht man gleichmäßige Zahlenreihen. In numpy gibt es verschiedene Möglichkeiten solche Reihen zu erzeugen.

  • Mit np.arange(start, stop, step) erzeugen Sie eine Zahlenreihe, also ein numpy-Array, zwischen dem Anfangswert start und dem Endwert stop mit einer vorgegebenen Schrittweite step. Die Grundeinstellung der Schrittweite ist 1. Die festen Größen sind dabei der Startwert und die Schrittweite. Als tatsächlicher Endwert wird der, im Vergleich zu stop, nächstkleinere Wert zurückgegeben, der mit der Schrittweite erreichbar ist. Die Anzahl der Werte ergibt sich aus den Parametern.

  • Auch np.linspace(start, stop, num) erzeugt eine Zahlenreihe. Allerdings sind die Parameter hier, neben Anfangs- und Endwert, die Anzahl der Werte num. Die Schrittweite des Arrays ergibt sich aus den expliziten Parametern.

Benutzt werden diese beiden Arrays unter anderem als x-Werte um den Verlauf von Funktionen graphisch darzustellen, wie wir in Abschn. 4.​1 sehen werden.

Darüber hinaus gibt es eine ganze Reihe weiterer Funktionen4 die spezielle vordefinierte Arrays erzeugen. Darunter auch – auf den ersten Blick – so ausgefallene wie

Das erzeugte Array hat die gleiche Länge wie Arrayname, besteht aber nur aus Einsen. Es wird uns in Kap. 6 nochmal begegnen.

Eine Übersicht aller Arrays erzeugenden Funktionen finden Sie unter https://​numpy.​org/​doc/​stable/​reference/​routines.​array-creation.​html

Slicing für Fortgeschrittene
Im vorherigen Abschnitt (Abschn. 3.1) hatten wir conditional slicing, die Auswahl von Arrayeinträgen, die eine Bedingung erfüllen, kennen gelernt. Nun ist es auch möglich mehrere Bedingungen zu kombinieren. Das geschieht, indem man zwischen den Bedingungen Verkettungsoperatoren platziert. Die wichtigsten dieser Operatoren sind:
  • & – Die Einträge erfüllen die linke und die rechte Bedingung (AND).

  • | – Die Einträge erfüllen die linke oder die rechte Bedingung, oder beide gleichzeitig (OR).

  • ^ – Die Einträge erfüllen nur die linke oder nur die rechte Bedingung, aber nicht beide gleichzeitig (exclusive OR, XOR).

Dabei lassen sich Bedingungen und Funktionen beliebig verschachteln. Der folgende Code

gibt Ihnen z. B. alle Werte der der magnetischen Flussdichte, die sich um weniger als eine Standardabweichung vom Mittelwert der Messreihe unterscheiden. Anwendungen für conditional slicing sind beispielsweise die Untersuchung von Messreihen oder die graphische Hervorhebung bestimmter Wertebereiche in Diagrammen.

3.3 Speichern von Daten

Sie können Arrays (z. B. die Ergebnisse Ihrer Berechnungen oder Stützstellen einer Funktion) auch in Textdateien exportieren. Die entsprechende Funktion heißt np.savetxt(). Haben wir zwei Arrays mit x- und y-Werten vorliegen, lautet der Befehl um sie als zwei Spalten in einer Textdatei abzulegen

Die Funktion np.column_stack() sorgt in diesem Beispiel dafür, dass die beiden Arrays als Spalten in die Datei geschrieben werden, da np.savetxt() standardmäßig mit Zeilen arbeitet, wie wir es schon von np.loadtxt() kennen (Abschn. 3.1). Mit dem Parameter header="..." stellen wir den Spalten einen auskommentierten header voran.

Standardmäßig werden die Werte mit der vollen verfügbaren Genauigkeit ausgegeben, was die Datei für Menschen nicht besonders gut lesbar macht. Dieses Verhalten lässt sich über das Argument fmt ändern. Der folgende Code speichert z. B. zwei Spalten mit jeweils drei Nachkommastellen5:

Die Python-Formatsyntax bietet Ihnen sehr viele Möglichkeiten das Zielformat Ihrer Werte Ihren Wünschen entsprechend anzupassen. Ausführliche Informationen dazu finden Sie unter https://​pyformat.​info/​.