Datenvisualisierung mit Pandas

Einführung

Pie Charts with Pandas

Es ist selten eine gute Idee, wenn Sie ihre wissenschaftlichen oder geschäftlichen Daten in einfachen Spalten und Zeilen präsentieren. Meistens verwenden wir doch verschiedene Arten von Diagrammen um unsere Daten zu visualisieren. Damit wird die Kommunikation der Information effizienter und macht die Daten greifbarer. Mit anderen Worten macht es komplexe Daten zugänglicher und verständlicher. Numerische Daten können in grafisch kodiert werden in Linien-Charts, Balken-Charts, Kuchen-Diagrammen, Histogrammen, Scatter-Plots und weitere.

Wir haben bereits die starken Möglichkeiten kennen gelernt um öffentlichkeitstaugliche Grafiken (Plots) zu erstellen. Matplotlib ist ein Low-Level-Werkzeug um das Ziel zu erreichen, da Sie ihrer Grafik noch durch Basis-Elemente erweitern können, wie bspw. Legenden, Bezeichnungen, usw. Mit Pandas können diese Möglichkeiten leichter umgesetzt werden.

Wir beginnen mit einem einfachen Beispiel einer Linien-Grafik.

Linien-Grafik in Pandas

Series

Sowohl die Pandas-Series, als auch die DataFrames, unterstüzen die Plot-Methode.

Sie können im folgenden einfaches Beispiel einer Linien-Grafik für ein Series-Objekt sehen. Wir verwenden eine einfache Python-Liste data für die Daten. Der Index wird für die x-Werte verwendet, oder die Domäne.

import pandas as pd
data = [100, 120, 140, 180, 200, 210, 214]
s = pd.Series(data, index=range(len(data)))
s.plot()
Führt man obigen Code aus, erhält man Folgendes:
<matplotlib.axes._subplots.AxesSubplot at 0x1140caa90>

Es ist möglich die Verwendung des Index zu unterdrücken, in dem der Parameter use_index = False gesetzt wird. In unserem Beispiel ergibt es das gleiche Ergebnis:

s.plot(use_index=False)
Führt man obigen Code aus, erhält man folgende Ausgabe:
<matplotlib.axes._subplots.AxesSubplot at 0x11e502f60>

Wir experimentieren jetzt mit einem Series-Objekt, welches einen Index mit alphabetischen Werten hat.

fruits = ['apples', 'oranges', 'cherries', 'pears']
quantities = [20, 33, 52, 10]
S = pd.Series(quantities, index=fruits)
S.plot()
Wir können die folgenden Ergebnisse erwarten, wenn wir den obigen Python-Code ausführen:
<matplotlib.axes._subplots.AxesSubplot at 0x11e5947b8>

DataFrames

Wir stellen nun die Plot-Methode für DataFrames vor. Dazu definieren wir ein Dictionary mit Bevölkerungswerten und Flächenangaben. Dieses Dictionary verwenden wir dann für die Erstellung eine DataFrame-Objektes, welches wir anschliessend für die Grafik verwenden wollen:

import pandas as pd
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],
          "area" : [1572, 891.85, 605.77, 1285, 
                    105.4, 414.6, 228, 755, 
                    525.2, 517, 101.9, 310.4, 
                    181.8]
}
city_frame = pd.DataFrame(cities,
                          columns=["population", "area"],
                          index=cities["name"])
print(city_frame)
           population     area
London        8615246  1572.00
Berlin        3562166   891.85
Madrid        3165235   605.77
Rome          2874038  1285.00
Paris         2273305   105.40
Vienna        1805681   414.60
Bucharest     1803425   228.00
Hamburg       1760433   755.00
Budapest      1754000   525.20
Warsaw        1740119   517.00
Barcelona     1602386   101.90
Munich        1493900   310.40
Milan         1350680   181.80

Der folgende Code erstellt die Grafik unseres DataFrames. Die Flächen-Werte werden noch mit 1000 multipliziert, da die "Flächen"-Linie sonst unsichtbar bzw. durch die x-Achse verdeckt würde:

city_frame["area"] *= 1000
city_frame.plot()
Der obige Python-Code liefert Folgendes:
<matplotlib.axes._subplots.AxesSubplot at 0x11e776b38>

Diese Grafik entspricht vielleicht nicht ihren Erwartungen, weil nicht alle Städte-Namen auf der x-Achse der Grafik erscheinen. Wir können das ändern indem wir explizit xticks definieren mit range(len(city_frame.index)). Ausserdem muss use_index = True gesetzt werden, damit nicht einfach numerische Werte (von 0 bis range(len(city_frame.index))) sondern tatsächlich die Städte-Namen angezeigt werden:

city_frame.plot(xticks=range(len(city_frame.index)),
                use_index=True)
Führt man obigen Code aus, erhält man folgende Ausgabe:
<matplotlib.axes._subplots.AxesSubplot at 0x11e7ddeb8>

Nun haben wir ein neues Problem. Die Städte-Namen überlagern sich. Mit dem Parameter rot = 90 werden die Zeichenketten um 90 Grad gedreht und und stehen damit vertikal:

city_frame.plot(xticks=range(len(city_frame.index)),
                use_index=True, 
                rot=90)
Wir erhalten die folgende Ergebnisse:
<matplotlib.axes._subplots.AxesSubplot at 0x11e8ac9e8>

Sekundärachsen (Twin Axes)

Die Flächen-Spalte haben wir mit 1000 multipliziert, damit Sie besser dargestellt werden kann. Stattdessen können auch Sekundärachsen (Twin Axes) verwendet werden, was wir im nächsten Beispiel demonstrieren werden. Dafür erstellen wir das DataFrame-Objekt city_frame neu, um die originale Flächen-Spalte zu haben:

city_frame = pd.DataFrame(cities,
                          columns=["population", "area"],
                          index=cities["name"])
print(city_frame)
           population     area
London        8615246  1572.00
Berlin        3562166   891.85
Madrid        3165235   605.77
Rome          2874038  1285.00
Paris         2273305   105.40
Vienna        1805681   414.60
Bucharest     1803425   228.00
Hamburg       1760433   755.00
Budapest      1754000   525.20
Warsaw        1740119   517.00
Barcelona     1602386   101.90
Munich        1493900   310.40
Milan         1350680   181.80

Um die Darstellung mit Sekundärachsen zu erreichen, benötigen wir subplots aus dem Modul matplotlib und die Funktion "twinx":

import matplotlib.pyplot as plt
fig, ax = plt.subplots()
fig.suptitle("City Statistics")
ax.set_ylabel("Population")
ax.set_xlabel("Cities")
ax2 = ax.twinx()
ax2.set_ylabel("Area")
city_frame["population"].plot(ax=ax, 
                              style="b-",
                              xticks=range(len(city_frame.index)),
                              use_index=True, 
                              rot=90)
city_frame["area"].plot(ax=ax2, 
                        style="g-",
                        use_index=True, 
                        rot=90)
fig.legend()
Der obige Python-Code liefert folgendes Ergebnis:
<matplotlib.legend.Legend at 0x11ea852b0>

Wir können Sekundärachsen auch direkt in Pandas ohne Zuhilfenahme von Matplotlib erzeugen, wie wir im Code des folgenden Programmes sehen:

import matplotlib.pyplot as plt
ax1= city_frame["population"].plot(style="b-",
                                   xticks=range(len(city_frame.index)),
                                   use_index=True, 
                                   rot=90)
ax2 = ax1.twinx()
#ax2.spines['right'].set_position(('axes', 1.0))
city_frame["area"].plot(ax=ax2,
                        style="g-",
                        use_index=True,
                        #secondary_y=True,
                        rot=90)
ax1.legend(loc = (.7,.9), frameon = False)
ax2.legend( loc = (.7, .85), frameon = False)
plt.show()

Mehrere Y-Achsen

Wir fügen eine weitere Achse zum city_frame hinzu, indem wir eine neue Spalte mit der Bevölkerungsdichte erstellen, d.h. die Anzahl der Menschen pro Quadratkilometer:

city_frame["density"] = city_frame["population"] / city_frame["area"]
print(city_frame)
           population     area       density
London        8615246  1572.00   5480.436387
Berlin        3562166   891.85   3994.131300
Madrid        3165235   605.77   5225.143206
Rome          2874038  1285.00   2236.605447
Paris         2273305   105.40  21568.358634
Vienna        1805681   414.60   4355.236372
Bucharest     1803425   228.00   7909.758772
Hamburg       1760433   755.00   2331.699338
Budapest      1754000   525.20   3339.680122
Warsaw        1740119   517.00   3365.800774
Barcelona     1602386   101.90  15725.083415
Munich        1493900   310.40   4812.822165
Milan         1350680   181.80   7429.482948

Damit gibt es drei Spalten zu zeichnen. Für diesen Zweck erstellen wir drei Achsen um die Werte darzustellen:

import matplotlib.pyplot as plt
fig, ax = plt.subplots()
fig.suptitle("City Statistics")
ax.set_ylabel("Population")
ax.set_xlabel("Cities")
ax_area, ax_density = ax.twinx(), ax.twinx() 
ax_area.set_ylabel("Area")
ax_density.set_ylabel("Density")
rspine = ax_density.spines['right']
rspine.set_position(('axes', 1.25))
ax_density.set_frame_on(True)
ax_density.patch.set_visible(False)
fig.subplots_adjust(right=0.75)
city_frame["population"].plot(ax=ax, 
                              style="b-",
                              xticks=range(len(city_frame.index)),
                              use_index=True, 
                              rot=90)
city_frame["area"].plot(ax=ax_area, 
                        style="g-",
                        use_index=True, 
                        rot=90)
city_frame["density"].plot(ax=ax_density, 
                           style="r-",
                           use_index=True, 
                           rot=90)
Wir erhalten die folgende Ausgabe:
<matplotlib.axes._subplots.AxesSubplot at 0x11ed16dd8>

Ein komplexeres Beispiel

Wir nutzen das zuvor erlangte Wissen im nächsten Beispiel. Wir verwenden dazu eine Datei mit Besucher-Statistiken der Webseite python-course.eu. Der Inhalt der Datei sieht folgendermassen aus:

Month Year  "Unique visitors"   "Number of visits"  Pages   Hits    Bandwidth Unit
Jun 2010    11  13  42  290 2.63 MB
Jul 2010    27  39  232 939 9.42 MB
Aug 2010    75  87  207 1,096   17.37 MB
Sep 2010    171 221 480 2,373   39.63 MB
...
Nov 2016    234,518 374,641 832,244 4,378,623   167.68 GB
Dec 2016    209,367 323,845 598,081 3,627,830   145.41 GB
Jan 2017    219,153 346,011 633,984 3,827,909   158.36 GB
Feb 2017    255,869 409,503 752,516 4,630,365   189.43 GB
Mar 2017    284,557 467,802 891,505 5,306,521   221.30 GB
%matplotlib inline
import pandas as pd
data_path = "data1/"
data = pd.read_csv(data_path + "python_course_monthly_history.txt", 
                   quotechar='"',
                   thousands=",",
                   delimiter=r"\s+")
def unit_convert(x):
    value, unit = x
    if unit == "MB":
        value *= 1024
    elif unit == "GB":
        value *= 1048576 # i.e. 1024 **2
    return value
b_and_u= data[["Bandwidth", "Unit"]]
bandwidth = b_and_u.apply(unit_convert, axis=1)
del data["Unit"]
data["Bandwidth"] = bandwidth
month_year =  data[["Month", "Year"]]
month_year = month_year.apply(lambda x: x[0] + " " + str(x[1]), 
                              axis=1)
data["Month"] = month_year
del data["Year"]
data.set_index("Month", inplace=True)
del data["Bandwidth"]
data[["Unique visitors", "Number of visits"]].plot(use_index=True, 
                                                   rot=90,
                                                    xticks=range(1, len(data.index),4))
Der obige Code führt zu folgendem Ergebnis:
<matplotlib.axes._subplots.AxesSubplot at 0x11eee3278>
ratio = pd.Series(data["Number of visits"] / data["Unique visitors"],
                  index=data.index)
ratio.plot(use_index=True, 
           xticks=range(1, len(ratio.index),4),
           rot=90)
Wir erhalten die folgende Ausgabe:
<matplotlib.axes._subplots.AxesSubplot at 0x11edf7278>

Spalten mit Zeichenketten (Strings) in Floats wandeln

Im Verzeichnis data1 haben wir die Datei programming_language_usage.txt mit einem Nutzungs-Ranking von Programmiersprachen. Die Daten wurden gesammelt und aufbereitet von TIOBE im März 2017.

Die Datei hat folgenden Inhalt:

"Mar 2017"  "Language"  Percentage
1       Java    16.384%
2       C   7.742%  
3       C++ 5.184%  
4       C#  4.409%  
5       Python  3.919%  
6       Visual Basic .NET   3.174%  
7       PHP 3.009%  
8       JavaScript  2.667%  
9       Delphi/Object Pascal    2.544%  

Die Prozent (Percentage)-Spalte enthält Strings mit dem Prozent-Zeichen. Das Zeichen können wir nutzen (oder loswerden) indem wir die Funktion read_csv() verwenden. Alles was wir dafür tun müssen, ist eine Konverter-Funktion zu definieren. Diese Konverter-Funktion übergeben wir dann an read_csv() über das converters Dictionary, welches Spaltennamen als Schlüssel und als Werte Referenzen auf Funktionen enthält.

def strip_percentage_sign(x):
    return float(x.strip('%'))
data_path = "data1/"
progs = pd.read_csv(data_path + "programming_language_usage.txt", 
                   quotechar='"',
                   thousands=",",
                   index_col=1,
                   converters={'Percentage':strip_percentage_sign},
                   delimiter=r"\s+")
del progs["Mar 2017"]
print(progs)
progs.plot(xticks=range(len(progs.index)),
           use_index=True, 
           rot=90)
                      Percentage
Language                        
Java                      16.384
C                          7.742
C++                        5.184
C#                         4.409
Python                     3.919
Visual Basic .NET          3.174
PHP                        3.009
JavaScript                 2.667
Delphi/Object Pascal       2.544
Swift                      2.268
Perl                       2.261
Ruby                       2.254
Assembly language          2.232
R                          2.016
Visual Basic               2.008
Objective-C                1.997
Go                         1.982
MATLAB                     1.854
PL/SQL                     1.672
Scratch                    1.472
Wir können die folgenden Ergebnisse erwarten, wenn wir den obigen Python-Code ausführen:
<matplotlib.axes._subplots.AxesSubplot at 0x11f9c8588>

Balken-Grafiken in Pandas

Balken-Grafiken mit Pandas zu erstellen ist ebenso einfach wie Linien-Grafiken. Dazu fügen wir den Schlüsselwort-Parameter kind zu plot-Methode hinzu und setzen den Wert auf bar.

Ein einfaches Beispiel

import pandas as pd
data = [100, 120, 140, 180, 200, 210, 214]
s = pd.Series(data, index=range(len(data)))
s.plot(kind="bar")
Der obige Code führt zu folgendem Ergebnis:
<matplotlib.axes._subplots.AxesSubplot at 0x11faca198>

Balken-Grafik für die Programmiersprachen-Nutzung

WIr gehen zurück zum Beispiel des Programmiersprachen-Rankings. Jetzt generieren eine Balken-Grafik der 6 meistverwendeten Programmiersprachen:

progs[:6].plot(kind="bar")
Wir können die folgende Ausgabe erwarten, wenn wir den obigen Python-Code ausführen:
<matplotlib.axes._subplots.AxesSubplot at 0x11f00c588>

Nun die Balken-Grafik mit allen Programmiersprachen:

progs.plot(kind="bar")
Wir erhalten die folgende Ausgabe:
<matplotlib.axes._subplots.AxesSubplot at 0x11fb84668>

Farbgebung einer Balken-Grafik

Es ist möglich die Balken individuell zu färben, indem eine Liste dem Schlüsselwort-Parameter color zugewiesen wird:

my_colors = ['b', 'r', 'c', 'y', 'g', 'm']
progs[:6].plot(kind="bar",
               color=my_colors)
Der obige Python-Code liefert folgendes Ergebnis:
<matplotlib.axes._subplots.AxesSubplot at 0x12023cf28>

Kuchen-Diagramme in Pandas

Ein einfaches Beispiel

import pandas as pd
fruits = ['apples', 'pears', 'cherries', 'bananas']
series = pd.Series([20, 30, 40, 10], 
                   index=fruits, 
                   name='Fruits')
series.plot.pie(figsize=(6, 6))
Der obige Python-Code führt zu folgender Ausgabe:
<matplotlib.axes._subplots.AxesSubplot at 0x1204b7e80>
fruits = ['apples', 'pears', 'cherries', 'bananas']
series = pd.Series([20, 30, 40, 10], 
                   index=fruits, 
                   name='Fruits')
explode = [0, 0.10, 0.40, 0.7]
series.plot.pie(figsize=(6, 6),
                explode=explode)
Führt man obigen Code aus, erhält man folgende Ausgabe:
<matplotlib.axes._subplots.AxesSubplot at 0x120586828>

Wir generieren die vorherige Balken-Grafik (Programmiersprachen) nun als Kuchen-Diagramm:

import matplotlib.pyplot as plt
my_colors = ['b', 'r', 'c', 'y', 'g', 'm']
progs.plot.pie(subplots=True,
               legend=False)
Der obige Code führt zu folgendem Ergebnis:
array([<matplotlib.axes._subplots.AxesSubplot object at 0x11f6e5940>],
      dtype=object)

Es ist nicht schön, dass das y-Label Percentage innerhalb der Grafik angezeigt wird. Wir entfernen die Bezeichnung mit der Funktion plt.ylabel('').

import matplotlib.pyplot as plt
my_colors = ['b', 'r', 'c', 'y', 'g', 'm']
progs.plot.pie(subplots=True,
               legend=False)
plt.ylabel('')
Wir erhalten die folgende Ausgabe:
Text(0,0.5,'')