Pandas Einführung

Die Pandas, ÃŒber die wir in diesem Kapitel schreiben, haben nichts mit den sÌßen Panda-BÀren zu tun und sie 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".

Es gibt oft 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 benötigt, sind aber extrem nÌtzlich. Deshalb listet das Pandas-Projekt diese auch als "optionale AbhÀngigkeiten".

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 Daten-Strukturen 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.

Daten-Strukturen

Wir beginnen mit folgenden zwei wichtigen 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. Es kann als eine Daten-Struktur mit zwei Arrays angesehen werden: Ein Array fungiert als Index, d.h. als Bezeichner (Label), und ein Array beinhaltet die aktuellen Daten.

Wir definieren ein einfaches Series-Objekt im folgenden Beispiel indem wir dieses Objekt mit einer Liste instanziieren. Wir werden spÀter sehen, dass auch andere Daten-Objekte verwenden können, z.B. Numpy-Arrays und Dictionaries.

In [27]:
import pandas as pd
S = pd.Series([11, 28, 72, 3, 5, 8])
S
Out[27]:
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, dagegen zeigt die linke Spalte den Index. Pandas erstellt einen Default-Index der bei 0 beginnt und bis 5 lÀuft, was der LÀnge-1 entspricht.

Wir können direkt auf die Indizes und die Werte der Series S zugreifen:

In [28]:
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:

In [29]:
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:

In [30]:
fruits = ['apples', 'oranges', 'cherries', 'pears']
quantities = [20, 33, 52, 10]
S = pd.Series(quantities, index=fruits)
S
Out[30]:
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 den selben Indizes addieren, so erhalten wir ein neues Series-Objekt mit diesem Index und die entsprechenden Werte werden hinzugefÃŒgt:

In [31]:
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)
sum(S)
apples      37
oranges     46
cherries    83
pears       42
dtype: int64

Out[31]:
115

Die Indizes mÃŒssen nicht identisch sein fÃŒr die Series-Addition. Der Index ist eine "Vereinigung" beider Indizes. Wenn ein Index nicht in beiden Series-Objekten vorkommt, so wird der entsprechende Wert auf NaN gesetzt:

In [32]:
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)
sum(S)
cherries       83.0
oranges        46.0
peaches         NaN
pears          42.0
raspberries     NaN
dtype: float64

Out[32]:
115

Es ist möglich auf einzelne Werte eines Series-Objektes zuzugreifen oder auch ein mehrere bei einer Liste von Indizes:

In [34]:
print(S['apples'])
20

In [35]:
print(S[['apples', 'oranges', 'cherries']])
apples      20
oranges     33
cherries    52
dtype: int64

Wie bei Numpy können wir die Skalar-Operationen oder Mathematische Funktionen auf ein Series-Objekt anwenden:

In [36]:
import numpy as np
print((S + 3) * 4)
print("======================")
np.sin(S)
apples       92
oranges     144
cherries    220
pears        52
dtype: int64
======================

Out[36]:
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 liefern entweder ein Series-Objekt oder ein DataFrame-Objekt zurÌck, in AbhÀngigkeit von "func".

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:

In [37]:
S.apply(np.sin)
Out[37]:
apples      0.912945
oranges     0.999912
cherries    0.986628
pears      -0.544021
dtype: float64

Wir können auch Python-Lambda-Funktionen benutzen. Wir nehmen folgende Aufgabenstellung an. Die PrÌfung der Menge der FrÌchte. Wenn weniger als 50 vorhanden sind, so wird der Bestand um 10 erhöht:

In [38]:
S.apply(lambda x: x if x > 50 else x+10 )
Out[38]:
apples      30
oranges     43
cherries    52
pears       20
dtype: int64
In [39]:
v = (4, 6)
S.apply(lambda x, y, z: (x + y) / z if x > 50 else x+10, (4, 6) )
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-39-f3467fbcdb42> in <module>()
      1 v = (4, 6)
----> 2 S.apply(lambda x, y, z: (x + y) / z if x > 50 else x+10, (4, 6) )

/home/bernd/anaconda3/lib/python3.5/site-packages/pandas/core/series.py in apply(self, func, convert_dtype, args, **kwds)
   2218         else:
   2219             values = self.asobject
-> 2220             mapped = lib.map_infer(values, f, convert=convert_dtype)
   2221 
   2222         if len(mapped) and isinstance(mapped[0], Series):

pandas/src/inference.pyx in pandas.lib.map_infer (pandas/lib.c:62658)()

TypeError: <lambda>() missing 2 required positional arguments: 'y' and 'z'

Filterung mit einem Booleschen-Array:

In [ ]:
S[S>30]

Ein Series-Objekt kann gesehen werden wie ein geordnetes Python-Dictionary mit einer festen LÀnge.

In [ ]:
"apples" in S

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. Die Indizes werden sortiert.

In [ ]:
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)

Wir haben bereits gesehen, dass wir eine Liste oder ein Tupel als SchlÌsselwort-Argument "index" Ìbergeben können. In diesem Fall passt die Liste (oder das Tupel) nicht unbedingt zu den SchlÌsseln, d.h. es können mehr oder weniger EintrÀge im Index sein:

In [ ]:
my_cities = ["London", "Paris", "Zurich", "Berlin", 
             "Stuttgart", "Hamburg"]
my_city_series = pd.Series(cities, index=my_cities)
print(my_city_series)

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 fehlende Werte prÌfen mit den Methoden isnull und notnull:

In [ ]:
print(my_city_series.isnull())
In [ ]:
print(my_city_series.notnull())

Wir erhalten ebenfalls NaN, wenn ein Wert in dem Dictionary None ist:

In [ ]:
d = {"a":23, "b":45, "c":None, "d":0}
S = pd.Series(d)
print(S)
In [ ]:
pd.isnull(S)
In [ ]:
pd.notnull(S)

DataFrame

Die grundlegende Idee von DataFrame basiert auf Tabellen. Wir können die Daten-Struktur eine DataFrame als tabellarisch und tabellenÀhnlich sehen. Es beinhaltet eine geordnete Sammlung von Spalten. Jede Spalte besteht aus einem eindeutigen Daten-Typen, aber verschiedene Spalten haben verschiedene Typen, z.B. hat die erste Spalte den Typ Integer, wÀhrend die zweite Spalte vom Typ Boolean ist, usw.

Ein DataFrame hat einen Zeilen- und ein Spalten-Index. Es ist wie ein Dictionary aus Series mit einem normalen Index.

In [ ]:
cities = {"name": ["London", "Berlin", "Madrid", "Rome", 
                   "Paris", "Vienna", "Bucharest", "Hamburg", 
                   "Budapest", "Warsaw", "Barcelona", 
                   "Munich", "Milan"],
          "population": [8615246, 3562166, 3165235, 2874038,
                         2273305, 1805681, 1803425, 1760433,
                         1754000, 1740119, 1602386, 1493900,
                         1350680],
          "country": ["England", "Germany", "Spain", "Italy",
                      "France", "Austria", "Romania", 
                      "Germany", "Hungary", "Poland", "Spain",
                      "Germany", "Italy"]}

city_frame = pd.DataFrame(cities)
print(city_frame)

Der Index (0,1,2,...) wird automatisch dem DataFrame zugewiesen. Wir können ebenfalls einen angepassten Index verwenden:

In [ ]:
ordinals = ["first", "second", "third", "fourth",
            "fifth", "sixth", "seventh", "eigth",
            "ninth", "tenth", "eleventh", "twelvth",
            "thirteenth"]
city_frame = pd.DataFrame(cities, index=ordinals)
print(city_frame)

Die Reihenfolge der Spalten kann neu angeordnet werden.

In [ ]:
city_frame = pd.DataFrame(cities,
                          columns=["name", 
                                   "country", 
                                   "population"],
                          index=ordinals)
print(city_frame)

Wir können die Summe aller Spalten berechnen oder die Summe bestimmter Spalten:

In [ ]:
city_frame.sum()
In [ ]:
city_frame["population"].sum()

Mit "cumsum" erhalten wir die kumulierte Summe:

In [ ]:
x = city_frame["population"].cumsum()
print(x)

x ist ein Pandas-Series-Objekt. Wir können dies zur "population"-Spalte zuordnen:

In [ ]:
city_frame["population"] = x
print(city_frame)

Wir können einen Spalte-Namen miteinbeziehen der nicht im Dictionary existiert. In diesem Fall, werden alle Werte dieser Spalte auf NaN gesetzt:

In [ ]:
city_frame = pd.DataFrame(cities,
                          columns=["name", 
                                   "country", 
                                   "area",
                                   "population"],
                          index=ordinals)
print(city_frame)

Es gibt zwei Wege auf eine Spalte eines DataFrame zuzugreifen. Das Ergebnis ist in beiden FÀllen ein Series-Objekt:

In [ ]:
# in a dictionary-like way:
print(city_frame["population"])
In [ ]:
# as an attribute
print(city_frame.population)
In [ ]:
print(type(city_frame.population))
In [ ]:
p = city_frame.population
p["first"] = 9000000
print(city_frame)

Im vorigen Beispiel wurde die population-Spalte nicht kopiert wurde. "p" ist eine Sicht auf die Daten von city_frame.

Ebenfalls können die Zeilen direkt angesprochen werden. Im folgenden lesen wir die Info der fÌnften Stadt:

In [ ]:
city_frame.ix['fourth']

Die Spalte "area" ist nicht definiert. Wir können alle Elemente der Spalte auf den gleichen Wert setzen:

In [ ]:
city_frame["area"] = 1572
print(city_frame)

In diesem Fall ist es definitiv besser die exakten Werte den StÀdten zuzuweisen. Die Liste mit den area-Werten muss die gleiche LÀnge haben wie die Anzahl der Zeilen in unserem DataFrame.

In [ ]:
# area in square km:
area = [1572, 891.85, 605.77, 1285, 
        105.4, 414.6, 228, 755, 
        525.2, 517, 101.9, 310.4, 
        181.8]
city_frame["area"] = area
print(city_frame)

Wir sortieren das DataFrame entsprechend den area-Werten:

In [ ]:
city_frame = city_frame.sort_values(by="area", ascending=False)
print(city_frame)

Wir nehmen an, dass wir nur die FlÀchen von London, Hamburg und Milan haben. Die FlÀchen sind in einem Series-Objekt mit den korrekten Indizes. Wir können dies ebenfalls zuweisen:

In [ ]:
city_frame = pd.DataFrame(cities,
                          columns=["name", 
                                   "country", 
                                   "area",
                                   "population"],
                          index=ordinals)

some_areas = pd.Series([1572, 755, 181.8], 
                    index=['first', 'eigth', 'thirteenth'])

city_frame['area'] = some_areas
print(city_frame)

Ein verschachteltes Dictionary kann einem DataFrame Ìbergeben werden. Die Indizes des Àusseren Dictionary werden als Spalten verwendet und die SchlÌssel des inneren Dictionary werden als Zeilen-Indizes benutzt:

In [ ]:
growth = {"Switzerland": {"2010": 3.0, "2011": 1.8, "2012": 1.1, "2013": 1.9},
          "Germany": {"2010": 4.1, "2011": 3.6, "2012":	0.4, "2013": 0.1},
          "France": {"2010":2.0,  "2011":2.1, "2012": 0.3, "2013": 0.3},
          "Greece": {"2010":-5.4, "2011":-8.9, "2012":-6.6, "2013":	-3.3},
          "Italy": {"2010":1.7, "2011":	0.6, "2012":-2.3, "2013":-1.9}
          } 
In [ ]:
growth_frame = pd.DataFrame(growth)
growth_frame

Sie haben lieber die Jahre in den Spalten und die LÀnder in den Zeilen? Keine Problem, Sie können die Daten austauschen:

In [ ]:
growth_frame.T
In [ ]:
growth_frame = pd.DataFrame(growth)
growth_frame.reindex(["2013", "2012", "2011", "2010"])

Das DataFrame mit Zufalls-Werten fÃŒllen:

In [ ]:
df = pd.DataFrame(np.random.randn(10, 5),
columns=['a', 'b', 'c', 'd', 'e'])
df

Wir möchten aus einer CSV-Datei die Bevölkerung aller LÀnder lesen (Juli 2014). Der Delimiter der Datei ist ein Leerzeichen und Kommas separieren die Tausender in den Zahlen:

In [ ]:
pop = pd.read_csv("countries_population.csv", 
                  quotechar="'", 
                  sep=" ", 
                  thousands=",")
pop
In [ ]: