Einführung in Pandas#
Für die folgenden Einführung nutzen wir einen Auszug aus den Daten, die wir im nächsten Abschnitt dann selbstständig sammeln und aufbereiten werden. Hier nutzen wir diese Daten, um in die grundlegenden Funktionen von pandas einzusteigen.
Import#
Wir haben die import-Anweisung ergänzt um as pd. Sie können alle Bibliotheken beim Importieren mit dem Schlüsselwort as umbenennen. Für pandas hat es sich etabliert, die Bibliothek als pd zu importieren. Das ist einfach eine Konvention geworden. Das Umbenennen von externen Bibliotheken beim Import ist besonders dann sinnvoll, wenn die Namen der Bibliotheken sehr lang sind. Beim Aufruf der jeweiligen Funktionen erspart einem das ein wenig Schreibarbeit.
Haben wir pandas nun erfolgreich importiert, können wir auf alle Funktionalitäten und Datenstrukturen zugreifen. Das werden wir uns direkt mit unserem Datensatz genauer anschauen.
import pandas as pd
Daten, die in einer CSV-Datei vorliegen, können auf die Weise einfach in einen Dataframe eingelesen werden. In der folgenden Zelle rufen wir mit pd.read_csv() (zur Dokumentation) die in Pandas enthaltene Funktion zum Einlesen einer CSV-Datei auf und wandeln unsere Daten zugleich in ein DataFrame-Objekt um. Dataframes und Series sind die Datenstrukturen, mit denen am meisten gearbeitet wird.
Pandas unterstützt den Import zahlreicher Dateiformate; alle entsprechenden Funktionen beginnen mit dem Präfix read_. Es gibt weitere Möglichkeiten, Daten aus einer list, einem dictionary oder aus anderen Dateiformaten wie json oder xml einzulesen. Wenn Sie die Dokumentation zu dieser Funktion aufrufen, dann sehen Sie, dass wir eine ganze Reihe von Parametern optional spezifieren können. Der hier beim Einlesen übergebene Parameter parse_dates wandelt diese Datenspalte in ein datetime-Objekt um - auf diese Weise können datumspezifische Operationen leicht durchgeführt werden.
Notwendig ist die Angabe des Dateinamen bzw. -pfads. Optional ergänzen wir die Angabe zur Kodierung. Standardmäßig wird angenommen, dass unser Delimiter, das Trennzeichen, ein Komma ist und dass die erste Zeile der CSV die Spaltenüberschriften repräsentiert.
df = pd.read_csv('data/AvH-letters-subset.csv', encoding='utf8', parse_dates=['date'])
Daten inspizieren#
Als nächstes lassen wir uns die ersten fünf Zeilen unseres Datenkorpus anzeigen. Das reicht aus, um zu prüfen, ob die Daten korrekt importiert wurden. Das sollte man immer machen, wenn man einen neuen Datensatz in Pandas öffnet.
Außerdem wenden wir das für DataFrame-Objekte verfügbare Attribut shape (zur Dokumentation) auf unser Datenkorpus an. Diese Methode gibt ein Tupel zurück, das Aufschluss über die Ausmaße der Dimensionalität unseres Dataframes gibt. Das erste Element repräsentiert gewissermaßen die Zeilen und das zweite Element die Spalten. Beachten Sie hierbei den Unterschied zu Funktionen: Bei Attributen benötigen Sie keine runden Klammern, denn Attribute repräsentieren Eigenschaften eines Objektes, während Funktionen etwas mit dem Objekt machen und dafür ggf. weitere Parameter benötigen. Mit df.head() zeigen wir die ersten fünf Zeilen an.
print(df.shape)
print(f'Der Dataframe hat {df.shape[0]} Zeilen und {df.shape[1]} Spalten.')
df.head()
Um schließlich noch einen Überblick über die Spalten (columns) zu erhalten, können wir das Attribut columns für unseren Dataframe verwenden. Werden die zurückgegebenen Werte einer Variablen zugewiesen, dann können Sie auch leicht mit dieser Information weiterarbeiten:
df.columns
Und sehr nützlich: Jede der einzelnen Spalten ist zugleich ein Attribut des DataFrame-Objekts. Das heißt, Sie können den Spaltennamen direkt an unsere Variable für das DataFrame-Objekt anhängen und erhalten die Inhalte der jeweiligen Spalte als Series-Objekt zurück, um die Informationen auf eine beliebige Art und Weise weiterzuverarbeiten.
df.loc[:, 'sender']
Wir können uns unsere Daten natürlich auch etwas genauer anzeigen lassen und konkretisieren, welche Bereiche wir in den Blick nehmen wollen.
Mit der Funktion head() lassen wir uns die ersten n Zeilen anzeigen (aber nicht alle Spalten):
df.head(10)
Der Konterpart dazu ist die Funktion tail(), mit der Sie die letzten n Zeilen des Datensatzes ausgeben können:
df.tail(10)
Um einen kompletten Überblick über ein Datenkorpus zu bekommen und beispielsweise auch zu prüfen, wie pandas die in den einzelnen Spalten enthaltenen Datentypen interpretiert hat, rufen wir die Funktion info() auf.
Grundsätzlich bestätigt uns die erste Zeile des Outputs zunächst einmal, dass es sich tatsächlich um ein DataFrame-Objekt handelt. Dieses DataFrame-Objekt besteht aus n Einträgen (= Zeilen) mit einer Index-Spanne von n zu n (hier 0 bis 49). Jede Zeile unseres Korpus lässt sich also über eine Indexposition ansteuern. Ferner erhalten wir eine Information darüber, wie viele Spalten die Tabelle enthält, hier 9. Viele der Spalten enthalten in jeder Zeile Werte. Einige der Spalten haben aber auch eine sehr viel größere Anzahl leerer Datenfelder. Die meisten unserer Spalten enthalten Daten vom Datentyp string (hier: object). Lediglich die Spalte date wird als Datetime (datetime64) interpretiert. In der vorletzten Zeile erhalten wir noch einmal eine Zusammenfassung der in unserem DataFrame enthaltenen Datentypen. Die letzte Zeile gibt Aufschluss über die benötigten Arbeitsspeicher-Ressourcen, die für das Speichern des DataFrames benötigt werden.
df.info()
Wir können checken, wie viele Zellen in den jeweiligen Spalten keine Werte enthalten.
df.isna().sum()
DataFrame-Objekte verfügen noch über eine ganze Reihe weiterer Attribute und Funktionen. Am besten überfliegen Sie einmal die Dokumentation und probieren das ein oder andere aus, um ein Gefühl für die Möglichkeiten zu bekommen. Die Dokumentation sollte man ohnehin immer schnell zur Hand haben, denn niemand merkt sich alle Befehle, auch die besten Programmierer:innen müssen manchmal noch grundlegende Aspekte wieder nachschlagen. Wichtig ist, dass Sie wissen, wo Sie fündig werden und eine ungefähre Idee davon haben, was eine Bibliothek kann. Sie müssen nicht alles auswendig lernen.
Auswählen von Spalten#
Eine einzelne Spalte eines Dataframes wird bei Pandas als Series bezeichnet. Auf Series kann man eigene spezifische Methoden anwenden. Es gibt verschiedene Schreibweisen, um eine Spalte auszuwählen. Wir zeigen hier verschiedene Möglichkeiten, wie die Auswahl von Spalten erfolgen kann. Sicherlich werden Sie auf diese Varianten treffen, wenn Sie den Code anderer lesen. Wir werden in diesem Jupyter Book die beiden letzten Optionen mit iloc und loc nutzen. Dies ist die beste Option und expliziteste Schreibweise, um Komplikationen bei einer möglichen Doppelbenennung von Spaltennamen und Python-Keywords zu verhindern.
df.loc[zeile, spalte]
df['sender']
df.sender
df.iloc[:, 3]
df.loc[:, 'sender']
einzelne Zellen auswählen#
df.loc[0, 'sender']
df.iloc[0, 3]
# Ausgabe der ersten fünf Zeilen der Spalte sender
df.loc[0:5, 'sender']
# Ausgabe der ersten Zeile
df.loc[0, :]
Kategorische Daten#
Wir können die Anzahl der mindestens einmal auftretenden Werte einer Spalte sowie diese Werte selbst ausgeben. Auch eine Übersicht über die Topwerte dieser kategorischen Datenspalte ist möglich.
df.loc[: , 'sender'].nunique()
df.loc[: , 'sender'].unique()
df.loc[: , 'sender'].describe()
unique: Die Anzahl der einzigartigen Werte in der Spalte. (kann auf Redundanzen hinweisen!)
top: Der häufigste Wert in der Spalte.
freq: Die Häufigkeit (Anzahl der Vorkommen) des häufigsten Wertes.
Erstellen einer neuen Spalte#
Neue Spalten können dem Dataframe leicht hinzugefügt werden. Wir werden zwei Spalten erstellen, die im konkreten Fall nicht unbedingt Aussagekraft besitzen; das ist bei diesem Beispeil auch nicht gewollt. Ein Vorgehen sieht wie folgt aus:
# zusammensetzen von zwei Spalten
df.loc[:, 'sender_to_receiver'] = df.loc[:, 'sender'] + ' => ' + df.loc[:, 'receiver']
# Wert für neue Spalte berechnen
df.loc[:, 'length_sender_name'] = df.loc[:, 'sender'].apply(len)
print(df.info())
df.head(5)
Abfragen mit boolscher Maske#
Mit der Hilfe von Maskierung, d.h. mit der Angabe True bzw. False bei einer bestimmten Abfrage, kann der Dataframe auf Grundlage dieser Abfrage gefiltert werden. Die zwei Beispiele sollen die Funktionsweise veranschaulichen.
Wie oft kommt das Adelsprädikat ‘von’ in den Namen der Briefempfänger vor?
query = df.loc[:, 'receiver'].str.contains('von').sum()
print(query)
mask = df.loc[:, 'receiver'].str.contains('von')
df_von = df.loc[mask, :]
df_von.loc[:, 'receiver'].head()
Wie oft ist ‘Bayreuth’ der Wert für den Ortsname in der Spalte place?
mask = df.loc[:, 'place'] == 'Bayreuth'
df_place = df.loc[mask, :]
print(df_place.shape[0])
df_place.loc[:, 'place'].head(7)