"Bisher hat uns das programmieren eher mehr Arbeit gemacht als uns welche abgenommen. Zeitersparnis bekommen wir sofern wir viele Rechnungen hintereinander ausführen müssen. Hierfür gibt es die **for**-Schleife. Diese Schleife führt die gleichen Zeilen eins Codes wiederholt für die Elemente in einer Liste aus:"
"Bei einer Schleife ist darauf zu achten, dass der Anweisungsblock welcher wiederholt ausgeführt werden soll mit 4x Leerzeichen eingrückt wurde. Dies entspricht einmal <img src=\"images/Tab-Key.png\" alt=\"Tab-Taste\" width=\"100\"/>"
"Statt das Ergebnis lediglich per `print`-Anweisung darstellen zu lassen, können wir auch unser Wissen um Listen benutzen und die berechneten Werte einer neuen Liste anfügen:"
"Wir können auch über mehre Daten gleichzeitig loopen. Hierzu kann die `zip` Anweisung genutzt werden. `zip` verbindet hierbei die einzelnen Elemente einer Liste wie bei einem Reißverschluss miteinander:"
"Dies kann zum Beispiel dann hilfreich sein wenn sich mehr als eine Variable ändern soll, z.B. bei einer Messreihe für die Schallgeschwindigkeit in Luft:"
"Kopiert eure Aufgabe 4.a. aus der Vorbereitung in das Notebook und berechnet nun für die Messwerte aus Aufgabe 4 a. die Leistung $P$ und den Widerstand $R$ sowie deren Fehler. Nutzt hierfür die ausführliche schrebweise der **for**-Schleife im Fall des Widerstands $R$ und den list-comprehension Syntax für die Leistung $P$. Fügt die berechneten Werte als neue Spalten and die Liste *daten* an. \n",
"## Das Darstellen von Messdaten mittels `matplotlib`:\n",
"Das Plotten von Daten ist eines der wichtigsten Mittel um eine Fülle von Informationen kompakt und verständlich seinem Gegenüber darzubieten. Gute Plots zu erstellen kann eine regelrechte Kunst sein und ist für ein gutes Paper, bzw. eine gute Bachelor- bzw. Masterarbeit unverzichtbar. \n",
"<figcaption>Resultate des XENON1T Dunkle Materie Experiments. Die Graphik wurde mittels Matplotlib in Python erstellt. </figcaption>\n",
"</figure>\n",
"\n",
"Jede Programmiersprache verfügt über zusätzliche Pakete (im Englischen \"packages\") welche die Funktionalität der verwendeten Programmiersprache erweitern. **Matplotlib** ist ein umfangreiches Package, welches das Zeichnen von 2D und 3D Grafiken ermöglicht. Alle Parameter und Einstellungen einer Grafik werden entsprechend des Python-Codes eingestellt. Dadurch wird das Erstellen der Grafik reproduzierbar und man kann schnell dieselbe Grafik mit neuen Daten füttern.\n",
"\n",
"Es ist unmöglich alle Möglichkeiten und Einstellungen die euch **Matplotlib** bietet auswendig zu kennen. Mit der Zeit werdet ihr ein solides Grundwissen der gängisten Befehle haben. Für alles weitere hilft euch die [Dokumentation und ihre Beispiele](http://matplotlib.org/). Des Weiteren ist insbesondere hier die **IPython Hilfe** und das **automatische Vervollständigen von Befehlen** besonders hilfreich.\n",
"\n",
"Für das Praktikum wollen wir uns zunächst lediglich drei unterschiedliche Arten von Plots angucken:\n",
"\n",
"* Normale Liniengrafiken\n",
"* Plots mit Fehlerbalken\n",
"* Histogramme "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Zunächst müssen wir Python mitteilen, dass wir das **Matplotlib** package nutzen möchten:"
"`import` läd für und aus dem package matplotlib das Modul `pyplot`. Mit Hilfe des Zusatzes `as plt` wird ein alias erstellt. Dieser Alias erspart uns im nachfolgenden Arbeit, wie wir im nachfolgenden Beispiel sehen können:"
"Innerhalb der Python-Community haben sich ein paar Standards etabliert an welche man sich halten sollte. So ist für `matplotlib.pyplot` der Alias `plt` zu verwenden. \n",
"\n",
"Im oberen Beispiel habt ihr nun auch bereits gesehen wie wir einfache Liniengrafiken erstellen können. Dabei sieht der Plot noch etwas blass aus. Dies können wir mit ein paar zusätzlichen Befehlen ändern."
"plt.xlabel('X-Achse') # <-- Beschriftung der x-Achse\n",
"plt.ylabel('Y-Achse') # <-- Beschiftung der y-Achse\n",
"plt.legend() # <-- Hinzufügen der Legend mit den \n",
" # in plot definierten labels\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Viele der eben verwendeten Optionen bieten euch unterschiedliche Auswahlmöglichkeiten:\n",
"\n",
"**Linestyle:**\n",
"* `''`: keine Linie\n",
"* `'-'`: durchgehende Linie\n",
"* `'--'`: gestrichelte Linie\n",
"* `'-.'`: Strich-Punktlinie\n",
"* `':'`: Punktlinie\n",
"* `'steps'`: Treppenfunktion\n",
"\n",
"**Color**:\n",
"* red, blue, yellow, \n",
"* RGB Werte von 0 bis 1 (statt von 0 bis 255): (1, 1, 1), (1, 0.2, 0.4)\n",
"\n",
"Darüber hinaus gibt es auch noch andere nützliche Styleoptionen wie `alpha` was die Transparenz eurer Linie ändert (Werte zwischen 0-1), oder `linewidth`-Option mit dessen Hilfe ihr die Linienbreite ändern könnt. \n",
"\n",
"Auch die anderen Befehle welche wir verwendetet haben verfügen über zusätzliche Optionen:"
"Ihr seht, das `plot` zwischen den angegebene Werte interpoliert. Möchtet ihr eine glatte Kurve zeichnen so müsst ihr die Anzahl an Punkten für die Interpolation erhöhen."
"In der Physik gehören zu jedem gemessen Wert ein Messunsicherheit/Messfehler. Diese Fehler sollten natürlich auch in unseren Grafiken korrekt dargestellt werden. Hierfür können wir den `errorbar`-Plot verwenden."
"#### Aufgabe 5.: Erstelle eine `errorbar`-Plot :\n",
"\n",
"Editiert die obere Zelle so, dass ihr mit Hilfe des Befehls \n",
"\n",
"```python\n",
"plt.errorbar()\n",
"```\n",
"\n",
"einen Errorbarplot erstellt. Verwende hier für die IPython help-funktion um den exakten Syntax zu erfahren. \n",
"\n",
"**Erinnerung:**\n",
"Ihr könnt die IPython-Hilfe aufrufen in dem ihr euren Cursor innerhalb das Wort errorbar von plt.errorbar bewegt und die Tastenkombination shift + tab verwendet. Lest nun nach wie ihr die x- und y-Werte und deren Fehler an die Funktion übergeben müsst.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Leider ist diese Standardvariante des Errorbar plots noch nicht das was wir möchten. Die Messwerte sind linear interpoliert und die errorbars sehen noch etwas eigenartig aus. Dies können wir jedoch im Handumdrehen ändern. Kümmern wir uns zunächst um die Plotmarker:"
"# linestyle='', # <-- Schaltet den Linienstyle aus | ls=''\n",
"# marker='d', # <-- Ändert den Markertyp in Diamanten | -----\n",
"# markerfacecolor='orange', # <-- Ändert die Markerfarbe zu Orange | mfc='orange'\n",
"# markeredgecolor='k', # <-- Setzt die Kantenfarbe auf schwarz | mec='k'\n",
"# markersize=7 # <-- Ändert die Markergröße | ms='7'\n",
"# ) \n",
"\n",
"plt.ylabel('Spannung [V]')\n",
"plt.xlabel('Strom [mA]')\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"All die Optionen welche wir hier für die Plotmarker verwendet haben können wir auch in der normalen `plt.plot`-Anweisung verwenden. Dabei gibt es eine ganze fülle an unterschiedlichen [marker Symbole](http://matplotlib.org/api/markers_api.html):\n",
" ecolor='k', # <-- Ändert die Linienfarbe der errorbars\n",
" elinewidth=2, # <-- Ändert die Fehlerbalkenbreite\n",
" capsize=5, # <-- Ändert die Breite der Endkappen der Fehlerbalken\n",
" capthick=2 # <-- Ändert die Dicke der Endkappen\n",
" ) \n",
"\n",
"plt.ylabel('Spannung [V]')\n",
"plt.xlabel('Strom [mA]')\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Histogramme:\n",
"\n",
"Ein weiterer Plottyp welcher häufig Verwendung findet ist das Histogramm. Um unser Histogramm mit Pseudozufallszahlen zu bestücken müssen wir diese erst erzeugen. Hierfür können wir das `numpy`-Modul verwenden. `numpy` ist ein weiteres Standardmodul welches viele nützliche Funktionen mit sich bringt. Hier wollen wir uns jedoch nur auf das erstellen von Zufallszahlen beschränken. "
"Auch für Histogramme gibt es viele unterschiedlichen Optionen welche ihr entweder mit Hilfe der Help-Funktion oder den Beispielen in der [Matplolibdokumentation](http://matplotlib.org/) herrausfinden könnt."
" range=(-3,5), # <-- Achtung im Gegensatz zur range-Anweisung ist \n",
" # das Intervall hier geschlossen [-3, 5]\n",
" histtype='step', # Ändert den Balkentyp in Stufen\n",
" linestyle='dashed',\n",
" label='Verteilung 1'\n",
" )\n",
"\n",
"plt.hist(rnd_numbers2, \n",
" bins=13, \n",
" range=(-3,5),\n",
" alpha=0.5, # Ändert die Transparenz der Balken \n",
" label='Verteilung 2'\n",
" )\n",
"\n",
"plt.legend()\n",
"plt.xlabel('Zufallswert')\n",
"plt.ylabel('Anzahl der Einträge')\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Bei Histogrammen solltet ihr immer darauf achten, dass euer binning sinnvoll gewählt ist. Weder zu viele noch zu wenig Bins führen zu einer sinnvollen Darstellung eurer Daten."
"Nach dem wir jetzt die verschiedenen Plottypen mit ihren unterschiedlichen Optionen kennen gelernt haben möchten wir diese natürlich auch speichern können. Dies können wir auf zwei unterschiedliche Arten machen.\n",
"\n",
"Entweder ihr macht mit eurer Maus einen Rechtsklick auf eure Grafik und wählt \"Grafik speichern als\" aus, oder ihr verwendet statt der `plt.show`- die `plt.savefig`-Anweisung dafür."
"Im folgenden wollen wir ein Plot mit einer gauss'schen Wahrscheinlichkeitsdichte erstellen. Geht hierfür wie folgt vor:\n",
"\n",
"1. Erstellt euch 500000 pseudo-Zufallszahlen, welche einer Gaußverteilung mit $µ=5$ und $sigma=2$ folgen.\n",
"2. Tragt die Zufallszahlen in ein Histogramm ein und normiert dieses so dass die Gesamtfläche eins beträgt. **Tipp: `plt.hist` hat hierfür einen optionalen Parameter benutzt die Help oder das Internet um herrauszufinden welcher es ist.**\n",
"3. Wählt ein geeignete `range` und ein `binning` von 100 für das Histogram.\n",
"4. Plottet anschließend die dazugehörige Gaußverteilung als Funktion. Geht dabei wie folgt vor:\n",
" 1. Erstellt eine Gaußfunktion. *Erinnerung:* eine Gaußverteilung ist gegeben durch:\n",
" **Tipp:** Das Numpy package beinhaltet die Zahlen pi und die Exponentialfunktion bereit. Ihr könnt diese über `np.pi` und `np.exp()` verwenden. \n",
" 2. Erstellt euch eine liste von x-Werten in der von euch gewählten range in 0.1er Schritten. Verwendetet hierfür die `range`-Funktion zusammen mit der listcomprehension.\n",
"Im folgenden wolllen wir die **Methode der kleinsten Quadrate (Least Squares)** näher beleuchten. Diese Methode wird oft benutzt um eine Funktion $\\lambda(x; \\ $**$\\phi$**$)$ mit den Funktionsparametern $\\mathbf{\\phi}$ an die gemessenen Punkte **$(x,y)$** anzupassen. Um jedoch die **Methode der kleinsten Quadrate** zu verstehen wollen wir sie erst einmal anschaulich und mathematisch herleiten. Dabei stüzen wir uns im folgenden auf eine Herleitung aus dem Buch **\"Statistical Data Analysis\"** von **Glen Cowan**.\n",
"\n",
"In unserem Grundpraktikum haben wir bereits gelernt, dass Messwerte durch Zufallszahlen $x_i$ representiert werden und einer gewissen **Wahrscheinlichkeitsdichtefunktion (probability density function)** $f(x)$ unterliegen. \n",
"\n",
"<figure class=\"image\">\n",
"<img src=\"images/MaterialPythonkurs092018/PorbDensFun.png\" alt=\"{{ Beispiel PDF }}\" width=70%>\n",
"</figure>\n",
"\n",
"\n",
"Eine **pdf** gibt an mit welcher **Wahrscheinlichkeit ein Wert $x_i$** innerhalb eines **infinitesimalen Intervals $\\text{d}x_i$** zu finden ist. Des Weitren gilt das die Gesamtwahrscheinlichkeit gegeben ist durch $\\int_S f(x) dx = 1$. \n",
"\n",
"Nun betrachten wir folgendes Beispiel: In unserem Labor messen wir genau drei mal die Raumtemperartur T. Auch hier gilt, dass unsere Messung der einzelnen $T_i$ einer gewissen **Wahrscheinlichkeitsdichtefunktion** folgen. Betrachtet nun das folgende Bild; welche **Wahrscheinlichkeitsdichtefunktion** passt besser zu den gezeigten Daten und **Warum?**\n",
"\n",
"<figure class=\"image\">\n",
"<img src=\"images/MaterialPythonkurs092018/ProbMaxTemp.png\" alt=\"{{ Beispiel PDF }}\" width=100%>\n",
"</figure>\n",
"\n",
"Die rechte Verteilung spiegelt unsere Messdaten besser wieder. Dies können wir auch mathematisch ausdrücken. Für $N$ voreinander unabhängige Zufallszahlen bzw. Messpunkte (in unserem Beispiel $N = 3$) ist die Gesamtwahrscheinlichkeit gegeben durch das Produkt der einzelnen Wahrscheinlichkeitsdichten $f(x_i, \\theta)$ multipliziert mit dem jeweiligen infinitesimalen element $dx_i$\n",
"\n",
"$$\\prod_{i = 1}^{N} f(x_i,\\theta) \\ dx_i \\text{ für alle } x_i \\text{ in } [x_i, x_i + dx_i]$$\n",
"\n",
"wobei $x_i$ in unserem Beispiel den Messpunkten $T_i$ und $f(x_i,\\theta)$ unserer Gausverteilung mit $\\theta = (\\mu, \\sigma)$ entspricht. Sprich sofern unsere Werte gut von der jeweiligen **Wahrscheinlichkeitsdichtefunktion** repräsentiert werden, d.h. wir die richtigen Parameter $\\theta$ gewählt haben (wie im rechten oberen Plot), gilt \n",
"\n",
"$$ \\prod_{i = 1}^{N} f(x_i,\\theta) dx_i$$ \n",
"\n",
"ist **maximal**. Da die einzelnen $dx_i$ von unseren Parametern $\\theta$ unabhängig sind gilt die gleiche Argumentation auch für \n",
"Wie kommen wir nun von der **likely hood function** auf unsere **Methode der kleinsten Quadrate** und dem fitten einer Funktion $\\lambda(x; \\ $**$\\phi$**$)$ an die gemessenen Punkte **$(x,y)$**? Dazu brauche wir noch einen Zwischenschritt. Oftmals ist es einfacher statt die **likely hood function** zu maximieren die so genannte **log likely hood function**\n",
"zu maximieren. Dies ist im Grunde das Gleiche, da der logarithmus eine monoton-steigende Funktion ist. Auch in unserem Fall der **Methode der kleinsten Quadrate** benötigen wir die **log likely hood function**. \n",
"\n",
"Stellt euch nun vor wir haben eine Messung mit $N$ voneinander unabhängigen Messpunkten (x,y). Des Weiteren nehmen wir an, dass alle $x_i$ ohne Fehler sind und das unsere $y_i$ gaußförmig um einen unbekannten Wahrenwert $\\lambda_i$ (sprich $\\lambda_i$ entspricht dem Erwartungswert $\\mu_i$ unserer Gaußverteilung) mit einer bekannten Varianz $\\Delta y_i^2$ verteilt sind (Diese Annahme lässt sich mit dem zentralen Grenzwertsatz begründen, so lange der Fehler sich aus der Summe kleinen Fehlern zusammensetzt). Die dazugehörige **likely hood function** ist dann gegeben durch:\n",
"wobei die konstanten Terme welche nicht von unserer Funktion $\\lambda(x_i; \\phi)$ abhängen vernachlässigt worden sind. Durch den Faktor $-\\frac{1}{2}$ ist das maximieren dieser **log likely hood function** gleich dem minnimieren von\n",
"Diese Funktion ist unsere gesuchte **Methode der kleinsten Quadrate**. Mit ihrer Hilfe kann eine beliebige Funktion $\\lambda(x; \\phi)$, welche liniear in ihren Parametern $\\phi$ ist, an unsere Messdaten $(x,y\\pm\\Delta y)$ gefittet werden. Dabei stellt der Fitprozess selbst lediglich ein Minimierungsproblem dar. Im folgenden sind unsere Annahmen noch einmal grafisch in einem Beispiel dargestellt.\n",
"\n",
"<figure class=\"image\">\n",
"<img src=\"images/MaterialPythonkurs092018/LeastSquare.png\" alt=\"{{ Least Square Beispiel }}\" width=100%>\n",
"</figure>"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Es gibt verschiedene Arten von Algorithmen um Minimierungsprobleme zu lösen. Wie diese genau Aufgebaut sind lernt ihr in anderen Progrmmierkursen wie zum Beispiel *Programmieren für Physiker* oder *Computer in der Wissenschaft*. Zum Glück haben uns bereits in Python andere Menschen diese Arbeit abgenommen und wir können aus dem Package `scipy.optimize` die Funktion `curve_fit` verwenden. \n",
"Hierbei stellt curve_fit eine Methode dar, Fit-Funktionen nach der obigen vorgestellten Methode der *kleinsten Quadraten* zu bestimmen. Dies hat zur Folge, dass lediglich die y-Fehler eurer Messwerte für den Fit verwendet werden können. "
"Ihr seht `curve_fit` gibt uns zwei unterschiedliche Listen zurück. Die erste Liste `para` beinhaltet die berechneten Fitparameter. `pcov` hingegen ist eine [Kovarianzmatrix](https://de.wikipedia.org/wiki/Kovarianzmatrix) auf deren Diagonalen ihr die Varianzen ($\\sigma^2$) der einzelnen Parameter findet (auf der Nebendiagonalen befinden sich die Kovarianzen). D.h. bei einer Funktion mit drei Parametern `def f(x, p1, p2, p3):` würde `para` und `pcov` allgemein so aussehen:\n",
"\n",
"```\n",
"para = [p1, p2, p3]\n",
"pcov = [[cov_1,1, cov_1,2, cov_1,3], \n",
" [cov_2,1, cov_2,2, cov_2,3],\n",
" [cov_3,1, cov_3,2, cov_3,3]]\n",
"```\n",
"wobei `cov_i,i` wie bereits erwähnt die einzelnen Kovarianzen bzw. Varianzen sind. Aber was genau macht jetzt curve_fit eigentlich um auf diese Werte zu kommen? Wie bereits erklärt basiert `curve_fit` auf der Methode der kleinsten Quadrate. D.h. die Funktion probiert etliche verschiedene Varianten eurer Parameter durch bis es die Kombination gefunden hat bei der das $\\chi^2$ klein wird. Gucken wir uns doch mal ein paar Zwischenschritte für unser Beispiel des ohm'schen Widerstandes an: \n",
" [Spannung(value, para[0]) for value in strom], \n",
" ls ='dashed',\n",
" color='orange',\n",
" label = f'Fitgerade mit R = {para[0]:0.2f} +/- {pcov[0,0]**(1/2):0.2f} ohm'\n",
" )\n",
"\n",
"plt.legend()\n",
"plt.ylabel('Spannung [V]')\n",
"plt.xlabel('Strom [mA]')\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Das Ergebnis sieht bereits ganz gut aus, allerdings kennt hier unsere Funktion `curve_fit` die Fehler unserer Messwerte noch garnicht. Da dies sehr unphysikalisch ist lasst uns das ganze nochmal mit Unsicherheiten wiederholen: "
"Wie ihr sehen könnt ist der Wert für den Widerstand zwar gleich geblieben, jedoch die Unsicherheit des Wertes hat sich erhöht.\n",
"\n",
"Wie gut denkt ihr fittet unsere obige Funktion unsere Messdaten? Sehr gut? Gut? Befriedigend? Oder doch eher schlecht? Wäre es nicht gut ein Maß für die Güte des Fits zu haben? Wie könnte ein solches Maß aussehen?\n",
"\n",
"Ihr habt das entscheiden Kriterium bereits kennen gelernt, bei der Methode der kleinsten Quadrate geht es darum das $\\chi^2$ zu minimieren sprich klein zu machen. Gucken wir uns hierzu erst noch einmal an wie sich das $\\chi^2$ berechnet:\n",
"Wie vergleicht sich dieses $\\chi^2$ nun mit einer Funktion welche unsere Daten schlechter beschreibt. Zum Beispiel sofern wir die Spannung über die Funktion \n",
"Wie ihr sehen könnt ist das $\\chi^2$ für unsere zweite Funktion etwas größer als für das klassische ohm'sche Gesetzt. Somit würden wir unsere zweiten Ansatz verwerfen. \n",
"\n",
"Damit man für einen gegebene Datensatz nicht hunderte von verschiedene Funktionen durchprobieren muss gibt es für das $\\chi^2$ eine allgemeine Faustregel, welche den berechneten $\\chi^2$-Wert mit der Anzahl unserer Freiheitsgrade vergleicht. Die Anzahl an Freiheitsgrade ist allgemeinhin gegeben als *Anzahl der Messwerte - Anzahl der Funktionsparameter* ($m - n$).\n",
"\n",
"1. Sofern $\\chi^2/\\text{ndof} >> 1$: sollte die Hypothese bzw. die Fitfunktion angezweifelt werden. Sie beschreibt in diesem Fall die Messdaten nur unzureichend. (Bzw. sollte $\\chi^2/\\text{ndof} > 1$ kann dies auch bedeuten, dass eure Unsicherheiten unterschätzt sind)\n",
"2. Sofern $\\chi^2/\\text{ndof} \\approx 1$: beschreibt die Hypothese bzw. die Fitfunktion die Daten wie erwartet und wird nicht abgelehnt. \n",
"3. Falls $\\chi^2/\\text{ndof} << 1$ beschreibt die Hypothese bzw. die Fitfunktion die Daten wesentlich besser als erwartet. In diesem Fall heißt es nicht, dass unsere Hypothese falsch ist, aber man sollte überprüfen ob die gemessenen Fehler nicht überschätzt worden sind (oder eine Korrelation zwischen denn Messfehlern vor liegt). \n",
"\n",
"Sofern ihr eine Arbeit schreibt und eure **Goodness-of-the-Fit** ($\\chi^2/\\text{ndof}$) angeben wollt so gebt immer beides an, das $\\chi^2$ und die Anzahl an Freiheitsgraden ndof. Beide Werte getrennt habne einen größeren Informationsgehalt als der Resultierende Quotient (genaueres lernt ihr in z.B. in der Vorlesung *Statistik, Datenanalyse und Simulationen* im Master)."
"Jetzt seid ihr ein letztes mal gefordert. In dieser Aufgabe wollen wir alles was wir heute gelernt haben nochmal reflektieren und anwenden. Erstellt hierfür ein neues Jupyter-Notebook und bearbeitet die Aufgaben im Skript. Sofern ihr Fragen bzw. Probleme habt vergesst nicht auf die folgenden Hilfsmöglichkeiten zurückzugreifen:\n",
"\n",
"1. Verwenden der IPython-Hilfe unter Verwendung der shift + tab Tasten.\n",
"2. Die ausführlichen Dokumentation von Python und das Angebot etlicher nützlicher Hilfsbeiträge in verschiedenen Foren (z.B. stackoverflow) im Internet.\n",
"3. Fragt bei den Assistenten nach während der Stunde nach bzw. nutzt den Emailkontakt auf der [gitlab Seite](https://gitlab.rlp.net/hoek/pgp1-python-einfuehrung/tree/master). \n"