Im vorigen Kapitel haben wir gesehen, dass der Datentyp Series logisch gesehen einer Spalte mit Index einer Excel-Tabelle eintspricht. In diesem Kapitel geht es nun um den Datentyp DataFrame, den man sich nun wie eine komplette Excel-Tabelle vorstellen kann. Man kann also sagen, dass dieser Datentyp auf Tabellen basiert. Ein DataFrame besteht aus einer geordneten Sequenz von Spalten. Jede Spalte besteht aus einem eindeutigen Daten-Typ, - wie eine Series- aber verschiedene Spalten können verschiedene Typen haben. So könnte beispielsweise eine Spalte Verkaufszahlen als Float-Zahlen enthalten, während eine andere die zugehörigen Jahreszahlen als Integer-Zahlen enthalten könnte.

Zusammenhang zu Series

Ein DataFrame hat einen Zeilen- und ein Spalten-Index. Es ist wie ein Dictionary aus Series mit einem normalen Index. Jede Series wird über einen Index, d.h. Namen der Spalte angesprochen. Wir demonstrieren diesen Zusammenhang im folgenden Beispiel, in dem drei Series-Objekte definiert werden und zu einem DataFrame zusammengebaut werden:

DataFrame

Spreadsheet und DataFrame

Die grundlegende Idee von DataFrame basiert auf Tabellen. Wir können die Daten-Struktur eines DataFrame als tabellarisch und tabellenähnlich sehen. Ein Dataframe beinhaltet eine geordnete Sammlung von Spalten. Jede Spalte besteht aus einem eindeutigen Daten-Typen, aber verschiedene Spalten haben verschiedene Typen, z.B. könnte die erste Spalte vom Typ Integer sein, 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. Wir demonstrieren dies im folgenden Beispiel, in dem drei Series-Objekte definiert werden:

import pandas as pd
years = range(2014, 2018)
shop1 = pd.Series([2409.14, 2941.01, 3496.83, 3119.55], index=years)
shop2 = pd.Series([1203.45, 3441.62, 3007.83, 3619.53], index=years)
shop3 = pd.Series([3412.12, 3491.16, 3457.19, 1963.10], index=years)

Was passiert, wenn diese "shop"-Series-Objekte konkateniert werden? Pandas stellt eine concat()-Funktion für diesen Zweck zur Verfügung:

pd.concat([shop1, shop2, shop3])
Der obige Python-Code liefert folgendes Ergebnis:
2014    2409.14
2015    2941.01
2016    3496.83
2017    3119.55
2014    1203.45
2015    3441.62
2016    3007.83
2017    3619.53
2014    3412.12
2015    3491.16
2016    3457.19
2017    1963.10
dtype: float64

Das Ergebnis ist wohl nicht das, was wir erwartet haben. Der Grund dafür ist, dass concat() für den axis-Parameter 0 verwendet. Probieren wir es mit "axis=1":

shops_df = pd.concat([shop1, shop2, shop3], axis=1)
print(shops_df)
            0        1        2
2014  2409.14  1203.45  3412.12
2015  2941.01  3441.62  3491.16
2016  3496.83  3007.83  3457.19
2017  3119.55  3619.53  1963.10

Die Frage ist, von welchem Datentyp das Ergebnis ist:

print(type(shops_df))
<class 'pandas.core.frame.DataFrame'>

Das bedeutet, dass wir Series-Objekte durch Konkatenierung in DataFrame-Objekte wandeln können!

Die Spaltennamen lauten:

shops_df.columns
Wir erhalten die folgende Ergebnisse:
RangeIndex(start=0, stop=3, step=1)
shops_df.columns.values
Der obige Python-Code führt zu folgender Ausgabe:
array([0, 1, 2])

Wir geben den Spalten noch Namen, um das DataFrame etwas leichter lesbar zu machen:

cities = ["Zürich", "Winterthur", "Freiburg"]
shops_df.columns = cities 
print(shops_df)
       Zürich  Winterthur  Freiburg
2014  2409.14     1203.45   3412.12
2015  2941.01     3441.62   3491.16
2016  3496.83     3007.83   3457.19
2017  3119.55     3619.53   1963.10

Andererseits wäre eine Umbenennung in unserem Fall überhaupt nicht notwendig gewesen, wenn die Series bereits entsprechend benamt gewesen wären. Wir zeigen dies in folgendem Fall:

shop1.name = "Zürich"
shop2.name = "Winterthur"
shop3.name = "Freiburg"
shops_df2 = pd.concat([shop1, shop2, shop3], axis=1)
print(shops_df2)
       Zürich  Winterthur  Freiburg
2014  2409.14     1203.45   3412.12
2015  2941.01     3441.62   3491.16
2016  3496.83     3007.83   3457.19
2017  3119.55     3619.53   1963.10

Auf die Spalten eines Dataframes können wir einfach durch Indizierung zugreifen:

print(shops_df["Zürich"])
2014    2409.14
2015    2941.01
2016    3496.83
2017    3119.55
Name: Zürich, dtype: float64

Jede einzelne der Spalten entsprach ursprünglich eine Series und entspricht auch immer noch einer Series. Dies können wir sehen, wenn wir uns den Typ anschauen:

print(type(shops_df["Zürich"]))
<class 'pandas.core.series.Series'>

Pandas bietet noch eine syntaktisch deutlich einfachere Methode auf die Spalten zuzugreifen. Die Spaltennamen wurden dazu als Properties implementiert, und dies bedeutet, dass man einfach den Spaltennamen mittels Punkt an das Dataframe anhängen kann, um die entsprechende Spalte anzusprechen.

print(shops_df.Zürich)
2014    2409.14
2015    2941.01
2016    3496.83
2017    3119.55
Name: Zürich, dtype: float64

DataFrames aus Dictionaries

Wie schon erwähnt, hat ein DataFrame einen Spalten- und einen Zeilen-Index. Man kann es sich also wie ein Dictionary aus Series-Objekten mit Keys vorstellen.

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)
         name  population  country
0      London     8615246  England
1      Berlin     3562166  Germany
2      Madrid     3165235    Spain
3        Rome     2874038    Italy
4       Paris     2273305   France
5      Vienna     1805681  Austria
6   Bucharest     1803425  Romania
7     Hamburg     1760433  Germany
8    Budapest     1754000  Hungary
9      Warsaw     1740119   Poland
10  Barcelona     1602386    Spain
11     Munich     1493900  Germany
12      Milan     1350680    Italy

Index eines DataFrames ändern

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

ordinals = ["first", "second", "third", "fourth",
            "fifth", "sixth", "seventh", "eigth",
            "ninth", "tenth", "eleventh", "twelvth",
            "thirteenth"]
city_frame = pd.DataFrame(cities, index=ordinals)
print(city_frame)
                 name  population  country
first          London     8615246  England
second         Berlin     3562166  Germany
third          Madrid     3165235    Spain
fourth           Rome     2874038    Italy
fifth           Paris     2273305   France
sixth          Vienna     1805681  Austria
seventh     Bucharest     1803425  Romania
eigth         Hamburg     1760433  Germany
ninth        Budapest     1754000  Hungary
tenth          Warsaw     1740119   Poland
eleventh    Barcelona     1602386    Spain
twelvth        Munich     1493900  Germany
thirteenth      Milan     1350680    Italy

Umsortierung der Spalten

Die Sortierung kann zum Zeitpunkt der Erstellung des DataFrame definiert und angepasst werden. Damit kann sichergestellt werden, dass wir eine definierte Sortierung der Spalten haben, wenn das DataFrame aus einem Dictionary erzeugt wird. Dictionaries waren bis Python 3.6 nicht geordnet, wie wir es im Kapitel zu Dictionaries gezeigt haben. Dadurch konnte man bis Python 3.6 nicht wissen, in welcher Reichenfolge die Indizes iteriert worden. Mit dem Parameter columns können wir jedoch eine Reihenfolge festlegen:

city_frame = pd.DataFrame(cities,
                          columns = ["name", 
                                     "country", 
                                     "population"])
print(city_frame)
         name  country  population
0      London  England     8615246
1      Berlin  Germany     3562166
2      Madrid    Spain     3165235
3        Rome    Italy     2874038
4       Paris   France     2273305
5      Vienna  Austria     1805681
6   Bucharest  Romania     1803425
7     Hamburg  Germany     1760433
8    Budapest  Hungary     1754000
9      Warsaw   Poland     1740119
10  Barcelona    Spain     1602386
11     Munich  Germany     1493900
12      Milan    Italy     1350680

Im folgenden ändern wir sowohl die Spaltenreihenfolge und die Indexreihenfolge mit der Funktion reindex:

city_frame.reindex(index=[0, 2, 4, 6,  8, 10, 12, 1, 3, 5, 7, 9, 11], 
                   columns=['country', 'name', 'population'])
Wir erhalten die folgende Ergebnisse:
country name population
0 England London 8615246
2 Spain Madrid 3165235
4 France Paris 2273305
6 Romania Bucharest 1803425
8 Hungary Budapest 1754000
10 Spain Barcelona 1602386
12 Italy Milan 1350680
1 Germany Berlin 3562166
3 Italy Rome 2874038
5 Austria Vienna 1805681
7 Germany Hamburg 1760433
9 Poland Warsaw 1740119
11 Germany Munich 1493900

Jetzt wollen wir die Spalten umbenennen. Dafür verwenden wir die DataFrame-Methode rename(). Die Methode unterstützt folgende Konventionen:

Wir benennen nun im folgenden Beispiel die Spalten unseres DataFrame in rumänische Bezeichnungen um. Den Parameter inplace setzen wir auf True, damit das DataFrame-Objekt direkt geändert wird und kein neues erzeugt wird. Der Default-Wert für den Parameter inplace ist False.

city_frame.rename(columns={"name":"Nume", 
                           "country":"țară", 
                           "population":"populație"},
                  inplace=True)
print(city_frame)
         Nume     țară  populație
0      London  England    8615246
1      Berlin  Germany    3562166
2      Madrid    Spain    3165235
3        Rome    Italy    2874038
4       Paris   France    2273305
5      Vienna  Austria    1805681
6   Bucharest  Romania    1803425
7     Hamburg  Germany    1760433
8    Budapest  Hungary    1754000
9      Warsaw   Poland    1740119
10  Barcelona    Spain    1602386
11     Munich  Germany    1493900
12      Milan    Italy    1350680

Bestehende Spalte als Index im DataFrame

Wir wollen im nächsten Beispiel einen "nützlicheren" Index für unser Beispiel erzeugen. Dafür verwenden wir die Landesnamen als Index, d.h. die Werte aus der Liste mit dem Key country aus unserem cities-Dictionary. Wir zeigen zuerst, wie man dies direkt bei der Erzeugung des DataFrames bewerkstelligen kann:

city_frame = pd.DataFrame(cities,
                          columns=['name', 'population'],
                          index=cities['country'])
print(city_frame)
              name  population
England     London     8615246
Germany     Berlin     3562166
Spain       Madrid     3165235
Italy         Rome     2874038
France       Paris     2273305
Austria     Vienna     1805681
Romania  Bucharest     1803425
Germany    Hamburg     1760433
Hungary   Budapest     1754000
Poland      Warsaw     1740119
Spain    Barcelona     1602386
Germany     Munich     1493900
Italy        Milan     1350680

Man kann auch in einem bestehenden DataFrame den Index neu setzen. Dazu nutzen wir die Methode set_index, um eine Spalte in einen Index zu wandeln. Dabei ist zu beachten, dass set_index ein neues DataFrame zurückliefert, bei dem die gewählte Spalte als Index verwendet wird:

city_frame = pd.DataFrame(cities)
city_frame2 = city_frame.set_index("country")
print(city_frame2)
              name  population
country                       
England     London     8615246
Germany     Berlin     3562166
Spain       Madrid     3165235
Italy         Rome     2874038
France       Paris     2273305
Austria     Vienna     1805681
Romania  Bucharest     1803425
Germany    Hamburg     1760433
Hungary   Budapest     1754000
Poland      Warsaw     1740119
Spain    Barcelona     1602386
Germany     Munich     1493900
Italy        Milan     1350680

Im vorherigen Beispiel haben wir gesehen, dass die Methode set_index ein neues DataFrame-Objekt liefert und nicht das originale Objekt verändert. Möchte man kein neues DataFrame erzeugen, sondern das bestehende direkt mit einem neuen Index versehen, so kann man den Parameter "inplace" auf True setzen. Dadurch wird dann das originale Objekt direkt verändert:

city_frame = pd.DataFrame(cities)
city_frame.set_index("country", inplace=True)
print(city_frame)
              name  population
country                       
England     London     8615246
Germany     Berlin     3562166
Spain       Madrid     3165235
Italy         Rome     2874038
France       Paris     2273305
Austria     Vienna     1805681
Romania  Bucharest     1803425
Germany    Hamburg     1760433
Hungary   Budapest     1754000
Poland      Warsaw     1740119
Spain    Barcelona     1602386
Germany     Munich     1493900
Italy        Milan     1350680

Selektion von Zeilen

Bis jetzt haben wir die DataFrame-Objekte über die Spalten indiziert, d.h. wir haben nur auf Spalten zugegriffen. Nun möchten wir demonstrieren, wie wir auch selektiv auf die Zeilen zugreifen können. Dazu verwenden wir die Locators loc und iloc.

Im ersten Beispiel erzeugen wir ein Dataframe, das nur aus den Zeilen besteht, in denen wir den Index "Germany" haben:

city_frame = pd.DataFrame(cities, 
                          columns=("name", "population"), 
                          index=cities["country"])
print(city_frame.loc["Germany"])
            name  population
Germany   Berlin     3562166
Germany  Hamburg     1760433
Germany   Munich     1493900

Will man mehrere Index-Werte angeben, übergibt man diese als Liste an loc:

print(city_frame.loc[["Germany", "France"]])
            name  population
Germany   Berlin     3562166
Germany  Hamburg     1760433
Germany   Munich     1493900
France     Paris     2273305

Nun wählen wir alle Zeilen aus, in denen in einer Spalte eine Bedingung erfüllt ist, also in unserem Beispiel die Bevölkerungsanzahl größer als zwei Millionen ist:

print(city_frame.loc[city_frame.population > 2000000])
           name  population
England  London     8615246
Germany  Berlin     3562166
Spain    Madrid     3165235
Italy      Rome     2874038
France    Paris     2273305

Summen und Kumulative Summen

Mit der Methode sum können die Summe von allen Spalten eines DataTypes berechnen, was aber in unserem Fall wenig Sinn macht:

print(city_frame.sum())

Nun berechnen wir die Summe der Bevölkerungszahlen:

city_frame["population"].sum()
Wir können die folgende Ausgabe erwarten, wenn wir den obigen Python-Code ausführen:
33800614

Mit cumsum berechnen wir die kumulative Summe:

x = city_frame["population"].cumsum()
print(x)
England     8615246
Germany    12177412
Spain      15342647
Italy      18216685
France     20489990
Austria    22295671
Romania    24099096
Germany    25859529
Hungary    27613529
Poland     29353648
Spain      30956034
Germany    32449934
Italy      33800614
Name: population, dtype: int64

Spaltenwerte ersetzen

Das eben berechnete 'x' ist ein Series-Objekt mit der kumulativen Summe. Diese Series können wir der population-Spalte zuweisen und ersetzen damit die alten Werte. Im folgenden Nutzen wir die Methode head, die nur die ersten fünf Zeilen ausgibt, da dies zur Veranschaulichung des Prinzips genügt:

city_frame["population"] = x
print(city_frame.head())
           name  population
England  London     8615246
Germany  Berlin    12177412
Spain    Madrid    15342647
Italy      Rome    18216685
France    Paris    20489990

Anstelle die Werte in der population-Spalte komplett durch die kumulativen Summen zu ersetzen, wollen wir die neuen Werte als neue zusätzliche Spalte cum_population dem ursprünglchen DataFrame anfügen.

city_frame = pd.DataFrame(cities,
                          columns=["country", 
                                   "population",
                                   "cum_population"],
                          index=cities["name"])
print(city_frame.head())
        country  population cum_population
London  England     8615246            NaN
Berlin  Germany     3562166            NaN
Madrid    Spain     3165235            NaN
Rome      Italy     2874038            NaN
Paris    France     2273305            NaN

Die neue Spalte cum_population enthält nur NaN-Werte, weil noch keine Daten zur Verfügung gestellt wurden.

Nun weisen wir die kumulativen Summen dieser neuen Spalte zu:

city_frame["cum_population"] = city_frame["population"].cumsum()
print(city_frame.head())
        country  population  cum_population
London  England     8615246         8615246
Berlin  Germany     3562166        12177412
Madrid    Spain     3165235        15342647
Rome      Italy     2874038        18216685
Paris    France     2273305        20489990

Bei der Erstellung eines DataFrame-Objektes aus einem Dictionary, können auch Spalten angegeben werden, die nicht im Dictionary enthalten sind. In diesem Fall werden die Werte ebenfalls auf NaN gesetzt:

city_frame = pd.DataFrame(cities,
                          columns=["country", 
                                   "area",
                                   "population"],
                          index=cities["name"])
print(city_frame.head())
        country area  population
London  England  NaN     8615246
Berlin  Germany  NaN     3562166
Madrid    Spain  NaN     3165235
Rome      Italy  NaN     2874038
Paris    France  NaN     2273305

In einem weiteren Schritt kann man dann die Werte für die Fläche in Form einer Liste bzw. eines Arrays an die Spalte area zuweisen.

# Flächen in qkm:
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.head())
        country     area  population
London  England  1572.00     8615246
Berlin  Germany   891.85     3562166
Madrid    Spain   605.77     3165235
Rome      Italy  1285.00     2874038
Paris    France   105.40     2273305

Sortierung

DataFrames lassen sich anhand von bestimmten Kritierien sortieren. Im folgenden Beispiel sortieren wir den Inhalt des DataFrame-Objektes anhand der area-Werte in absteigender Größe:

city_frame = city_frame.sort_values(by="area", ascending=False)
print(city_frame)
           country     area  population
London     England  1572.00     8615246
Rome         Italy  1285.00     2874038
Berlin     Germany   891.85     3562166
Hamburg    Germany   755.00     1760433
Madrid       Spain   605.77     3165235
Budapest   Hungary   525.20     1754000
Warsaw      Poland   517.00     1740119
Vienna     Austria   414.60     1805681
Munich     Germany   310.40     1493900
Bucharest  Romania   228.00     1803425
Milan        Italy   181.80     1350680
Paris       France   105.40     2273305
Barcelona    Spain   101.90     1602386

Nehmen wir an, dass wir lediglich die Flächen-Werte von London, Hamburg und Milan hätten. Die areas-Werte befinden sich in einem Series-Objekt mit den korrekten Indizes. Die Zuweisung funktioniert ebenfalls:

city_frame = pd.DataFrame(cities,
                          columns=["country", 
                                   "area",
                                   "population"],
                          index=cities["name"])
some_areas = pd.Series([1572, 755, 181.8], 
                    index=['London', 'Hamburg', 'Milan'])
city_frame['area'] = some_areas
print(city_frame)
           country    area  population
London     England  1572.0     8615246
Berlin     Germany     NaN     3562166
Madrid       Spain     NaN     3165235
Rome         Italy     NaN     2874038
Paris       France     NaN     2273305
Vienna     Austria     NaN     1805681
Bucharest  Romania     NaN     1803425
Hamburg    Germany   755.0     1760433
Budapest   Hungary     NaN     1754000
Warsaw      Poland     NaN     1740119
Barcelona    Spain     NaN     1602386
Munich     Germany     NaN     1493900
Milan        Italy   181.8     1350680

Spalten einfügen

In vorherigen Beispielen haben wir Spalten bei der Erstellung des DataFrame hinzugefügt. Es ist jedoch oft notwendig Spalten direkt in ein bereits bestehendes DataFrame einzufügen.

city_frame = pd.DataFrame(cities,
                          columns = ["country", 
                                     "population"],
                          index = cities["name"])
city_frame.insert(loc = 1, 
                  column = 'area', 
                  value = area)
print(city_frame)
           country     area  population
London     England  1572.00     8615246
Berlin     Germany   891.85     3562166
Madrid       Spain   605.77     3165235
Rome         Italy  1285.00     2874038
Paris       France   105.40     2273305
Vienna     Austria   414.60     1805681
Bucharest  Romania   228.00     1803425
Hamburg    Germany   755.00     1760433
Budapest   Hungary   525.20     1754000
Warsaw      Poland   517.00     1740119
Barcelona    Spain   101.90     1602386
Munich     Germany   310.40     1493900
Milan        Italy   181.80     1350680

DataFrame und verschachtelte Dictionaries

Verschachtelte Dictionaries können ebenfalls an ein DataFrame übergeben werden. Die Indizes des äusseren Dictionary entsprechen den Spalten und die inneren Schlüssel der Dictionaries entsprechen den Indizes der einzelnen Zeilen:

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}
          } 
growth_frame = pd.DataFrame(growth)
print(growth_frame)
      Switzerland  Germany  France  Greece  Italy
2010          3.0      4.1     2.0    -5.4    1.7
2011          1.8      3.6     2.1    -8.9    0.6
2012          1.1      0.4     0.3    -6.6   -2.3
2013          1.9      0.1     0.3    -3.3   -1.9

Sie möchten vielleicht die Jahre als Spalten und die Länder als Zeilen? Eine Vertauschung von Index und Spalten ist mittels transpose ganz einfach zu realisieren:

print(growth_frame.transpose())
             2010  2011  2012  2013
Switzerland   3.0   1.8   1.1   1.9
Germany       4.1   3.6   0.4   0.1
France        2.0   2.1   0.3   0.3
Greece       -5.4  -8.9  -6.6  -3.3
Italy         1.7   0.6  -2.3  -1.9

Statt transpose() kann man auch einfach die Property-Schreibweise T verwenden:

print(growth_frame.T)
             2010  2011  2012  2013
Switzerland   3.0   1.8   1.1   1.9
Germany       4.1   3.6   0.4   0.1
France        2.0   2.1   0.3   0.3
Greece       -5.4  -8.9  -6.6  -3.3
Italy         1.7   0.6  -2.3  -1.9
growth_frame = growth_frame.T
growth_frame2 = growth_frame.reindex(["Switzerland", 
                                      "Italy", 
                                      "Germany", 
                                      "Greece"])
print(growth_frame2)
             2010  2011  2012  2013
Switzerland   3.0   1.8   1.1   1.9
Italy         1.7   0.6  -2.3  -1.9
Germany       4.1   3.6   0.4   0.1
Greece       -5.4  -8.9  -6.6  -3.3