Python Listen sind sehr flexibel, da sie Werte unterschiedlicher Datentypen beinhalten können und einfach verändert werden können (bspw. mit append
). Diese Flexibilität geht jedoch auf Kosten der Performance, sodass Listen für numerische Berechnungen nicht ideal sind.
Das Numpy Modul definiert daher den n-dimensionalen Array Datentyp numpy.ndarray
, der für numerische Berechnungen auf höchst performanten C und Fortran Code zurückgreift.
Arrays können nur Werte eines einzelnen numerischen Datentyps (bspw. floating point Werte) enthalten und sind sehr viel starrer als Listen. Dies ist jedoch für viele wissenschaftliche Anwendung, wie die Arbeit mit Datensätzen, genau was wir brauchen!
Wir importieren das Numpy Modul per Konvention unter der Abkürzung np
:
import numpy as np # Das Numpy Modul wird per Konvention als `np` abgekürzt
Am einfachsten erstellen wir Numpy Arrays aus Python Listen, indem wir die numpy.array
Funktion verwenden:
a = np.array([ 1, 2, 3, 5, 8, 13 ])
a
array([ 1, 2, 3, 5, 8, 13])
b = np.array([ [ 1.5, 2.2, 3.1 ], [ 4.0, 5.2, 6.7 ] ])
b
array([[ 1.5, 2.2, 3.1], [ 4. , 5.2, 6.7]])
Numpy Arrays haben einige Attribute, die hilfreiche Informationen über das Array geben:
a.ndim, b.ndim # Die Zahl der Dimensionen des Arrays
(1, 2)
a.shape, b.shape # Die Länge des Arrays in jeder Dimension
((6,), (2, 3))
a.dtype, b.dtype # Der Datentyp des Arrays
(dtype('int64'), dtype('float64'))
Erinnerung: Verwendet die
<TAB>
-Autovervollständigung und die?
-Dokumentation im Jupyter Notebook wenn ihr nicht wisst, welche Funktionen es gibt oder was diese bewirken!
numpy.arange
Funktion arbeitet ähnlich wie Python's range
Funktion, kann jedoch auch floating-point Argumente annehmen:np.arange(10)
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
np.arange(1.5, 2, 0.1)
array([ 1.5, 1.6, 1.7, 1.8, 1.9])
numpy.linspace
und numpy.logspace
, welche eine Anzahl von Werten in linearem oder logarithmischem Abstand zwischen zwei Zahlen generiert:np.linspace(10, 20, 4)
array([ 10. , 13.33333333, 16.66666667, 20. ])
np.logspace(1, 3, 4)
array([ 10. , 46.41588834, 215.443469 , 1000. ])
numpy.zeros
und numpy.ones
Arrays erstellen, die mit Nullen oder Einsen gefüllt sind. Indem wir dem Argument shape
dieser Funktionen statt einem Integer einen Tupel übergeben, können wir auch mehrdimensionale Arrays erzeugen:np.zeros(5)
array([ 0., 0., 0., 0., 0.])
np.ones((5, 2))
array([[ 1., 1.], [ 1., 1.], [ 1., 1.], [ 1., 1.], [ 1., 1.]])
a) Erstelle ein Array a
, das 11 Werte zwischen $10^{-20}$ und $10^{-10}$ in logarithmischem Abstand enthält.
a = np.logspace(-20, -10, 11)
from numpy.testing import assert_array_equal
try:
a
except NameError:
raise NameError("Es gibt keine Variable 'a'. Weise das Array einer Variablen mit diesem Namen zu.")
assert_array_equal(a, [1.00000000e-20, 1.00000000e-19, 1.00000000e-18, 1.00000000e-17, 1.00000000e-16, 1.00000000e-15, 1.00000000e-14, 1.00000000e-13, 1.00000000e-12, 1.00000000e-11, 1.00000000e-10])
b) Erstelle ein Array b
, das 10-mal den Wert 2
enthält.
Hinweis: Schaue, ob numpy
eine passende Funktion bereitstellt.
b = np.repeat(2, 10)
from numpy.testing import assert_array_equal
try:
b
except NameError:
raise NameError("Es gibt keine Variable 'b'. Weise das Array einer Variablen mit diesem Namen zu.")
assert_array_equal(b, [2, 2, 2, 2, 2, 2, 2, 2, 2, 2])
c) Erstelle ein Array c
, das der Einheitsmatrix in 3 Dimensionen entspricht.
Hinweis: Auch hier stellt numpy
bereits eine passende Funktion bereit.
c = np.identity(3)
from numpy.testing import assert_array_equal
try:
c
except NameError:
raise NameError("Es gibt keine Variable 'c'. Weise das Array einer Variablen mit diesem Namen zu.")
assert_array_equal(c, [[1,0,0],[0,1,0],[0,0,1]])
Arrays können mit den Standardoperatoren +-*/**
elementweise kombiniert werden:
x = np.array([1,2,3])
y = np.array([4,5,6])
x + 2 * y
array([ 9, 12, 15])
x ** y
array([ 1, 32, 729])
Achtung: Für Python-Listen sind diese Operatoren völlig anders definiert!
Wir können alle Funktionen auf Numpy Arrays anwenden, die für Reihen definiert sind:
a = np.arange(3)
len(a)
3
for x in a:
print(x)
0 1 2
a[0]
0
Die Slicing Syntax von Reihen haben wir schon kennengelernt. Sie erlaubt uns, auf einzelne Elemente oder Teile einer Reihe zuzugreifen:
a[start:stop:step]
Numpy erweitert diese Syntax auf mehrdimensionale Arrays:
b[start:stop:step, start:stop:step]
x = np.arange(10)
x[:5]
array([0, 1, 2, 3, 4])
x[::2]
array([0, 2, 4, 6, 8])
Alternativ können wir statt einem Index auch eine Liste von Indizes in das Subskript schreiben und erhalten die zugehörigen Elemente aus dem Array:
x = np.array([ 1, 6, 4, 7, 9 ])
indices = [ 1, 0, 2, 1 ]
x[indices]
array([6, 1, 4, 6])
Außerdem erweitert Numpy diese Syntax um die Masking Funktionalität. Dabei geben wir im Subskript ein Array von Booleans an, welches die gleiche Länge hat, und erhalten nur die Elemente, für die wir True
angegeben haben:
x = np.array([ 1, 6, 4, 7, 9 ])
mask = np.array([ True, True, False, False, True ])
x[mask]
array([1, 6, 9])
Masking ist deshalb äußerst praktisch, weil die Vergleichsoperatoren in Kombination mit Numpy Arrays wiederum Boolean Arrays zurückgeben:
x > 4
array([False, True, False, True, True], dtype=bool)
Somit können wir Teile eines Arrays herausfiltern, die einer Bedingung entsprechen:
x[x > 4]
array([6, 7, 9])
Bedingungen werden mit dem &
Operator kombiniert:
x[(x > 4) & (x < 8)]
array([6, 7])
Solange die Boolean-Maske die gleiche Länge wie das Array hat, kann es natürlich ein beliebiges Array sein. So können wir bspw. von zwei Arrays nur solche Werte behalten, die in einem Bereich bezüglich einer der beiden Arrays liegen:
x = np.linspace(-1, 1, 11)
y = np.arange(11)
mask = x**2 < 0.5**2
print(x)
print(y)
print(mask)
[-1. -0.8 -0.6 -0.4 -0.2 0. 0.2 0.4 0.6 0.8 1. ] [ 0 1 2 3 4 5 6 7 8 9 10] [False False False True True True True True False False False]
x[mask]
array([-0.4, -0.2, 0. , 0.2, 0.4])
y[mask]
array([3, 4, 5, 6, 7])
Wenn ein Slice oder eine Maske eines Arrays auf der linken Seite einer Zuweisung steht, wird diesem Teil des Original-Arrays zugewiesen:
x = np.array([ 1, 6, 4, 7, 9 ])
x[x > 4] = 0
x
array([1, 0, 4, 0, 0])
Gegeben ein Array x
der Länge n
, berechne das Array dx
der Länge n-1
mit den Werten dx[i] = x[i+1] - x[i]
. Verwende keine Schleifen sondern Slicing!
Hinweis: Du musst zwei Arrays subtrahieren, von denen das eine der um 1 versetzte hintere und das andere der vordere Teil von x
ist.
Erinnerung: Mit negativen Zahlen im Subskript wählst du Indizes vom Ende einer Reihe aus.
x = np.array([ 1, 1, 2, 3, 5, 8 ])
### BEGIN SOLUTION
dx = x[1:] - x[:-1]
### END SOLUTION
from numpy.testing import assert_array_equal
try:
dx
except NameError:
raise NameError("Es gibt keine Variable 'dx'. Weise das Array einer Variablen mit diesem Namen zu.")
assert_array_equal(dx, [0, 1, 1, 2, 3])
Während Funktionen aus dem math
Modul wie sin
oder exp
auf Zahlen anwendbar sind, sind die gleichnamigen Funktionen aus dem numpy
Modul auf Arrays anwendbar. Die Funktion wird auf alle Element des Arrays angewendet und ist typischerweise um einiges schneller als jedes Element einzeln zu berechnen:
phi = np.linspace(0, 2*np.pi, 10) # 10 Werte zwischen 0 und 2π
np.sin(phi) # Der Sinus jedes dieser Werte
array([ 0.00000000e+00, 6.42787610e-01, 9.84807753e-01, 8.66025404e-01, 3.42020143e-01, -3.42020143e-01, -8.66025404e-01, -9.84807753e-01, -6.42787610e-01, -2.44929360e-16])
Außerdem gibt es viele Funktionen, die Eigenschaften eines Arrays berechnen:
x = np.linspace(0, 10, 100)
np.sum(x), np.mean(x), np.std(x)
(500.0, 5.0, 2.9157646512850626)
Diese Funktionen generalisieren auf mehrere Dimensionen, indem die Achse angegeben wird, auf der die Berechnung durchgeführt werden soll:
x = np.array([ [ 1, 2 ], [ 3, 4 ] ])
np.sum(x), np.sum(x, axis=0), np.sum(x, axis=1)
(10, array([4, 6]), array([3, 7]))
Mit der numpy.loadtxt
Funktion können wir Daten aus einer Datei als Numpy Array einlesen:
data = np.loadtxt('data/temperatures.txt')
data.shape
(6679, 2)
Die Funktion gibt ein zweidimensionales Array mit den Zeilen der eingelesenen Datei zurück. Alle Werte einer Spalte können wir durch Slicing erhalten:
date = data[:,0] # Alle Zeilen, jeweils erste Spalte
T = data[:,1] # Alle Zeilen, jeweils zweite Spalte
date, T
(array([ 1995.00274, 1995.00548, 1995.00821, ..., 2013.27926, 2013.282 , 2013.28474]), array([ 0.944444, -1.61111 , -3.55556 , ..., 10.5556 , 8.94444 , 11.1667 ]))
Hinweis: Die
numpy.loadtxt
Funktion kann auch direkt ein Array für jede Spalte zurückgeben, wenn das Argumentunpack=True
übergeben wird:date, T = np.loadtxt('data/temperatures.txt', unpack=True)Weitere praktische Optionen, wie die ersten Zeilen zu überspringen u.ä., findet ihr in der Dokumentation. Entfernt das '
#
'-Zeichen in der folgenden Zelle und schaut euch die Optionen mal an:
#np.loadtxt?
Mit der verwandten np.savetxt
Funktion können wir Daten als Textdatei abspeichern:
#np.savetxt?
Hinweis: Im Jupyter Notebook erhalten wir eine praktische Vorschau auf den Anfang einer Datei mit dem
!head path/to/file
Aufruf. Dies ist sehr hilfreich um die enthaltenen Daten zu prüfen, oder ob es Titelzeilen zu Überspringen gibt.
!head data/temperatures.txt
1995.00274 0.944444 1995.00548 -1.61111 1995.00821 -3.55556 1995.01095 -9.83333 1995.01369 -10.2222 1995.01643 -9.5 1995.01916 -10.2222 1995.02190 -6.61111 1995.02464 -2.94444 1995.02738 1.55556
numpy.save
¶Die numpy.loadtxt
und numpy.savetxt
Funktionen arbeiten mit Textdateien. Wenn ihr ein Numpy Array jedoch nur zwischenspeichern möchtet, bspw. das Ergebnis einer langen numerischen Berechnung, könnt ihr es auch mit numpy.save
in einer .npy
Binärdatei speichern:
# lange numerischen Berechnung hier
result = np.random.random(10)
print(result)
# Ergebnis zwischenspeichern
np.save('data/result.npy', result)
[ 0.64614477 0.29188998 0.637517 0.03316986 0.4732502 0.68255594 0.52305211 0.07812359 0.17744681 0.83103071]
Anstatt die Berechnung jedes mal erneut durchführen zu müssen, könnt ihr nun einfach mit numpy.load
das zwischengespeicherte Ergebnis laden:
result = np.load('data/result.npy')
print(result)
[ 0.64614477 0.29188998 0.637517 0.03316986 0.4732502 0.68255594 0.52305211 0.07812359 0.17744681 0.83103071]
Hinweis: Diese Vorgehensweise kann viel Zeit sparen während ihr an einem Teil eures Programms arbeitet, das die numerische Berechnung nicht betrifft, bspw. die graphische Ausgabe als Plot.
Die Datei data/temperatures.txt
enthält Temperaturdaten aus Heidelberg von 1995 bis einschließlich 2012. Schaue dir die Struktur der Daten zunächst an:
!head data/temperatures.txt
1995.00274 0.944444 1995.00548 -1.61111 1995.00821 -3.55556 1995.01095 -9.83333 1995.01369 -10.2222 1995.01643 -9.5 1995.01916 -10.2222 1995.02190 -6.61111 1995.02464 -2.94444 1995.02738 1.55556
a) Lese die Daten mithilfe der numpy.loadtxt
Funktion ein und weise die beiden Spalten zwei Variablen date
und T
zu.
date, T = np.loadtxt('data/temperatures.txt', unpack=True)
from numpy.testing import assert_array_almost_equal
try:
date
except NameError:
raise NameError("Es gibt keine Variable 'date'. Weise das Array einer Variablen mit diesem Namen zu.")
try:
T
except NameError:
raise NameError("Es gibt keine Variable 'T'. Weise das Array einer Variablen mit diesem Namen zu.")
assert_array_almost_equal(date[:3], [ 1995.00274, 1995.00548, 1995.00821], 4, "Das Array 'date' enthält nicht die richtigen Daten. Verwende die 'unpack=True' Funktion von 'numpy.loadtxt' wie im Hinweis oben.")
assert_array_almost_equal(T[:3], [ 0.944444, -1.61111, -3.55556], 4, "Das Array 'T' enthält nicht die richtigen Daten. Verwende die 'unpack=True' Funktion von 'numpy.loadtxt' wie im Hinweis oben.")
b) Berechne für jedes Jahr von 1995 bis einschließlich 2012 die Durchschnittstemperatur, die minimale und die maximale Temperatur. Füge dabei der Liste yearly_temperatures
für jedes Jahr eine Zeile mit dem Jahr und diesen drei Werten hinzu.
Die Datei enthält fehlerhafte Daten, die durch den Wert +/-99
gekennzeichnet sind und nicht in die Berechnung mit einbezogen werden dürfen.
Hinweis: Gehe die Jahre in einer for-Schleife durch und verwende eine Maske für das Array T
, sodass du nur die Temperaturdaten des entsprechenden Jahres als Slice erhälst. Darauf kannst du dann die Numpy Funktionen für den Mittelwert, das Minimum und das Maximum anwenden.
Erinnerung: Mehrere Masken kannst du mit dem &
-Operator kombinieren.
yearly_temperatures = []
### BEGIN SOLUTION
for year in range(1995, 2013):
temperatures = T[(date >= year) & (date < year + 1) & (np.abs(T) != 99)]
yearly_temperatures.append([year, np.mean(temperatures), np.min(temperatures), np.max(temperatures)])
### END SOLUTION
from tabulate import tabulate
print(tabulate(yearly_temperatures, headers=["Jahr", "Durchschnitt [°C]", "Minimal [°C]", "Maximal [°C]"]))
Jahr Durchschnitt [°C] Minimal [°C] Maximal [°C] ------ ------------------- -------------- -------------- 1995 8.7656 -13.2778 25.9444 1996 7.22983 -15.5 23.8333 1997 8.54842 -12.8889 21.5556 1998 9.2457 -12.2222 25.5 1999 9.11065 -9.83333 25.3333 2000 9.76545 -16.7778 24.7778 2001 9.00713 -12.1667 24.5556 2002 9.88171 -11.1111 25.1111 2003 9.39833 -14.3333 27.6667 2004 8.87702 -10.7778 23 2005 8.22475 -14.1111 25.1111 2006 9.16377 -11.2778 25.9444 2007 9.76865 -8.83333 26.2778 2008 9.66087 -5.11111 24.0556 2009 9.3723 -10.7778 23.2778 2010 8.32131 -9.33333 25.7222 2011 9.69163 -9.27778 25.5 2012 9.22527 -15.4444 24.7222
from numpy.testing import assert_array_almost_equal
assert_array_almost_equal(yearly_temperatures[0], [ 1995, 8.7656, -13.2778, 25.9444 ], 4, "Die Daten sind nicht richtig. Überprüfe, ob jedes Element der Liste 'yearly_temperatures' wiederum eine Liste mit den Werten Jahr, Durchschnittstemperatur, Minimum und Maximum ist und du die fehlerhaften Werte +/-99 herausgefiltert hast.")
c) Berechne diese Daten analog aufgeteilt in Monate statt Jahre, also bspw. die Durschnittstemperatur im Januar im ganzen gemessenen Zeitraum.
Hinweis: Den Zeitpunkt innerhalb eines Jahres, wobei 0
dem Jahresanfang und 1
dem Jahresende entspricht, erhälst du mit dem Modulo Operator: date % 1
monthly_temperatures = []
### BEGIN SOLUTION
for month in range(0, 12):
temperatures = T[(date % 1 >= month / 12) & (date % 1 < (month + 1) / 12) & (np.abs(T) != 99)]
monthly_temperatures.append([month + 1, np.mean(temperatures), np.min(temperatures), np.max(temperatures)])
### END SOLUTION
from tabulate import tabulate
print(tabulate(monthly_temperatures, headers=["Monat", "Durchschnitt [°C]", "Minimal [°C]", "Maximal [°C]"]))
Monat Durchschnitt [°C] Minimal [°C] Maximal [°C] ------- ------------------- -------------- -------------- 1 -0.8494 -16.7778 12.2222 2 0.662865 -15.4444 11.9444 3 4.47399 -7.66667 16.2778 4 9.27625 -2.11111 20.7222 5 13.9793 5.22222 23.1667 6 17.2042 7.77778 25.1111 7 18.4592 10.5 26.2778 8 18.2187 9.5 27.6667 9 13.7085 3.88889 22.7222 10 9.38013 -1.44444 20.5 11 3.74218 -7.11111 14.1667 12 0.220317 -15.5 11.2778
from numpy.testing import assert_array_almost_equal
assert_array_almost_equal(monthly_temperatures[0][1:], [ -0.8494, -16.7778, 12.2222 ], 4, "Die Daten sind nicht richtig. Überprüfe, ob jedes Element der Liste 'monthly_temperatures' wiederum eine Liste mit den Werten Monat, Durchschnittstemperatur, Minimum und Maximum ist und du die fehlerhaften Werte +/-99 herausgefiltert hast.")