8.1 Series und DataFrame#
Einfache Listen reichen nicht aus, um größere Datenmengen oder Tabellen
effizient zu speichern. Dazu benutzen Data Scientists die Datentypen Series
oder DataFrame
aus dem Modul Pandas. Daher werden wir uns in diesem Kapitel
mit diesen beiden Datentypen beschäftigen. Darüber hinaus lernen wir das häufig
verwendete Datenformat csv
kennen.
Lernziele#
Lernziele
Sie können Pandas mit der üblichen Abkürzung pd importieren.
Sie können aus einer Liste das Datenobjekt Series erzeugen.
Sie kennen das CSV-Dateiformat.
Sie können eine csv-Datei mit read_csv() einlesen.
Sie konnen mit .info() sich einen Überblick über die importierten Daten verschaffen.
Import von pandas#
Pandas ist eine Bibliothek zur Verarbeitung und Analyse von Daten in Form von Datenreihen und Tabellen. Die beiden grundlegenden Datenstrukturen sind Series und DataFrame. Dabei wird Series für Datenreihen genommen, also sozusagen die Verallgemeinerung von Vektoren bzw. eindimensionalen Arrays. Der Datentyp DataFrame repräsentiert Tabellen, also sozusagen Matrizen bzw. verallgemeinerte zweidimensionale Arrays.
Um das Modul pandas benutzen zu können, müssen wir es zunächst importieren. Es ist üblich, dabei dem Modul die Abkürzung pd zu geben, damit wir nicht immer pandas schreiben müssen, wenn wir eine Funktion aus dem pandas-Modul benutzen.
import pandas as pd # kürze das Modul pandas als pd ab, um Schreibarbeit zu sparen
Series aus Liste erzeugen#
Der Datentyp Series speichert Datenreihen. Liegt beispielsweise eine Reihe von
Daten vor, die in einer Variable vom Datentyp Liste gespeichert ist, so wird
über die Methode pd.Series(liste)
ein neues Series-Objekt erzeugt, dass die
Listenelemente enthält. Im folgenden Beispiel haben wir Altersangaben in einer
Liste, also [25, 22, 43, 37]
und initialisieren über pd.Series()
die
Variable alter
:
alter = pd.Series([25, 22, 43, 37])
print(alter)
0 25
1 22
2 43
3 37
dtype: int64
Was ist aber jetzt der Vorteil von Pandas? Warum nicht einfach bei der Liste bleiben oder aber, wenn Performance wichtig sein sollte, ein eindimensionales Numpy-Array nehmen? Der wichtigste Unterschied ist der Index.
Bei einer Liste oder einem Numpy-Array ist der Index implizit definiert. Damit
ist gemeint, dass bei der Initialisierung automatisch ein Index 0, 1, 2, 3, …
angelegt wird. Wenn bei einer Liste l = [25, 22, 43, 37]
auf das zweite
Element zugegriffen werden soll, dann verwenden wir den Index 1 (zur Erinnerung:
Python zählt ab 0) und schreiben
l = [25, 22, 43, 37]
print("2. Element der Liste: ", l[1])
2. Element der Liste: 22
Die Datenstruktur Series ermöglich es aber, einen expliziten Index zu setzen.
Über den optionalen Parameter index=
speichern wir als Zusatzinformation noch
ab, von welcher Person das Alter abgefragt wurde. In dem Fall sind es die vier
Personen Alice, Bob, Charlie und Dora.
alter = pd.Series([25, 22, 43, 30], index=["Alice", "Bob", "Charlie", "Dora"])
print(alter)
Alice 25
Bob 22
Charlie 43
Dora 30
dtype: int64
Jetzt ist auch klar, warum beim ersten Mal, als wir print(alter)
ausgeführt
haben, die Zahlen 0, 1, 2, 3 ausgegeben wurden. Zu dem Zeitpunkt hatte das
Series-Objekt noch einen impliziten Index wie eine Liste. Was noch an
Informationen ausgegeben wird, ist das Attribut dtype
. Darin gespeichert ist
der Datentyp der gespeicherten Werte. Auf dieses Attribut kann auch direkt mit
dem Punktoperator zugegegriffen werden.
print(alter.dtype)
int64
Offensichtlich sind die gespeicherten Werte Integer.
Mini-Übung
Erzeugen Sie ein Series-Objekt mit den Wochentagen als Index und der Anzahl der Vorlesungs/Übungs-Stunden an diesem Wochentag.
# Hier Ihr Code:
Lösung
stundenplan = pd.Series([4, 0, 4, 6, 8], index=["Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag"])
print(stundenplan)
DataFrame für Tabellen#
Bei Auswertung von Messungen ist aber der häufigste Fall der, dass Daten in Form einer Tabelle vorliegen. Ein DataFrame-Objekt entspricht einer Tabelle, wie man sie beispielsweise von Excel, LibreOffice oder Numbers kennt. Sowohl Zeile als auch Spalten sind indiziert. Typischerweise werden die Daten in der Tabelle zeilenweise angeordnet. Damit ist gemeint, dass jede Zeile einen Datensatz darstellt und die Spalten die Eigenschaften speichern.
Ein DataFrame kann direkt über mehrere Pandas-Series-Objekte oder verschachtelte Listen erzeugt werden. Da es in der Praxis nur selten vorkommt und nur für sehr kleine Datenmengen praktikabel ist, Daten händisch zu erfassen, fokussieren wir gleich auf die Erzeugung von DataFrame-Objekten aus einer Datei.
Import von Tabellen#
Tabellen liegen werden oft in dem Dateiformat abgespeichert, das die jeweilige Tabellenkalkulationssoftware Excel, Numbers oder OpenOfficeCalc als Standard eingestellt hat. Wir betrachten in dieser Vorlesung aber primär Tabellen, die in einem offenen Standardformat vorliegen und damit unabhängig von der verwendeten Software und dem verwendeten Betriebssystem sind. Der Import von Excel wird kurz gestreift.
Import von Tabellen im CSV-Format#
Das Dateiformat CSV speichert Daten zeilenweise ab. Dabei steht CSV für “comma separated value”. Die Trennung der Spalten erfolgt durch ein Trennzeichen, normalerweise durch das Komma. Im deutschsprachigen Raum wird gelegentlich ein Semikolon verwendet, weil Dezimalzahlen das Komma zum Abtrennen der Nacchkommastellen verwenden.
Um Tabellen im csv-Format einzulesen, bietet Pandas eine eigene Funktion namens
read_csv
an (siehe
Dokumentation/read_csv).
Wird diese Funktion verwendet, um die Daten zu importieren, so wird automatisch
ein DataFrame-Objekt erzeugt. Beim Aufruf der Funktion wird der Dateiname
übergeben, aber beispielweise könnte auch ein anderes Trennzeichen eingestellt werden.
Am besten sehen wir uns die Funktionsweise von read_csv
an einem Beispiel an.
Sollten Sie mit einem lokalen JupyterNotebook arbeiten, laden Sie bitte die
Datei
bundesliga_top7_offensive.csv
herunter und speichern Sie sie in denselben Ordner, in dem auch dieses
JupyterNotebook liegt. Die csv-Datei stammt von
Kaggle.
Wie der Name schon verrät, sind darin Spielerdaten zu den Top7-Fußballvereinen
der Bundesligasaison 2020/21 enthalten.
Führen Sie dann anschließend die folgende Code-Zelle aus.
import pandas as pd
data = pd.read_csv('bundesliga_top7_offensive.csv')
Es erscheint keine Fehlermeldung, aber den Inhalt der geladenen Datei sehen wir
trotzdem nicht. Dazu verwenden wir die Methode .head()
.
data.head()
Name | Club | Nationality | Position | Age | Matches | Starts | Mins | Goals | Assists | Penalty_Goals | Penalty_Attempted | xG | xA | Yellow_Cards | Red_Cards | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | Manuel Neuer | Bayern Munich | GER | GK | 34 | 33 | 33 | 2970 | 0 | 0 | 0 | 0 | 0.00 | 0.01 | 1 | 0 |
1 | Thomas Müller | Bayern Munich | GER | MF | 30 | 32 | 31 | 2674 | 11 | 19 | 1 | 1 | 0.24 | 0.39 | 0 | 0 |
2 | David Alaba | Bayern Munich | AUT | DF,MF | 28 | 32 | 30 | 2675 | 2 | 4 | 0 | 0 | 0.04 | 0.08 | 4 | 0 |
3 | Jérôme Boateng | Bayern Munich | GER | DF | 31 | 29 | 29 | 2368 | 1 | 1 | 0 | 0 | 0.01 | 0.02 | 6 | 0 |
4 | Robert Lewandowski | Bayern Munich | POL | FW | 31 | 29 | 28 | 2458 | 41 | 7 | 8 | 9 | 1.16 | 0.13 | 4 | 0 |
Die Methode .head()
zeigt uns die ersten fünf Zeilen der Tabelle an. Wenn wir
beispielsweise die ersten 10 Zeilen anzeigen lassen wollen, so verwenden wir die
Methode .head(10)
mit dem Argument 10.
data.head(10)
Name | Club | Nationality | Position | Age | Matches | Starts | Mins | Goals | Assists | Penalty_Goals | Penalty_Attempted | xG | xA | Yellow_Cards | Red_Cards | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | Manuel Neuer | Bayern Munich | GER | GK | 34 | 33 | 33 | 2970 | 0 | 0 | 0 | 0 | 0.00 | 0.01 | 1 | 0 |
1 | Thomas Müller | Bayern Munich | GER | MF | 30 | 32 | 31 | 2674 | 11 | 19 | 1 | 1 | 0.24 | 0.39 | 0 | 0 |
2 | David Alaba | Bayern Munich | AUT | DF,MF | 28 | 32 | 30 | 2675 | 2 | 4 | 0 | 0 | 0.04 | 0.08 | 4 | 0 |
3 | Jérôme Boateng | Bayern Munich | GER | DF | 31 | 29 | 29 | 2368 | 1 | 1 | 0 | 0 | 0.01 | 0.02 | 6 | 0 |
4 | Robert Lewandowski | Bayern Munich | POL | FW | 31 | 29 | 28 | 2458 | 41 | 7 | 8 | 9 | 1.16 | 0.13 | 4 | 0 |
5 | Joshua Kimmich | Bayern Munich | GER | MF | 25 | 27 | 25 | 2194 | 4 | 10 | 0 | 0 | 0.10 | 0.27 | 4 | 0 |
6 | Kingsley Coman | Bayern Munich | FRA | FW,MF | 24 | 29 | 23 | 1752 | 5 | 10 | 0 | 0 | 0.21 | 0.34 | 1 | 0 |
7 | Benjamin Pavard | Bayern Munich | FRA | DF | 24 | 24 | 22 | 1943 | 0 | 0 | 0 | 0 | 0.02 | 0.09 | 3 | 0 |
8 | Alphonso Davies | Bayern Munich | CAN | DF | 19 | 23 | 22 | 1763 | 1 | 2 | 0 | 0 | 0.01 | 0.04 | 2 | 1 |
9 | Serge Gnabry | Bayern Munich | GER | FW,MF | 25 | 27 | 20 | 1644 | 10 | 2 | 0 | 0 | 0.44 | 0.25 | 4 | 0 |
Offensichtlich wurde beim Import der Daten wieder ein impliziter Index 0, 1, 2,
usw. gesetzt. Das ist nicht weiter verwunderlich, denn Pandas kann nicht wissen,
welche Spalte wir als Index vorgesehen haben. Und manchmal ist ein automatisch
erzeugter impliziter Index auch nicht schlecht. In diesem Fall würden wir aber
gerne als Zeilenindex die Namen der Spieler verwenden. Daher modifizieren wir
den Befehl mit index_col=
. Die Namen stehen in der 1. Spalte, was in
Python-Zählweise einer 0 entspricht.
data = pd.read_csv('bundesliga_top7_offensive.csv', index_col=0)
data.head(10)
Club | Nationality | Position | Age | Matches | Starts | Mins | Goals | Assists | Penalty_Goals | Penalty_Attempted | xG | xA | Yellow_Cards | Red_Cards | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Name | |||||||||||||||
Manuel Neuer | Bayern Munich | GER | GK | 34 | 33 | 33 | 2970 | 0 | 0 | 0 | 0 | 0.00 | 0.01 | 1 | 0 |
Thomas Müller | Bayern Munich | GER | MF | 30 | 32 | 31 | 2674 | 11 | 19 | 1 | 1 | 0.24 | 0.39 | 0 | 0 |
David Alaba | Bayern Munich | AUT | DF,MF | 28 | 32 | 30 | 2675 | 2 | 4 | 0 | 0 | 0.04 | 0.08 | 4 | 0 |
Jérôme Boateng | Bayern Munich | GER | DF | 31 | 29 | 29 | 2368 | 1 | 1 | 0 | 0 | 0.01 | 0.02 | 6 | 0 |
Robert Lewandowski | Bayern Munich | POL | FW | 31 | 29 | 28 | 2458 | 41 | 7 | 8 | 9 | 1.16 | 0.13 | 4 | 0 |
Joshua Kimmich | Bayern Munich | GER | MF | 25 | 27 | 25 | 2194 | 4 | 10 | 0 | 0 | 0.10 | 0.27 | 4 | 0 |
Kingsley Coman | Bayern Munich | FRA | FW,MF | 24 | 29 | 23 | 1752 | 5 | 10 | 0 | 0 | 0.21 | 0.34 | 1 | 0 |
Benjamin Pavard | Bayern Munich | FRA | DF | 24 | 24 | 22 | 1943 | 0 | 0 | 0 | 0 | 0.02 | 0.09 | 3 | 0 |
Alphonso Davies | Bayern Munich | CAN | DF | 19 | 23 | 22 | 1763 | 1 | 2 | 0 | 0 | 0.01 | 0.04 | 2 | 1 |
Serge Gnabry | Bayern Munich | GER | FW,MF | 25 | 27 | 20 | 1644 | 10 | 2 | 0 | 0 | 0.44 | 0.25 | 4 | 0 |
Import von Tabellen im xlsx-Format#
Eine sehr bekannte Tabellenkalkulationssoftware ist Excel von Microsoft. Excel
bringt sein eigenens proprietäres Datenformat mit, in der Regel erkennbar an der
Dateiendung .xlsx
. Laden Sie sich den Datensatz zu den Top7-Bundesligavereinen
als Excel-Datei
bundesliga_top7_offensive.xlsx
herunter.
data = pd.read_excel('bundesliga_top7_offensive.xlsx', index_col=0)
data.head(5)
Club | Nationality | Position | Age | Matches | Starts | Mins | Goals | Assists | Penalty_Goals | Penalty_Attempted | xG | xA | Yellow_Cards | Red_Cards | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Name | |||||||||||||||
Manuel Neuer | Bayern Munich | GER | GK | 34 | 33 | 33 | 2970 | 0 | 0 | 0 | 0 | 0.00 | 0.01 | 1 | 0 |
Thomas Müller | Bayern Munich | GER | MF | 30 | 32 | 31 | 2674 | 11 | 19 | 1 | 1 | 0.24 | 0.39 | 0 | 0 |
David Alaba | Bayern Munich | AUT | DF,MF | 28 | 32 | 30 | 2675 | 2 | 4 | 0 | 0 | 0.04 | 0.08 | 4 | 0 |
Jérôme Boateng | Bayern Munich | GER | DF | 31 | 29 | 29 | 2368 | 1 | 1 | 0 | 0 | 0.01 | 0.02 | 6 | 0 |
Robert Lewandowski | Bayern Munich | POL | FW | 31 | 29 | 28 | 2458 | 41 | 7 | 8 | 9 | 1.16 | 0.13 | 4 | 0 |
Vermutlich erhalten Sie zunächst eine Fehlermeldung: Missing optional dependency 'openpyxl'. Use pip or conda to install openpyxl.
Falls das der
Fall sein sollte und Sie interessiert daran sind, Excel-Dateien lesen und
schreiben zu können, installieren Sie bitte das Modul openpyxl
mit !conda install openpyxl
oder !pip install openpyxl
nach. In dieser Vorlesung
verwenden wir nur CSV-Dateien, so dass ein Nachinstallieren für die
Vorlesung/Übung nicht notwendig ist.
Übersicht verschaffen mit info#
Das obige Beispiel zeigt uns zwar nun die ersten 10 Zeilen des importierten
Datensatzes, aber wie viele Daten insgesamt enthalten sind oder welche Vereine
noch kommen, können wir mit der .head()
-Methode nicht erfassen. Dafür stellt
Pandas die Methode .info()
zur Verfügung. Probieren wir es einfach aus.
data.info()
<class 'pandas.core.frame.DataFrame'>
Index: 177 entries, Manuel Neuer to Joshua Mees
Data columns (total 15 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 Club 177 non-null object
1 Nationality 177 non-null object
2 Position 177 non-null object
3 Age 177 non-null int64
4 Matches 177 non-null int64
5 Starts 177 non-null int64
6 Mins 177 non-null int64
7 Goals 177 non-null int64
8 Assists 177 non-null int64
9 Penalty_Goals 177 non-null int64
10 Penalty_Attempted 177 non-null int64
11 xG 177 non-null float64
12 xA 177 non-null float64
13 Yellow_Cards 177 non-null int64
14 Red_Cards 177 non-null int64
dtypes: float64(2), int64(10), object(3)
memory usage: 22.1+ KB
Mit .info()
erhalten wir eine Übersicht, wie viele Spalten es gibt und auch
die Spaltenüberschriften werden aufgelistet. Dabei sind Überschriften wie Name
selbsterklärend, aber was xG
bedeutet, erschließt sich nicht von selbst. Dazu
brauchen wir mehr Informationen von den Autor:innen der Daten.
Weiterhin entnehmen wir der Ausgabe von .info()
, dass in jeder Spalte 177
Einträge sind, die ‘non-null’ sind. Damit ist gemeint, dass diese Zellen beim
Import nicht leer waren. Zudem wird bei jeder Spalte noch der Datentyp
angegeben. Für die Namen, die als Strings gespeichert sind, wird der allgemeine
Datentyp ‘object’ angegeben. Beim Alter/Age wurden korrektweise Integer erkannt
und die mittlere erwartete Anzahl von Toren pro Spiel ‘xG’ (= expected number of
goals from the player in a match) wird als Float angegeben.