====== Advanced Python Functionalities ====== ===== Jupyter Notebook ===== Ein Jupyter Notebook ist eine Art Notizbuch, in welchem man Test im Markdown Stil mit Python-Code kombinieren kann. Jupyter Notebooks haben die Extension `.ipyn`. Standardmässig werden Jupyter-Notebooks direkt im Browser geöffnet, bearbeitet und ausgeführt. Man kann die mittlerweile aber auch direkt im VSCode machen, allerdings ist der Funktionsumfang beschränkt. Beispielsweise funktionieren die meisten Widgets (welche sehr nützlich sind) leider nicht im VSCode (Stand 2023). Es lohnt sich deshalb, beide Arten von Jupyter zu installieren. ==== Setup Jupyter im Browser ==== Jupyter kann ganz normal via pip installiert werden: ``` pip install notebook ``` Navigiere in der Konsole zum Ordner, in dem du arbeiten möchtest und öffne Jupyter mit dem Befehl ``` jupyter notebook ``` Jetzt kannst du ein neues Notebook erstellen und los gehts! Achtung: Jupyter wird auf diese Weise für die Standardinstallation von Python installiert. Falls du mehrere Python-Versionen installiert hast, musst du dafür sorgen, dass Jupyter für die richtige Version installiert wird. Empfehlenswert ist die Verwendung eines Tools, welches verschiedene Python-Versionen verwaltet wie *pyenv*. Hier findet man detaillierte Infos zu den verschiedenen Installationsmöglichkeiten: [[https://jupyter.org/install]] ==== Setup Jupyter in VSCode ==== 1. In VSCode installiere "Jupyter" Extension von Microsoft. 1. Erstelle neues Notebook: Command Palette (nutze Shortcut!) / "Create: Neues Jupyter Notebook" ==== Mit Jupyter Notebook arbeiten ==== Ein Jupyter Notebook besteht aus einer Abfolge von Zellen, die entweder **Code** (z. B. Python-Code) oder **Text** (im Markdown-Format) enthalten können. Codezellen ermöglichen das Ausführen von Code in Echtzeit und das Anzeigen der Ergebnisse direkt im Notebook. Textzellen dienen zur Dokumentation, Beschreibung des Codes, Anleitungen oder zur Erklärung von Ergebnissen. **Codezelle:** * Schreibe deinen Code ganz normal. * Ausführen mit **Shift + Enter**. * Beachte, dass die letzte Zeile Code automatisch geprintet wird. Im folgenden Codeblock wird also `5` ausgegeben, obwohl der Code kein print-Command beinhaltet: x = 4 y = 5 x y **Textzelle:** * Schreibe Text im Markdown-Stile * Einfache Darstellung mathematischer Ausdrücke mit **LaTeX**. * Beispiel: # Überschrift 1. Ordnung ## Überschrift 2. Ordnung ### Überschrift 3. Ordnung Die Funktion $f(x) = x^2$ ist eine quadratische Funktion. Die folgende Funktion ist eine kubische Funktion: $$f(x) = \frac{1}{4} x^3$$ ==== Magic & Cheat Sheet ==== In Jupyter-Notebooks kann man sogenannte **Magic-Befehle** verwenden, welche immer mit `%` beginnen. Diese und viele anderen wichtigen Befehle findet man im folgenden **Cheat Sheet:** {{ :talit:jupyter_notebook_cheatsheet_edureka_image.png?nolink&300 |}} Klicke auf folgenden Link, um das Cheat Sheet als PDF herunterzuladen: {{ :talit:jupyter_notebook_cheatsheet_edureka.pdf |}} ===== Numpy ===== **Numpy** ist eine leistungsstarke Python-Bibliothek, die für numerische Berechnungen verwendet wird. Sie bietet viele Funktionen und Werkzeuge, um mit grossen Mengen von **Daten** umzugehen und **mathematische Operationen** durchzuführen. In diesem Tutorial werden wir uns auf die Grundlagen von Numpy konzentrieren und dir zeigen, wie du sie verwenden kannst. === Getting started === Zuerst muss Numpy installiert werden, verwende dazu pip: `pip install numpy`. Danach kannst du Numpy in deinem Projekt importieren: import numpy as np Hier haben wir Numpy mit dem Namen `np` importiert, um unseren Code etwas kürzer zu machen. Nun können wir mit `np.` auf alle Numpy-Funktionen zugreifen. === Erstellen von Numpy-Arrays === Der grundlegende Datenbehälter in Numpy ist das **Numpy-Array**. Dieses hat Ähnlichkeiten mit einer *Liste*, kann aber viel mehr. Du kannst ein Numpy-Array erstellen, indem du die Funktion `np.array()` verwendest. Hier ist ein Beispiel: import numpy as np # Erstellen eines Numpy-Arrays aus einer Python-Liste my_list = [1, 2, 3, 4, 5] # normale Python-Liste my_array = np.array(my_list) # numpy-array print(my_array) Die Ausgabe wird sein: [1 2 3 4 5] Du kannst auch **mehrdimensionale Arrays** erstellen, indem du *geschachtelte Listen* verwendest. Hier ist ein Beispiel: import numpy as np # Erstellen eines zweidimensionalen Numpy-Arrays my_list = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] my_array = np.array(my_list) print(my_array) print(my_array.shape) # Output: (3,3) weil ist 3x3-Matrix Die Ausgabe wird sein: [[1 2 3] [4 5 6] [7 8 9]] In der Mathematik versteht man unter einer **Matrix** eine rechteckige Anordnung von Zahlen, z.B. $$ \begin{pmatrix} 2 & 4 & 1 & 7 & 3 \\ 9 & 5 & 6 & 8 & 2 \\ 0 & 3 & 2 & 1 & 5 \\ 6 & 4 & 7 & 3 & 9 \\ \end{pmatrix} $$ Genau, von da hat der fantastische Film seinen Namen! Aber es ist eigentlich auch klar, dass ein Film, der nach einem wichtigen mathematischen Objekt benannt wird, toll sein muss! Aber wir schweifen ab ... **Mehrdimensionale Arrays** eignen sich super, um Vektoren abzubilden. Mit einem einzelnen Befehl kann man **spezielle Arrays/Matrizen** erzeugen: ones = np.ones((2,3)) # lauter Einsen zeros = np.zeros((2,3)) # lauter Nullen t = np.linspace(3,5,10) # 1D-Array von 3 bis und mit 5 mit 10 Elementen, alle gleicher Abstand === Grundlegende Operationen === Numpy ermöglicht es uns, viele mathematische Operationen auf Arrays durchzuführen. Hier sind einige grundlegende Operationen, die du ausprobieren kannst: **Elementweise Addition, Subtraktion, Multiplikation und Division:** import numpy as np a = np.array([1, 2, 3]) b = np.array([4, 5, 6]) print(a + b) # Elementweise Addition print(a - b) # Elementweise Subtraktion print(a * b) # Elementweise Multiplikation print(a / b) # Elementweise Division **Skalaroperationen:** import numpy as np a = np.array([1, 2, 3]) print(a + 2) # Skalaraddition print(a - 2) # Skalarsubtraktion print(a * 2) # Skalarmultiplikation print(a / 2) # Skalardivision **Matrixoperationen:** import numpy as np a = np.array([[1, 2], [3, 4]]) b = np.array([[5, 6], [7, 8]]) # Matrixmultiplikation print(np.dot(a, b)) # Ausgabe: [[19 22] # [43 50]] # Transponierte einer Matrix print(a.T) # Ausgabe: [[1 3] # [2 4]] # Inverse einer Matrix c = np.array([[1, 2], [3, 4]]) c_inv = np.linalg.inv(c) print(c_inv) # Ausgabe: [[-2. 1. ] # [ 1.5 -0.5]] === Elemente und Teilarrays auslesen === Numpy bietet auch leistungsstarke Möglichkeiten, um auf bestimmte Elemente oder Bereiche von Arrays zuzugreifen. Hier sind einige Beispiele: import numpy as np a = np.array([1, 2, 3, 4, 5]) # Einzelne Elemente auslesen print(a[0]) # Ausgabe: 1 print(a[-1]) # Ausgabe: 5 # Teilarray ausgeben print(a[1:4]) # Ausgabe: [2 3 4] print(a[:3]) # Ausgabe: [1 2 3] print(a[2:]) # Ausgabe: [3 4 5] Dies geht auch für mehrdimensionale Arrays a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) print(a[0, 1]) # Ausgabe: 2 print(a[:, 1]) # Ausgabe: [2 5 8] print(a[:2, 1:]) # Ausgabe: [[2 3] # [5 6]] Um einen Numpy-Array `arr` in einer Form auszugeben, in der man sie wieder in Code kopieren kann, verwendet man die `np.array2string` Funktion: arr_string = np.array2string(arr, separator=',') === Numpy-Funktionen === Numpy bietet viele eingebaute Funktionen für verschiedene mathematische Operationen. Hier sind einige Beispiele: **Summe, Durchschnitt und Maximum eines Arrays:** import numpy as np a = np.array([1, 2, 3, 4, 5]) print(np.sum(a)) # Ausgabe: 15 print(np.mean(a)) # Ausgabe: 3.0 print(np.max(a)) # Ausgabe: 5 Sehr praktisch ist, dass solche Funktionen auf ein *ganzes Array* angewendet werden können. Damit kann man sehr viele Operationen auf 1x ausführen: import numpy as np a = np.array([0, np.pi/2, np.pi]) print(np.sin(a)) # Ausgabe: [0. 1. 1.2246468e-16] print(np.cos(a)) # Ausgabe: [ 1.000000e+00 6.123234e-17 -1.000000e+00] **Zufallszahlen:** import numpy as np # Generiere ein Array mit Zufallszahlen zwischen 0 und 1 random_array = np.random.rand(5) print(random_array) # Generiere ein Array mit ganzen Zufallszahlen zwischen 0 und 9 random_array = np.random.randint(0, 10, size=5) print(random_array) ===== Lambda-Operator ===== Der lambda-Operator in Python ermöglicht die Erstellung **anonymer Funktionen**, auch als **Lambda-Funktionen** bekannt. Diese Funktionen werden *ohne Namen* definiert und werden in der Regel für einfache Ausdrücke verwendet, die nur aus *einer Zeile* bestehen. Der lambda-Operator wird häufig in Verbindung mit Funktionen höherer Ordnung wie `map()`, `filter()` und `reduce()` (mehr dazu unten) verwendet. Die **allgemeine Syntax** einer Lambda-Funktion sieht wie folgt aus: lambda argumente: output Hier repräsentieren `argumente` die Eingabeparameter der Funktion und `output` ist die Berechnung, die von der Funktion durchgeführt wird. Die Lambda-Funktion nimmt die Argumente entgegen, wertet den Ausdruck aus und gibt das Ergebnis zurück. Zum Beispiel möchten wir eine Funktion erstellen, die eine gegebene Zahl quadriert. Standardmässig würden wir dies wie folgt machen: def square(x): return x**2 Alternativ können wir die Funktion mit dem lambda-Operator definieren: square = lambda x: x**2 In diesem Fall nimmt die Lambda-Funktion ein einzelnes Argument `x` entgegen und der Ausdruck `x**2` quadriert den Wert von `x`. Wir können diese Lambda-Funktion dann verwenden, um das Quadrat einer Zahl zu berechnen: result = square(5) print(result) # Ausgabe: 25 Lambda-Funktionen können auch mehrere Argumente entgegennehmen. Wenn wir zum Beispiel eine Lambda-Funktion erstellen möchten, die die Summe von zwei Zahlen berechnet, können wir dies wie folgt tun: add = lambda x, y: x + y Hier nimmt die Lambda-Funktion `addition` zwei Argumente `x` und `y` entgegen und gibt ihre Summe zurück. Wir können sie folgendermaßen verwenden: result = add(3, 4) print(result) # Ausgabe: 7 Lambda-Funktionen sind besonders nützlich, wenn wir einfache Funktionen benötigen, die "on the fly" erstellt werden, ohne explizit eine benannte Funktion zu definieren. Sie werden häufig in Situationen verwendet, in denen wir eine Funktion als Argument an eine andere Funktion übergeben möchten oder wenn wir kurzen, prägnanten Code schreiben möchten. ===== Funktionen map(), filter() und reduce() ====== Die Funktionen `map()`, `filter()` und `reduce()` sind nützliche Funktionen in Python, die häufig in Kombination mit Lambda-Funktionen verwendet werden, um Transformationen auf Daten anzuwenden oder Elemente aus einer Sequenz auszuwählen. === Die Funktion map() === Die Funktion `map()` wendet eine angegebene Funktion auf jedes Element einer Sequenz an und gibt eine neue Sequenz mit den transformierten Werten zurück. Die allgemeine Syntax der `map()`-Funktion lautet: map(function, sequence) Hierbei steht `function` für die Funktion, die auf jedes Element angewendet werden soll, und `sequence` repräsentiert die Eingabesequenz. Beispiel: numbers = [1, 2, 3, 4, 5] squared_numbers = list(map(lambda x: x**2, numbers)) print(squared_numbers) # Ausgabe: [1, 4, 9, 16, 25] In diesem Beispiel wird die Lambda-Funktion `lambda x: x**2` auf jedes Element der Liste `numbers` angewendet und die transformierten Werte werden in der Liste `squared_numbers` gespeichert. Beachte, dass `map()` ein Map-Objekt zurückgibt. Deshalb muss man diese noch in eine Liste umwandeln. === Die Funktion filter() === Die Funktion `filter()` filtert Elemente aus einer Sequenz basierend auf einer angegebenen Funktion und gibt eine neue Sequenz mit den gefilterten Elementen zurück. Die allgemeine Syntax der `filter()`-Funktion lautet: filter(function, sequence) Hierbei steht `function` für die Funktion, die jedes Element überprüft, und `sequence` repräsentiert die Eingabesequenz. Beispiel: numbers = [1, 2, 3, 4, 5] even_numbers = list(filter(lambda x: x % 2 == 0, numbers)) print(even_numbers) # Ausgabe: [2, 4] In diesem Beispiel wird die Lambda-Funktion `lambda x: x % 2 == 0` verwendet, um nur die geraden Zahlen aus der Liste `numbers` zu filtern und in der Liste `even_numbers` zu speichern. === Die Funktion reduce() === Die Funktion `reduce()` wendet eine angegebene Funktion auf die Elemente einer Sequenz an, um eine einzige Wertreduktion durchzuführen. Die allgemeine Syntax der `reduce()`-Funktion lautet: from functools import reduce reduce(function, sequence) Hierbei steht `function` für die Funktion, die die Reduktion durchführt, und `sequence` repräsentiert die Eingabesequenz. Beispiel: from functools import reduce numbers = [1, 2, 3, 4, 5] sum_of_numbers = reduce(lambda x, y: x + y, numbers) print(sum_of_numbers) # Ausgabe: 15 In diesem Beispiel wird die Lambda-Funktion `lambda x, y: x + y` verwendet, um die Summe aller Elemente in der Liste `numbers` zu berechnen. Diese Funktionen bieten eine kompakte und elegante Möglichkeit, Transformationen auf Daten anzuwenden oder Elemente aus Sequenzen auszuwählen. Sie sind besonders nützlich in Situationen, in denen eine Schleife vermieden oder der Code auf eine Zeile reduziert werden soll.