Pandas Einführung
Die Pandas, über die wir in diesem Kapitel schreiben, haben nichts mit den süßen Panda-Bären zu tun und süße Bären sind auch nicht das, was unsere Besucher hier in einem Python-Tutorial erwarten. Pandas ist ein Python-Modul, dass die Möglichkeiten von Numpy, Scipy und Matplotlib abrundet. Das Wort Pandas ist ein Akronym und ist abgleitet aus "Python and data analysis" und "panal data".
Pandas ist eine Software-Bibliothek die für Python geschrieben wurde. Sie wird für Daten-Manipulation und -Analyse verwendet. Sie stellt spezielle Funktionen und Datenstrukturen zur Verfügung für die Manipulation von numerischen Tabellen und Zeit-Serien. Pandas ist eine freie Software und wurde unter der Drei-Klausel-BSD-Lizenz veröffentlicht.
Oft gibt es Verwirrung darüber, ob Pandas nicht eine Alternative zu Numpy, Scipy und Matplotlib sei. Die Wahrheit ist aber, dass Pandas auf Numpy aufbaut. Das bedeutet auch, dass Numpy für Pandas Voraussetzung ist. Scipy und Matplotlib werden von Pandas nicht grundlegend benötigt, sind aber eine wertvolle Ergänzung. Deshalb listet das Pandas-Projekt diese auch als "optionale Abhängigkeiten".
Daten-Strukturen in Pandas
Wir beginnen mit beiden wichtigsten Daten-Strukturen von Pandas:
- Series und
- DataFrame
Series
Eine Series ist ein eindimensionales Array-ähnliches Objekt. Es kann verschiedene Daten-Typen aufnehmen, z.B. Integers, Floats, Strings, Python-Objekte, usw.
Eine Series kann als eine Datenstruktur mit zwei Arrays angesehen werden: Ein Array fungiert als Index, d.h. als Bezeichner (Label), und ein Array beinhaltet die aktuellen Daten (Werte).
Wir definieren im folgenden Beispiel ein einfaches Series-Objekt, indem wir dieses Objekt mit einer Liste instanziieren. Wir werden später sehen, dass wir auch andere Daten-Objekte verwenden können, z.B. Numpy-Arrays und Dictionaries.
import pandas as pd S = pd.Series([11, 28, 72, 3, 5, 8]) print(S)
0 11 1 28 2 72 3 3 4 5 5 8 dtype: int64
Wir haben in unserem Beispiel keinen Index definiert. Trotzdem sehen wir zwei Spalten in der Ausgabe: Die rechte Spalte zeigt unsere Daten, die linke Spalte stellt den Index dar. Pandas erstellt einen Default-Index, der bei 0 beginnt und bis 5 läuft.
Wir können direkt auf die Indizes und die Werte der Series S zugreifen:
print(S.index) print(S.values)
RangeIndex(start=0, stop=6, step=1) [11 28 72 3 5 8]
Wenn wir dies mit der Erstellung eines Arrays in NumPy vergleichen, stellen wir viele Gemeinsamkeiten fest:
import numpy as np X = np.array([11, 28, 72, 3, 5, 8]) print(X) print(S.values) # both are the same type: print(type(S.values), type(X))
[11 28 72 3 5 8] [11 28 72 3 5 8] <class 'numpy.ndarray'> <class 'numpy.ndarray'>
Bis hierhin unterscheiden sich die Series noch nicht wirklich von den ndarrays aus Numpy. Das ändert sich aber, sobald wir Series-Objekte mit individuellen Indizes definieren:
fruits = ['apples', 'oranges', 'cherries', 'pears'] quantities = [20, 33, 52, 10] S = pd.Series(quantities, index=fruits) print(S)
apples 20 oranges 33 cherries 52 pears 10 dtype: int64
Eine großer Vorteil gegenüber Numpy-Arrays ist hier ganz offensichtlich: Wir können beliebige Indizes verwenden.
Wenn wir zwei Series-Objekte mit denselben Indizes addieren, so erhalten wir ein neues Series-Objekt mit diesem Index, und die Werte entsprechen den Summen der entsprechenden Werte aus den beiden Series-Objekten.
fruits = ['apples', 'oranges', 'cherries', 'pears'] S = pd.Series([20, 33, 52, 10], index=fruits) S2 = pd.Series([17, 13, 31, 32], index=fruits) print(S + S2) print("Summe aus S: ", sum(S))
apples 37 oranges 46 cherries 83 pears 42 dtype: int64 Summe aus S: 115
Die Indizes müssen nicht identisch sein für die Addition von Series-Typen. Der resultierende Index ist eine "Vereinigung" beider Indizes. Wenn ein Index nicht in beiden Series-Objekten vorkommt, so wird der entsprechende Wert auf NaN gesetzt:
fruits = ['peaches', 'oranges', 'cherries', 'pears'] fruits2 = ['raspberries', 'oranges', 'cherries', 'pears'] S = pd.Series([20, 33, 52, 10], index=fruits) S2 = pd.Series([17, 13, 31, 32], index=fruits2) print(S + S2)
cherries 83.0 oranges 46.0 peaches NaN pears 42.0 raspberries NaN dtype: float64
Prinzipiell können die Indizes auch komplett verschieden sein, wie im folgenden Beispiel. Der zweite Index besteht aus der türkischen Übersetzung der englischen Fruchtnamen:
fruits = ['apples', 'oranges', 'cherries', 'pears'] fruits_tr = ['elma', 'portakal', 'kiraz', 'armut'] S = pd.Series([20, 33, 52, 10], index=fruits) S2 = pd.Series([17, 13, 31, 32], index=fruits_tr) print(S + S2)
apples NaN armut NaN cherries NaN elma NaN kiraz NaN oranges NaN pears NaN portakal NaN dtype: float64
Indizierung
Es ist möglich, auf einzelne Werte eines Series-Objektes zuzugreifen:
print(S['apples'])
20
Man kann auch auf mehrere Indizes gleichzeitig zugreifen, wenn man die Indices als Liste übergibt:
print(S[['apples', 'oranges', 'cherries']])
apples 20 oranges 33 cherries 52 dtype: int64
Filterung mit einem Booleschen Array:
S[S>30]Der obige Code führt zu folgendem Ergebnis:
oranges 33 cherries 52 dtype: int64
Wie bei Numpy sind auch Operationen mit Skalaren oder die Anwendung von mathematischen Funktionen auf ein Series-Objekt möglich:
import numpy as np print((S + 3) * 4) print("======================") print(np.sin(S))
apples 92 oranges 144 cherries 220 pears 52 dtype: int64 ====================== apples 0.912945 oranges 0.999912 cherries 0.986628 pears -0.544021 dtype: float64
pandas.Series.apply
Series.apply(func, convert_dtype=True, args=(), **kwds)
Die Funktion "func" wird auf das Series-Objekt angwendet und liefert, in Abhängigkeit von "func", entweder ein Series-Objekt oder ein DataFrame-Objekt zurück.
Parameter | Bedeutung |
---|---|
func | Eine Funktion, die auf das gesamte Series-Objekt (Numpy-Funktion) oder nur auf einzelne Werte des Series (Python-Funktion) angewendet wird. |
convert_dtype | Ein Boolescher Wert. Wenn dieser auf True gesetzt wird (Standard), so wird versucht, bei der Anwendung einen besseren dtype für die elementweisen Funktions-Ergebnisse zu finden. Wenn der Parameter auf False gesetzt wird, so wird dtype=objekt verwendet. |
args | Positions-Argumente, die an die Funktion "func" übergeben werden, zusätzlich zu den Werten des Series-Objektes. |
**kwds | Zusätzliche Schlüsselwort-Argumente, die als Schlüsselworte an die Funktion übergeben werden. |
Beispiel:
S.apply(np.log)Wir können die folgende Ausgabe erwarten, wenn wir den obigen Python-Code ausführen:
apples 2.995732 oranges 3.496508 cherries 3.951244 pears 2.302585 dtype: float64
Wir können auch Python-Lambda-Funktionen benutzen. Wir werden nun die Anzahl der Früchte prüfen: Wenn weniger als 50 von einer Sorte vorhanden sind, so soll der Bestand um 10 erhöht werden. Ansonsten lassen wir den Betrag unverändert:
S.apply(lambda x: x if x > 50 else x+10 )Wir können die folgenden Ergebnisse erwarten, wenn wir den obigen Python-Code ausführen:
apples 30 oranges 43 cherries 52 pears 20 dtype: int64
Zusammenhang zu Dictionaries
Ein Series-Objekt kann angesehen werden wie ein geordnetes Python-Dictionary mit einer festen Länge.
Wir können bei der Erstellung eines Series-Objektes ein Dictionary übergeben. Wir erhalten ein Series-Objekt mit den Schlüsseln des Dictionarys als Indizes.
cities = {"London": 8615246, "Berlin": 3562166, "Madrid": 3165235, "Rome": 2874038, "Paris": 2273305, "Vienna": 1805681, "Bucharest": 1803425, "Hamburg": 1760433, "Budapest": 1754000, "Warsaw": 1740119, "Barcelona": 1602386, "Munich": 1493900, "Milan": 1350680} city_series = pd.Series(cities) print(city_series)
London 8615246 Berlin 3562166 Madrid 3165235 Rome 2874038 Paris 2273305 Vienna 1805681 Bucharest 1803425 Hamburg 1760433 Budapest 1754000 Warsaw 1740119 Barcelona 1602386 Munich 1493900 Milan 1350680 dtype: int64
NaN - Fehlende Daten
Ein Problem bei Aufgaben in der Datenanalyse besteht in fehlenden Daten.
Schauen wir uns noch einmal das vorherige Beispiel an. Dabei erkennen wir, dass die Indizes der Series mit den Keys des Dictionaries übereinstimmt, aus dem das Series-Objekt cities_series erzeugt wurde. Nehmen wir nun an, dass wir einen Index haben wollen, der sich nicht mit den Keys des Dictionaries überschneidet. Dafür können wir eine Liste oder ein Tupel dem Keyword-Argument 'index' mitgeben, um die Indizes zu definieren. Im nächsten Beispiel übergeben wir eine Liste (oder ein Tupel) als Indizes, welches nicht mit den Keys übereinstimmt. Das bedeutet, dass einige Städte des Dictionaries fehlen und für Stuttgart und Zürich keine Daten vorhanden sind.
my_cities = ["London", "Paris", "Zurich", "Berlin", "Stuttgart", "Hamburg"] my_city_series = pd.Series(cities, index=my_cities) print(my_city_series)
London 8615246.0 Paris 2273305.0 Zurich NaN Berlin 3562166.0 Stuttgart NaN Hamburg 1760433.0 dtype: float64
Abgesehen von den NaN-Werten werden bei den anderen Bevölkerungswerten die Werte in float-Werte gewandelt. Im folgenden Beispiel gibt es keine fehlenden Daten, und damit werden die Werte in Integer-Werte gewandelt:
my_cities = ["London", "Paris", "Berlin", "Hamburg"] my_city_series = pd.Series(cities, index=my_cities) my_city_seriesWir können die folgende Ausgabe erwarten, wenn wir den obigen Python-Code ausführen:
London 8615246 Paris 2273305 Berlin 3562166 Hamburg 1760433 dtype: int64
Die Methoden isnull() und notnull()
Wir sehen, dass die Städte, die nicht im Dictionary existieren, den Wert NaN zugewiesen bekommen. NaN
steht für "not a number". Es kann in unserem Beispiel auch als "fehlt" verstanden werden.
Wir können mit den Methoden isnull
und notnull
fehlende Werte prüfen:
my_cities = ["London", "Paris", "Zurich", "Berlin", "Stuttgart", "Hamburg"] my_city_series = pd.Series(cities, index=my_cities) print(my_city_series.isnull())
London False Paris False Zurich True Berlin False Stuttgart True Hamburg False dtype: bool
print(my_city_series.notnull())
London True Paris True Zurich False Berlin True Stuttgart False Hamburg True dtype: bool
Zusammenhang zwischen NaN und None
Wir erhalten ebenfalls NaN
, wenn ein Wert in dem Dictionary None
ist:
d = {"a":23, "b":45, "c":None, "d":0} S = pd.Series(d) print(S)
a 23.0 b 45.0 c NaN d 0.0 dtype: float64
pd.isnull(S)Wir können die folgende Ausgabe erwarten, wenn wir den obigen Python-Code ausführen:
a False b False c True d False dtype: bool
pd.notnull(S)Wir können die folgenden Ergebnisse erwarten, wenn wir den obigen Python-Code ausführen:
a True b True c False d True dtype: bool
Fehlende Daten filtern
Es ist möglich, die fehlenden Daten mit der Methode dropna
aus einem Series-Objekt herauszufiltern.
Die Methode liefert ein neues Series-Objekt zurück, welches keine NaN
-Werte enthält:
print("Vorher:\n") print(my_city_series) print("\nNachher:\n") print(my_city_series.dropna())
Vorher: London 8615246.0 Paris 2273305.0 Zurich NaN Berlin 3562166.0 Stuttgart NaN Hamburg 1760433.0 dtype: float64 Nachher: London 8615246.0 Paris 2273305.0 Berlin 3562166.0 Hamburg 1760433.0 dtype: float64
Fehlende Daten auffüllen
In vielen Fällen will man die fehlenden Daten gar nicht filtern. Stattdessen möchten man diese mit passenden Werten auffüllen. Eine gute Methode ist fillna
:
print(my_city_series.fillna(0))
London 8615246.0 Paris 2273305.0 Zurich 0.0 Berlin 3562166.0 Stuttgart 0.0 Hamburg 1760433.0 dtype: float64
Okay, das sind nicht wirklich passende Werte für die Bevölkerung von Zürich und Stuttgart. Wenn wir der Methode fillna() ein Dictionary mitgeben, können wir so die passenden Daten bereitstellen, z.B. die Bevölkerungswerte für Zürich und Stuttgart. Wir setzen den Parameter inplace
auf True
, damit die Änderungen auch in dem Objekt geändert werden. Bei True
wird ein neues Objekt mit den Einsetzungen erzeugt und zurückgeliefert, und das alte bleibt dabei unverändert:
missing_cities = {"Stuttgart":597939, "Zurich":378884} my_city_series.fillna(missing_cities, inplace=True) my_city_seriesFührt man obigen Code aus, erhält man folgende Ausgabe:
London 8615246.0 Paris 2273305.0 Zurich 378884.0 Berlin 3562166.0 Stuttgart 597939.0 Hamburg 1760433.0 dtype: float64
Dabei haben wir aber immer noch das Problem mit Integer-Werten. Die Werte, die Integer sein sollten, wie die Anzahl der Menschen, werden nach wie vor in Float-Werte gewandelt. Mit der Methode astype
können wir die Daten in Integer wandeln:
my_city_series = my_city_series.astype(int) print(my_city_series)
London 8615246 Paris 2273305 Zurich 378884 Berlin 3562166 Stuttgart 597939 Hamburg 1760433 dtype: int64