{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "(pandas)=\n", "# Pandas DataFrames\n", "\n", "\n", "\n", "Im vorherigen Abschnitt {ref}`numpy` haben wir bereits Vektoren und Matrizen und deren Handling, welches breiten Anwendungsbereich findet, in Python kennengelernt. Allerdings benötigt man für statistische Analysen häufig weitere Datenstrukturen, die über die klassischen numerischen Anwendungen hinaus gehen. Das Pandas Paket (Pandas = Python Data Analysis Library) liefert neben Serien vor allem DataFrames, welche Daten in Tabellenform, ähnlich wie in Excel oder SQL zusammenfassen und setzt auf der NumPy-Bibliothek auf. DataFrames haben\n", "Spaltennamen, einen Zeilenindex und erlauben insbesondere in verschiedenen Spalten gemischte Datentypen (int, float, string, usw), was in Datenmatrizen nicht variiert werden kann. Bei Datenanalysen müssen häufig neue Variablen erstellt werden oder Daten in ihrer Struktur angepasst oder zusammengefasst werden. Hierfür bietet Pandas zahlreiche hilfreiche Funktionen. Außerdem stellt Pandas Routinen zum Importieren und Exportieren von CSV-Daten sowie zur Erstellung typischer Graphiken rund um die Datenanalyse bereit, da Pandas ebenso auf dem Paket `Matplotlib` aufsetzte, welches wir im Abschnitt {ref}`graphs` noch näher betrachten werden.\n", "\n", "All diese Schritte der Datenaufbereitung (Datenimport, Datenbereinigung, Anpassen der Datentypen, Datentransformation, Zusammenfassen verschiedener Datensätze, Aufbereitung für verschiedene statische Anaylsen oder Optimierungsroutinen) fasst man unter dem Begriff `Data Wrangling` zusammen. Dieser sehr aufwendige Prozess umfasst ca. 80 Prozent der Arbeitszeit, während die eigentlichen Analysen und Visualisierungen dann häufig nur noch ca. 20 Prozent der tatsächlichen Arbeitszeit für sich beanspruchen.\n", "\n", "Pandas liefert somit viele praktische Funktionen zur Daten- und Zeitreihenanalyse und ist daher ein Standardwerkzeug in Data Science, Statistik, Finance und Machine Learning. \n", "\n", "Auf den Doku-Seiten der Pakete oder anderen Programmiererseiten finden sich häufig kompakte Übersichten, der wichtigsten Befehle, was das Arbeiten mit dem entsprechen Pakten erleichtert: Pandas-Cheatsheet. \n", "\n", "## Pandas installieren\n", "\n", "Zunächst muss die **Pandas**-Bibliothek installiert werden. **Conda** erledigt dies mit dem Konsolenbefehl:\n", "```bash\n", "conda install -c conda-forge pandas\n", "```\n", "Alternativ kann das Paket auch mittels `Pip` installiert werden\n", "```bash\n", "pip install pandas\n", "```\n", "\n", "\n", "\n", "## Numpy-Array vs. Pandas DataFrame\n", "\n", "Das folgende einfache Beispiel zeigt die Vorteile eines Pandas-DataFrames gegenüber einem Numpy-Array - wir betrachten einen einfachen Datensatz:\n", "\n", "| Obstsorte | Menge | Preis (in €) |\n", "|----------|----------|----------|\n", "| Apfel | 10 | 0.5 |\n", "| Banane | 5 |0.8 |\n", "| Apfel | 7 |0.55|\n", "| Kirsche | 20 | 0.2 |\n", "\n", "Die erste Spalte enthält Strings, die zweite Integer und die dritte Spalte Floats, daher sind Numpy-Arrays nur sehr umständlich zu handeln.. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Pakete einbinden: das Matplotlib-Paket werden wir im folgenden Abschnitt {ref}`graphs` genauer betrachten und soll hier nur zur Erstellung einfacher Graphiken, die Pandas bereitstellt, aufzeigen. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Umsetzung als zweidimensionales NumPy-Array" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Daten als NumPy-Array, wegen verschiedener Typen wird dtype als object gesetzt \n", "data = np.array([\n", " [\"Apfel\", 10, 0.5],\n", " [\"Banane\", 5, 0.8],\n", " [\"Apfel\", 7, 0.55],\n", " [\"Kirsche\", 20, 0.2]\n", "], dtype=object)\n", "\n", "data" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(\"Datentypene 1. Zeile: %s %s %s\" % (type(data[0,0]), type(data[0,1]), type(data[0,2])))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Daher müssen beim Rechnen mit `data` immer jeweils die Typen konvertiert werden" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Umsatzspalte berechnen: Menge * Preis\n", "umsatz = data[:,1].astype(float) * data[:,2].astype(float)\n", "umsatz" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Die Berechnung des Umsatzes für jede einzelne Produktart ist entsprechend kompliziert " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Gesamtumsatz pro Produkt (manuell gruppieren)\n", "produkte = np.unique(data[:,0])\n", "print(\"Produkte: %s\" % produkte)\n", "for p in produkte:\n", " mask = data[:,0] == p\n", " print(p, umsatz[mask].sum())\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Vergleiche: Zugriff 1. Spalte (Index 0; : für gesamte Zeile)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "data[:,0]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "data[:,2] #Spalte 2 (=3. Spalte)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Durchschnittspreis\n", "avg_preis = data[:,2].astype(float).mean()\n", "print(\"Durchschnittspreis:\", avg_preis)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Deutlich einfacher ist das Handling in Pandas:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Umsetzung in Pandas als DataFrame" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Daten als DataFrame\n", "df = pd.DataFrame({\n", " \"Produkt\": [\"Apfel\", \"Banane\", \"Apfel\", \"Kirsche\"],\n", " \"Menge\": [10, 5, 7, 20],\n", " \"Preis\": [0.5, 0.8, 0.55, 0.2]\n", "})\n", "\n", "df" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Hinweis**: die Pandas Funktion `DataFrame()` bekommt im obigen Beispiel in Zeile 2 ein sogenanntes Dictionary (von Listen) übergeben, welches in geschweiften Klammern `{}` in Python gesetzt wird und entspricht einer Implementierung Spalte für Spalte, wobei jede Spalte für eine Variable steht. Alternativ kann der DataFrame auch zeilenweise aufgebaut werden, dann bekommt die Funktion `DataFrame()` eine Liste von Dictionaries (eins je Zeile) übergeben. Mit der Funktion `DataFrame.append()` können dann weitere Zeilen hinzugefügt werden. \n", "\n", "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Auf die einzelnen Spalten des DataFrames können wir auf folgende zwei verschiedene Arten zugreifen " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df[\"Produkt\"]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#falls nur 1 Variable (=1 Spalte): funktioniert der Zugriff auch mittels .\n", "df.Preis" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#Zugriff auf mehrere Spalten einfach mittels Liste der entsprechenden Variablen\n", "df[[\"Preis\",\"Menge\"]]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Neue Variablen (Spalten) können wie folgt hinzugefügt werden:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df[\"Umsatz\"]=df[\"Menge\"] * df[\"Preis\"]\n", "print(df)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#fuegen hier noch weitere Spalte Umsatz2 mit selben Werten hinzu, um Alternative aufzuzeigen\n", "df = df.assign(Umsatz2=df[\"Menge\"] * df[\"Preis\"])\n", "print(df)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Die Pandas-Funktionen `groupby()` sowie `pivot_table()` liefern eine sehr bequeme Möglichkeit, den Datensatz in Untergruppen zu unterteilen und dann für eine oder mehrere Variablen Berechnungen durchzuführen. Den Gesamtumstz je Produkt erhält man beispielsweise, indem wir den DataFrame nach den einzelnen Produkten gruppieren und anschließend die Umsätze summieren:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Gesamtumsatz und verkaufte Menge je Produkt \n", "df.groupby(\"Produkt\")[\"Umsatz\"].sum()\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df.pivot_table(\"Umsatz\",index=\"Produkt\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#mehrere Spalten einfach als Liste\n", "df.groupby(\"Produkt\")[[\"Umsatz\",\"Menge\",\"Umsatz2\"]].sum()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#oder falls sinnvoll für alle Spalten\n", "df.groupby(\"Produkt\").sum()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Ebenso können wir auf einzelne Spalten (Variablen) zahlreiche statistische Funktionen anwenden" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Durchschnittspreis\n", "print(\"Durchschnittspreis:\", df[\"Preis\"].mean())" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df.groupby(\"Produkt\").mean()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "mittels `groupby` können wir jetzt beispielsweise einfach die Anzahl für jede Obstsorte ermitteln:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Auf die Spalte bzw. Variable Menge kann ebenso mittels der zweiten Alternative zugegriffen werden:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df.groupby('Produkt').Menge.sum()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Die Befehle `.loc` (label-based) und `.iloc` (integer position-based) sind zwei sehr wichtige Instrumente in der Datenanalyse, welche wir nachfolgend ausführlicher behandeln möchten. Weitere Informationen finden Sie [hier](https://discovery.cs.illinois.edu/guides/DataFrame-Fundamentals/dataframe-loc-vs-iloc/#:~:text=The%20difference%20between%20the%20loc,by%20one%20for%20each%20row)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df_neu = pd.DataFrame({\n", " \"Produkt\": [\"Apfel\", \"Banane\", \"Apfel\", \"Kirsche\"],\n", " \"Menge\": [10, 5, 7, 20],\n", " \"Preis\": [0.5, 0.8, 0.55, 0.2]\n", "})\n", "df_neu=df_neu.set_index(\"Produkt\")\n", "\n", "df_neu.loc[\"Banane\"]\n", "\n", "# Alternativ\n", "df_neu.iloc[1]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Wenn wir auf mehrere Objekte zugreifen wollen, dann klappt dass wie folgt:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df_neu.loc[[\"Banane\", \"Kirsche\"]]\n", "\n", "#Alternativ\n", "df_neu.iloc[[1,3]]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Wenn wir Werte miteinander vergleichen wollen, dann ist folgender Befehl hilfreich" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df_neu.loc[[\"Kirsche\", \"Banane\"], \"Preis\"]\n", "\n", "# Alternativ\n", "df_neu.iloc[[1,3], 1]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Teilstichproben durch Filterbedingungen\n", "\n", "Bsp: Herausfiltern aller Zeilen mit Apfel, nutzen hierzu Vergleichsoperatoren" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "filter1 = df['Produkt']=='Apfel'\n", "filter1\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Einschränkung des DataFrames auf Filter:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df[filter1]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df.loc[filter1]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Herausfiltern Menge mindestens 10" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "filter2 = df['Menge'] >=10\n", "df[filter2]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Logisch verknüpfte Filterbedingungen: Sollen beispielsweise alle Einkäufe von Äpfeln, die mehr als 50 Cent gekostet haben herausgefiltert werden:\n", "\n", "Achtung während bisher Vergleichsoperatoren für Vektoren immer automatisch elementweise angewendet wurden, z.B. beim Vergleich der Sorte auf Äpfel mittels `df['Produkt']=='Apfel'` ist die logische Verknüpfung mehrerer Vektoren nicht mehr eindeutig, so " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "filter_apfel = df['Produkt'] == 'Apfel'\n", "filter_preis = df['Preis'] > 0.5\n", "\n", "print(filter_apfel)\n", "print(filter_preis) " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#logische Und-Verknuepfung funktioniert nicht fuer Vektoren / Arrays - Fehlermeldung!\n", "filter_apfel and filter_preis" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Für Vektoren nutzen wir die Numpy-Funktion `logical_and()`, die elementweise arbeitet" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "filter_komplex = np.logical_and(filter_apfel, filter_preis)\n", "print(filter_komplex)\n", "\n", "df[filter_komplex]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Alternativ kann auch `&` für die elementweise Und-Verknüpfung sowie `|` für die logische Oder-Operation anstatt `np.logical_or()` verwendet werden " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "filter_apfel & filter_preis" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "filter_apfel | filter_preis" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "np.logical_or(filter_apfel,filter_preis)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Das Analogon zu `not` für elementweise Operationen ist `~` bzw. `np.logical_not()`" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(not True)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(~filter_apfel)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(np.logical_not(filter_apfel))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Filtern mittels `isin()` beispielsweise für Strings" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "obst_spezial = [\"Banane\",\"Kirsche\",\"Birne\"]\n", "df[df[\"Produkt\"].isin(obst_spezial)]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Weitere hilfreiche Pandas-Funktionen" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Pandas stellt sehr viele hilfreiche Funktionen zur Datenmanipluation für DataFrames bereit. Für Details verweisen wir auf die [Pandas-Dokumentation](https://pandas.pydata.org/docs/). So haben wir bereits `groupby` kennengelernt, mit dem der Datensatz nach kategoriellen Variablen (eine oder mehrere) gruppiert werden und für jede einzelne Untergruppe (beispielsweise für jede Obstsorte) weitere Funktionen angewendet werden können. Häufig ist die Kombination bzw. Hintereinanderausführung solcher Befehle besonders mächtig. \n", "Beispielhaft erstellen wir einen neuen Dataframe mit den Gesamtumsätzen je Sorte, indem wir für die Umsätze für jede einzelne Obstsorte nochmals den Gesamtumsatz ermitteln:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df2 = df.groupby(\"Produkt\")[\"Umsatz\"].sum()\n", "df2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Aufgabe**: Probieren Sie weitere Funktionen, die auf den DataFrame df angewendet werden können, z. B. `head()`,`tail()`, `info()`, `shape()`, `describe()`, `agg()`, `value_counts()` oder `sort_values()` sowie `df.index` und `df.columns` und `df.values`" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df.info()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df.describe()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Zählen für kategorielle Variablen:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df[\"Produkt\"].value_counts()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Bemerkung:** Der Befehl `.value_counts(sort=True)` sortiert die Werte. Mit dem Befehl `.value_counts(normalize=True)` werden die anteiligen Werte für das jeweilige Produkt berechnet." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df[\"Produkt\"].value_counts(normalize=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Aggregate and Apply: (dt. Aggregieren und Anwenden ) " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df.apply('sum')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df.groupby(\"Produkt\").apply(\"mean\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df.agg(['min', 'max', 'sum'])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Achtung: Nicht jede Funktion ist für jeden Datentyp beliebig anwendbar oder sinnvoll, z.B. liefert der folgende Abruf einen Fehler, da kein Mittelwert für Strings (kategorielle Variable Produkt) berechnet werden kann und dies für nominal skalierte Merkmale natürlich auch nicht sinnvoll ist" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#lange Fehleranalyse ist hier ausgeblendet\n", "df.agg(['sum',\"mean\"])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Allerdings können wir das auch den Aufruf mittels Dictionaries präzisieren, welche Funktion auf welche Variablen angewendet werden soll, so dass für die Variable hier beispielsweise " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#dictionary of function list\n", "df.agg({\"Produkt\":[\"min\", \"max\",\"sum\"], \"Preis\": [\"min\", \"max\",\"sum\", \"mean\",\"median\"], \"Umsatz\": [\"min\", \"max\",\"sum\", \"median\",\"mean\"]})" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "sortieren:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df.sort_values(['Preis'])" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df.sort_values([\"Produkt\",\"Preis\"], ascending=[False,True])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Eine zentrale Fragestellung im Datenimport ist:\n", "1. Haben wir NaN-Werte (NaN=not a numer)? \n", "2. Wenn ja, wie viele?\n", "3. Wie gehen wir damit um?\n", "Zum ersten Teil der Frage möchten wir an dieser Stelle einige Methoden vorstellen, wie wir erkennen, dass NaN-Werte in unserem importieren Datensatz haben.\n", "Mithilfe von `.isna()`können wir erkennen, ob ein \"richtiger Wert\" in der Zelle vorhanden ist, oder nicht. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "NaN_df=pd.read_csv(\"./../../Data/beispiel_nan.csv\") \n", "print(NaN_df)\n", "\n", "NaN_df.isna() # Zeigt uns, ob Werte fehlen oder nicht. \n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Auf den ersten Blick sieht die `.isna()`-Methode sehr attraktiv aus, jedoch könnte dies bei größeren Datensätzen problematisch werden. Deshalb ist die Kombination von `.isna()`und `.any()` in solchen Fällen besser geeignet. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "NaN_df.isna().any()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Durch diese Kombination erfahren wir, ob in einer Spalte NaN's vorhanden sind, oder nicht. Jedoch haben wir noch keine Aussage erhalten, wie viele NaN's vorhanden sind, was uns zur Beantwortung unserer zweiten Frage führt. Zur numerischen Lösung können wir `.isna()`mit `.sum()` kombinieren und erhalten die Nazahl der NaN's je Spalte. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "NaN_df.isna().sum()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Wenn wir nach einer graphischen Lösung suchen, bietet der nachfolgende Code einen Ausweg" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "NaN_df.isna().sum().plot(kind=\"bar\", rot=50) # mithilfe von rot, können wir unsere Beschriftung auf der x-Achse neigen. \n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Nachdem wir nun gesehen haben, wie viele Werte je Spalte fehlen, können wir uns nun der Beantwortung unserer dritten Frage widmem. Der Umgang mit NaN's ist immer anhängig von der Situation. Am einfachsten wäre es, wenn wir vom Datenerheber einen vollständigen Datensatz (erneut) geliefert bekommen. Bedauerlicherweise wird das in der Regel kaum/nie stattfinden. Also müssen wir uns zwischen den beide Methoden **entfernen** und **auffüllen** entscheiden. Die erste Methodik ist eine sehr gewagte Methode, da uns dadurch Daten verloren gehen. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "NaN_df.dropna()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In einigen Fällen kann es sinnvoll sein fehlende NaN-Werte im Datensatz mit geeigneten Werten aufzufüllen, im Folgenden Code-Beispiel illustrieren wir das einfach mit Null. Hier sollte man allerdings sehr vorsichtig vorgehen, da selbst ein Auffüllen mit Nullen in einer Spalte zwar nicht die Summe, aber statistische Maßzahlen wie das arithmetische Mittel beeinflusst. Denn das Auffüllen der Werte hat Einfluss auf die Stichprobengröße, wie das einfache Beispiel illustriert. \n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "NaN_0 = NaN_df.fillna(0)\n", "print(NaN_0)\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(\"mit NaN: Summe Einkommen = %2.0f, Mittel =%2.1f, n=%i\" % \n", " (NaN_df.Einkommen.sum(), NaN_df.Einkommen.mean(), NaN_df.Einkommen.count()))\n", "\n", "print(\"ohne NaN: Summe Einkommen = %2.0f, Mittel =%2.1f, n=%i\" % \n", " (NaN_0.Einkommen.sum(), NaN_0.Einkommen.mean(), NaN_0.Einkommen.count()))\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Für die statistische Analyse und die Erstellung von Graphiken ist das Ersetzen von NaN-Werten also nicht notwendig, da typischerweise NaN-Werte hier automatisch ignoriert werden." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(NaN_df.info())\n", "\n", "print(NaN_0.info())\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Achtung:** Sollten Sie die Werte mit z.B. *NaN_df.fillna(\"Kein Wert geliefert\")* auffüllen, sind später Berechnungen nicht mehr möglich, da ja verschiedene Datentypen in der Spalte vorhanden sind. So wäre zum Beispiel die Berechnung des Durchschnittsalters problematisch." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Index vs Spalten" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(\"Spaltennamen = Variablen: %s \\n\" % df.columns)\n", "print(\"Index für Zugriff auf Datenzeilen: %s\" % df.index)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Der Index entspricht entweder der Zeilennummer -- analog zu NumPy-Arrays beginnend mit 0 (d.h. 0 für die erste Zeile im Datensatz, 1 für die zweite Zeile usw) oder dem Zeilennamen bzw. Label. Beispielsweise haben Finanzdatensätze üblicherweise das Datum als Index gesetzt. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#values liefert immer array zurueck: hier wird deutlich, dass Pandas auf NumPy aufsetzt\n", "df.values" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(df)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df_index = df.set_index(\"Produkt\")\n", "print(df_index)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df_index.index" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df_index.loc['Apfel']" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#ersten 2 Zeilen \n", "df_index[:2]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#dafür funktioniert jetzt der normale Integer-Index nicht mehr und verursacht eine Fehlermeldung\n", "df_index.loc[0:2]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#nutzen dafür iloc() Operator\n", "df_index.iloc[0:2]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Anmerkung**: Den Index abweichend von 0,1,2, .. zu setzen hat Vor- und Nachteile. Üblicherweise wird bei Finanzdaten das Handelsdatum als Index gesetzt, das werden wir aber in {ref}`yfinance` näher betrachten. Möchte man eine gewöhnliche Spalte als Index verwenden, setzt man diese mittels `set_index()` und möchte man einen Index wieder in eine normale Spalte umwandeln nutzt man die Funktion `reset_index()`. Zum Sortieren ist zudem die Funktion `sort_index()` nützlich " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Einfache Pandas Graphiken \n", "\n", "Da Pandas auf dem mächtigen Graphik-Paket `MatPlotLib` aufsetzt, können auch sehr einfach Graphiken erzeugt werden:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df2.plot(kind=\"bar\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df2.plot(kind=\"bar\",title=\"Gesamtumsatz\", rot = 45)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Im nächsten Abschnitt {ref}`graphs` sehen wir, wie wir diese Graphiken noch individueller anpassen können, z.B. neben Titel auch Labels hinzufügen:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df2.plot(kind=\"bar\")\n", "plt.title(\"Gesamtumsatz pro Produkt\")\n", "plt.ylabel(\"Umsatz (€)\")\n", "plt.show()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Einkommen=pd.read_csv(\"./../../Data/Daten_Diagramme.csv\", sep=\";\") # sep=\";\" ist manchmal notwendig, wenn die Daten aus der CSV schlecht lesbar importiert werden.\n", "# To-Do: An Ordnerstruktur anpassen\n", "print(Einkommen.head())\n", "\n", "Einkommen.rename(columns={\"Alter
in Jahren\" : \"Alter in Jahren\"}, inplace=True) #Spalte Alter umbenannt --> inplace=True => die Operation verändert das Objekt direkt (also „in place“) und gibt kein neues DataFrame zurück.\n", "\n", "Einkommen.plot(x=\"Alter in Jahren\", y=\"Nichterwerbspersonen (f)\", kind=\"scatter\", rot=45)\n", "plt.figure(figsize=(12,5)) #Breite = 12 Zoll, Höhe = 5 Zoll\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Wir können auch ein Balkendiagramm erstellen und für Vergleiche nebeneinander legen." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Einkommen.plot(\n", " x=\"Alter in Jahren\",\n", " y=[\"Nichterwerbspersonen (f)\", \"Nichterwerbspersonen (m)\"], # Beide Spalten in y Definieren\n", " kind=\"bar\",\n", " rot=50,\n", " title=\"Anzahl der Nichterwerbspersonen je Altersgruppe\"\n", ")\n", "\n", "plt.legend([\"männlich\", \"weiblich\"])\n", "plt.show()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Einkommen.plot(\n", " x=\"Alter in Jahren\",\n", " y=[\"Nichterwerbspersonen (f)\", \"Nichterwerbspersonen (m)\"], \n", " kind=\"barh\", # horizntales Balkendiagramm\n", " rot=50,\n", " title=\"Anzahl der Nichterwerbspersonen je Altersgruppe\"\n", ")\n", "\n", "plt.legend([\"männlich\", \"weiblich\"])\n", "plt.show()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Einkommen.plot(\n", " x=\"Alter in Jahren\",\n", " y=[\"Nichterwerbspersonen (f)\", \"Nichterwerbspersonen (m)\"],\n", " kind=\"bar\",\n", " rot=50,\n", " subplots=True, #somit in einer Grafik zwei Diagramme dargestellt \n", " title=[\n", " \"Anzahl der Nichterwerbspersonen (f) je Altersgruppe\",\n", " \"Anzahl der Nichterwerbspersonen (m) je Altersgruppe\"\n", " ]\n", ")\n", "\n", "plt.tight_layout() # Macht das Diagramm etwas \"schöner\" vom Layout her\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Des weiteren können wir auch Histogramme übereinanderlegen. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#### Pandas\n", "\n", "Einkommen[[\"Nichterwerbspersonen (f)\", \"Nichterwerbspersonen (m)\"]].plot(\n", " kind=\"hist\",\n", " bins=10, # Anzahl der Klassen \n", " alpha=0.5, # Erhöht Transaprent\n", " title=\"Verteilung der Nichterwerbspersonen (Pandas)\"\n", ")\n", "\n", "plt.legend([\"weiblich\", \"männlich\"])\n", "plt.xlabel(\"Anzahl\") # Beschriftung x-Achse\n", "plt.ylabel(\"Häufigkeit\") # Beschriftung y-Achse\n", "\n", "plt.show()\n", "\n", "### Matplotlib\n", "plt.hist(Einkommen[\"Nichterwerbspersonen (f)\"], bins=10, alpha=0.5, label=\"weiblich\")\n", "plt.hist(Einkommen[\"Nichterwerbspersonen (m)\"], bins=10, alpha=0.5, label=\"männlich\")\n", "\n", "plt.title(\"Histogramm der Nichterwerbspersonen (Matplotlib)\")\n", "plt.xlabel(\"Anzahl\")\n", "plt.ylabel(\"Häufigkeit\")\n", "plt.legend()\n", "\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Tidy Datenformat\n", "\n", "Es gibt zahlreiche Möglichkeiten umfangreiche Datensätze in Tabellen-Form darzustellen. Für die Basis-Philosophie des \n", "Tidy-Formats\n", "\n", "verweisen wir an dieser Stelle auf die Literatur und fassen hier nur kurz die grundlegende Idee zusammen.\n", "\n", "Das Tidy Datenformat gibt eine strukturierte und standardisierte Anordnung der Daten vor, bei der jede Variable in einer eigenen Spalte steht und jede Beobachtung in einer eigenen Zeile. \n", "Obiger DataFrame 'Einkommen' ist beispielsweise nicht im Tidy-Format, da das Einkommen für 4 verschiedene Gruppen (Kombinationen von Geschlecht und Erwerbstätigkeit mit jeweils 2 Ausprägungen) in 4 Spalten, statt einer abgebildet ist. Im Tidy-Format gäbe es neben der Altersgruppe eine Spalte für die Variable 'Geschlecht', eine Spalte für die Variable 'Erwerbstätikeit' und eine Spalte für die Variable 'Einkommen'. \n", "Dieses Prinzip bringt Übersichtlichkeit und erleichtert sämtliche Analyse- und Verarbeitungsschritte, insbesondere in Pandas.\n", "In jeder Zelle der Tabelle darf daher nur ein Wert stehen, d. h. keine Zellen mit mehrfach codierten Informationen oder Textformatierungen.\n", "\n", "Das folgende kleine Beispiel verletzt diese Anforderungen, da die Umsätze (1 Variable) für jedes Unternehmen ein einer extra Spalte stehen und zudem die erste Spalte sowohl das Quartal als auch das Jahr enthält. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Nicht-tidy: Spalten sind verschiedene Unternehmen und die Zeilen verschiedene Quartale\n", "df_umsatz = pd.DataFrame({\n", " 'Quartal': ['Q1 2024', 'Q2 2024', 'Q3 2024'],\n", " 'Apple': [28.4, 31.5, 33.2],\n", " 'Microsoft': [24.2, 25.0, 27.1],\n", " 'Google': [19.5, 20.7, 21.3]\n", "})\n", "print(df_umsatz)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Dieses sogenannte \"breite\" Datenforman kann mittels der Pandas Funktion `melt()` aber in das Tidy Format überführt werden, indem wir im ersten Schritt die Jahreszahl und das Quartal trennen und dann zusätzlich eine neue Variablen `Firma` einführen. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#Schritt 1: Spalte 'Quartal' in 'Quartal' und 'Jahr' aufteilen \n", "df_umsatz[[\"Quartal\", \"Jahr\"]] = df_umsatz[\"Quartal\"].str.split(' ', expand=True)\n", "\n", "print(df_umsatz)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#Schritt 2: wide to long\n", "df_tidy = df_umsatz.melt(id_vars=['Quartal', 'Jahr'], \n", " value_vars=['Apple', 'Microsoft', 'Google'],\n", " var_name='Firma', value_name='Umsatz')\n", "print(df_tidy)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Einfache Graphiken aus Pandas..." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df_tidy[df_tidy.Quartal == \"Q3\"].plot(x=\"Firma\", y=\"Umsatz\", kind = \"bar\", title =\"Umsatz Q3 2024\")\n", "df_tidy[df_tidy.Firma == \"Apple\"].plot(x=\"Quartal\", y=\"Umsatz\", kind = \"bar\", title =\"Umsatz Apple 2024\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Seaborn nutzt das tidy-Format viel effizienter. Das folgende Beispiel dient hier nur kurz der Illustration und wird nochmals in Abschnitt {ref}`graphs:seaborn` detailierter vorgestellt." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import seaborn as sns\n", "\n", "sns.barplot(data=df_tidy, x='Firma', y='Umsatz', hue='Quartal')\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "(pandas-read)=\n", "## Datenexport und -import von CSV-Datensätzen und Pandas DataFrames\n", "\n", "* Comma Separated Values (CSV) können von vielen Programmen weiterverarbeitet werden: pd.read_csv(\"daten.csv\")` zum Einlesen und zum Erstellen `df.to_csv(\"daten.csv\")` \n", "\n", "* Zum Laden von Python-Objekten ohne Konvertierung (nur für Python lesbar = Picklen): `df.to_pickle(\"daten.pkl\")` `pd.read_pickle(\"daten.pkl\")` \n", "\n", "* Sowohl zum Einlesen als auch Speichern einer Datei ist das aktuelle `Working directory` Ausgangspunkt und kann mit der Funktion `getcwd()` vom Basispaket `os` (operating system) abgefragt bzw. mittels `chdir()` angepasst werden. \n", "\n", "* Liegt die einzulesende Datei nicht direkt im Working Directory, welches üblicherweise dem Quellverzeichnis des Python-Skriptes bzw. des Jupyter-Notebooks entspricht, aber auch andersweitig gesetzt werden kann, muss der Pfad zur einzulesenden Datei angegeben werden. \n", " \n", "* Hier empfielt sich die Pfadangabe mittels **relativer Pfade**. Soll beispielsweise die Datei `DatenDatei.csv` in dem Unterordner `Daten`, startet der relative Pfad im aktuellen Verzeichnis, welches mit einem `.` gesetzt wird: \n", " ```python\n", " #relativer Pfad, ausgehend vom working directory\n", " df_import = pd.read_csv(\"./Daten/DatenDatei.csv\")\n", " ```\n", " Liegen die Daten hingegen in einem übergeordneten Ordner, gibt man dies im Pfad einfach mit zwei Punkten `..` an. Falls also beispielsweise in Windows im Ordner `C:\\Studium\\Finance\\Python-Kurs\\` es einen Unterordner `C:\\Studium\\Finance\\Python-Kurs\\Code` sowie einen Unterordner `C:\\Studium\\Finance\\Python-Kurs\\Daten` gibt und das aktuelle Working Directory `C:\\Studium\\Finance\\Python-Kurs\\Code` liegt, lautet der Aufruf\n", " ```python\n", " #relativer Pfad\n", " df_import = pd.read_csv(\"./../Daten/DatenDatei.csv\")\n", " ```\n", " Der erste Punkt besagt einfach starte im Working Directory, hier also im Ordner `C:\\Studium\\Finance\\Python-Kurs\\Code`. Durch die Angabe der zwei Punkte `..` wird dann in den übergeordneten Ordner `C:\\Studium\\Finance\\Python-Kurs\\` gewechselt und dort dann die CSV-Date im Ordner `Daten\\DatenDatei.csv` eingelesen. Der Vorteil gegenüber der Nutzung absoluter Pfade\n", " ```python\n", " #absoluter Pfad\n", " df_import = pd.read_csv(\"C:/Studium/Finance/Python-Kurs/Daten/DatenDatei.csv\")\n", " ``` \n", " liegt auf der Hand, denn dieser Aufruf funktioniert ausschließlich für die vorliegende Ordner-Struktur. Wollen Sie allerdings das Skript auf einem anderen Rechner laufen lassen, in dem obiger Ordner Python-Kurs sich beispielsweise auf `C:\\users\\danau\\Dokumente\\Finance\\Python-Kurs` befindet, müssten Sie den absoluten Pfad entsprechend anpassen\n", " ```python\n", " #absoluter Pfad\n", " df_import = pd.read_csv(\"C:/Studium/Finance/Python-Kurs/Daten/DatenDatei.csv\")\n", " #relativer Pfad\n", " df_import = pd.read_csv(\"./../Daten/DatenDatei.csv\")\n", " ```\n", " während der relative Pfad auch auf dem anderen Dateisystem funktioniert." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import os\n", "cwd = os.getcwd()\n", "print(cwd)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Iterationen über DataFrames als Alternative zu Apply / Map - Funktionen - to do!\n", "\n", "```{note}\n", "to do: \n", "* Iteration über DataFrames vs. apply() ???\n", "```\n", "\n", "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Alternative Pakete zum Arbeiten mit großen Datensätzen: Exkurs in das Arbeiten mit Polars\n", "\n", "**Polars** ist eine moderne Open-Source-Bibliothek für Datenanalyse in Python und Rust, die speziell für hohe Geschwindigkeit und Speicher­effizienz entwickelt wurde. Während `pandas` seit über einem Jahrzehnt der De-facto-Standard für tabellarische Daten in Python ist, stößt es bei sehr großen Datensätzen oder rechenintensiven Workflows oft an Performancegrenzen. Genau hier setzt Polars an: Die Bibliothek nutzt eine spaltenorientierte Speicherstruktur auf Basis von **Apache Arrow** und ist in Rust implementiert, wodurch sie sowohl äußerst schnell als auch ressourcenschonend arbeitet.\n", "\n", "Ein wesentlicher Unterschied liegt in der Arbeitsweise. Pandas ist überwiegend Single-Threaded, während Polars automatisch mehrere CPU-Kerne nutzt und dadurch auf modernen Rechnern deutlich schneller ist. Außerdem unterstützt Polars eine sogenannte Lazy API: Anweisungen werden nicht sofort ausgeführt, sondern zunächst gesammelt, optimiert und dann als effiziente Query-Pipeline berechnet. Das ermöglicht Polars, Berechnungen zu beschleunigen und unnötige Operationen zu vermeiden.\n", "\n", "Für Anwender fühlt sich Polars trotz dieser technischen Unterschiede vertraut an, da die zentrale Datenstruktur ebenfalls „DataFrame“ heißt. Viele Konzepte ähneln pandas, jedoch mit einer moderneren und oft konsistenteren Syntax. Der Nutzen für Praktiker liegt vor allem darin, dass Analysen, die mit pandas bei Millionen von Zeilen ins Stocken geraten, mit Polars problemlos und interaktiv möglich sind.\n", "\n", "Zusammengefasst ist Polars besonders dann eine sinnvolle Alternative zu `pandas`, wenn es um große Datenmengen, parallele Verarbeitung und hohe Geschwindigkeit geht. Pandas hingegen bleibt weiterhin sehr stark, wenn es um breite Unterstützung im Ökosystem, Kompatibilität mit bestehenden Bibliotheken und vielfältige Tutorials geht.\n", "\n", "Zunächst muss die **Polras**-Bibliothek installiert werden. **Conda** erledigt dies mit dem Konsolenbefehl:\n", "```bash\n", "conda install -c conda-forge polars\n", "```\n", "Alternativ kann das Paket auch mittels `Pip` installiert werden\n", "```bash\n", "pip install polars\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Wir möchten den Einsatz von `polars` an dem *Intraday Stock Data (1 min) – S&P 500 – 2008–21* Datensatz \n", "\n", " \n", "\n", "mit ca. 2 Millionen Zeilen demonstrieren. Dies ist ein umfangreiches, fein aufgelöstes Zeitreihen-Datenset mit Preisdaten auf Minutenebene. Er eignet sich ausgesprochen gut zur Analyse von Marktbewegungen im Tagesverlauf oder zur Entwicklung und Validierung kurzfristiger Handels- und Vorhersagestrategien. \n", "\n", "\n", "In Python importieren wir `polars` wie folgt.\n", "```bash\n", "import polars as pl\n", "````" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import polars as pl\n", "\n", "SP500 = pl.read_csv(\"./../../Data/SP500_08-21.csv\")\n", "SP500.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Wir sehen den Datentyp der jeweiligen Spalte in dem wir uns den Datframe mit `.head()` anzeigen lassen. Nachfolgend sehen wir, dass der `.tail()` analog zu `pandas` funktioniert. Sowohl beim `.head()` als auch bei `.tail()` wird uns die Anzahl der angezeigten Zeilen und Spalten wiedergegeben. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "SP500.tail()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Wenn wir uns die genaue Anzahl der Zeilen und Spalten wiedergeben lassen wollen, verwenden wir `.shape`. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "SP500.shape" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Mit dem Befehl `.glimpse` wird uns die Anzahl der gesamten Zeilen und Spalten und die Datentypen der jeweiligen Spalte wiedergegeben." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "SP500.glimpse" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Wollen wir mal die Importzeit zwischen `polars` und `pandas` vergleichen, um zu sehen, welches Package den Datensatz schneller importiert." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import time\n", "\n", "# Polars\n", "start_time_polars=time.time()\n", "SP500 = pl.read_csv(\"./../../Data/SP500_08-21.csv\")\n", "end_time_polars= time.time()\n", "\n", "# Pandas\n", "start_time_pandas=time.time()\n", "SP500_pd=pd.read_csv(\"./../../Data/SP500_08-21.csv\")\n", "end_time_pandas=time.time()\n", "\n", "print(f\"Die Importzeit mit Polars beträgt {end_time_polars - start_time_polars:.2f} Sekunden und mit Pandas {end_time_pandas - start_time_pandas:.2f} Sekunden.\")\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Nachfolgend stellen wir Ihnen eine Selecting-Befehle vor." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "SP500[1]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "SP500[-1]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "SP500[0:3]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "SP500[\"open\"]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "SP500[[\"open\", \"close\"]]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "SP500[:3, [\"date\", \"open\", \"close\"]]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "SP500.select(\"date\", \"volume\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "SP500.sort(\"volume\", descending=True)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "SP500.top_k(3, by=\"volume\") " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "SP500.describe()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Mit dem Befehl `pl.col()` integriert in `.select()` werden uns zwei bestimmte Spalten wiedergegeben. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "SP500.select(\n", " \"date\",\n", " pl.col(\"volume\")\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Wir können auch Modifizierungen ganz bequem durchführen. In unserem Codebeispiel kombinieren wir ein `pl.col()`, `.round()` und `.alias()`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "SP500.select(\n", " \"date\",\n", " (pl.col(\"close\")).round(2).alias(\"Schlusskurs\")\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Des weiteren ist möglich eine Spalte in unserem Dataframe zu ergänzen. Im nachfolgende Codebeispiel wollen wir sehen, ob Werte in der Spalte *volume* existieren. Dazu verwenden wir das Kommando `pl.lit()`. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "SP500.select(\n", " \"date\",\n", " (pl.col(\"volume\")).round(2).alias(\"Handelsvolumen\"),\n", " pl.lit(True).alias(\"available\")\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "SP500.select(\n", " \"date\", \n", " \"volume\",\n", " pl.col(\"volume\").mean().alias(\"average volume\"), \n", " pl.col(\"volume\").max().alias(\"max volume\")\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Wir wollen nun eine neue Spalte erzeugen, welche uns stets den Durchschnittskurs wiedergibt. Dazu verwenden wir `.with_columns`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "SP500.with_columns(\n", " ((pl.col(\"high\")+pl.col(\"low\"))*1/2).alias(\"average price\")\n", " )" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Wenn wir eine Spalte umbenennen wollen, können wir das mit `.rename()`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "SP500.rename({\n", " \"date\":\"Datum\"\n", "})" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Eine Spalte zu löschen funktioniert mit dem `drop()` Kommando." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "SP500.drop(\"barCount\", \"average\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Wir können auch mithilfe von `null.count()` die Anzahl der fehlenden Werte ausgeben lassen. \n", "\n", "**Bemerkung:** In `polars` werden die fehlenden Werte nicht mit NaN angegeben, sondern mit einer null." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "SP500.null_count()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Wir wollen an dieser Stelle den Exkurs in `polars` beenden. Interessierte finden unter \n", "\n", "Polars Package\n", " weitere Ausführung." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 4 }