# invisible
import numpy as np
import pandas as pd
np.core.arrayprint._line_width = 60
pd.set_option('display.max_colwidth', 65)
pd.set_option('display.max_columns', 5)
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".
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)
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)
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))
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)
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))
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)
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)
print(S['apples'])
Man kann auch auf mehrere Indizes gleichzeitig zugreifen, wenn man die Indices als Liste übergibt:
print(S[['apples', 'oranges', 'cherries']])
Filterung mit einem Booleschen Array:
S[S>30]
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))
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 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 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)
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)
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_series
my_cities = ["London", "Paris", "Zurich", "Berlin",
"Stuttgart", "Hamburg"]
my_city_series = pd.Series(cities, index=my_cities)
print(my_city_series.isnull())
print(my_city_series.notnull())
d = {"a":23, "b":45, "c":None, "d":0}
S = pd.Series(d)
print(S)
pd.isnull(S)
pd.notnull(S)
print("Vorher:\n")
print(my_city_series)
print("\nNachher:\n")
print(my_city_series.dropna())
print(my_city_series.fillna(0))
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_series
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)