The Python Quants



Datenanalyse mit Python

Dr. Yves J. Hilpisch

The Python Quants GmbH

www.pythonquants.com

yves@pythonquants.com

@dyjh

Köln, 5. Quantified Self Meetup – 27. März 2014

Über Mich

Im Überblick:

  • Geschäftsführer der The Python Quants GmbH
  • Gründer der Visixion GmbH – The Python Quants
  • Lehrbeauftragter Mathematical Finance an der Universität des Saarlandes
  • Fokus auf die Finanzindustrie und Financial Analytics
  • Buch (2013) "Derivatives Analytics with Python"
  • Buch (2014) "Python for Finance", O'Reilly
  • Dr.rer.pol in Mathematical Finance
  • Diplom-Kaufmann
  • Kampfkunst und -sport

Vgl. auch www.hilpisch.com.

Typische Probleme in der Datenanalyse

Jeder der mit Daten arbeitet, trifft auf ein oder mehrere Probleme aus der folgenden Liste:

  • Quellen: Daten kommen im Allgemeinen aus unterschiedlichen Quellen, wie z.B. SQL-Datenbanken, Excel-Dateien oder CSV-Dateien
  • Formate: selbst Daten aus der gleichen Quelle, z.B. CSV-Datei, können stark unterschiedlich formattiert sein (z.B. Semikolon statt Komma)
  • Struktur: selbst Daten die aus der gleichen Quelle und im gleichen Format kommen, z.B. als CSV-Datei, können stark unterschiedliche Strukturen (z.B. einfacher Index vs. Multi-Index) aufweisen
  • Vollständigkeit: Daten aus der realen Welt sind, gegeben einen Benchmark (Zeit-)Index, nicht immer vollständig; zwei Zeitreihen können eine unterschiedliche Anzahl an Datenpunkten aufweisen
  • Konventionen: bestimmte Daten, wie z.B. Datum- und Zeitangaben, werden häufig nach stark unterschiedlichen Konventionen abgespeichert (z.B. Tag zuerst vs. Monat zuerst)
  • Interpretation: einige Standarddaten, wie z.B. Datum- und Zeitangaben, sollten einfach und automatisiert zu interpretieren sein
  • Performance: bei großen Datenmengen sollte das Processing von Datan performant von statten gehen

Eine grundlegende Python-Installation für die Datenanalyse umfasst mindestens die folgenden Pakete:

  • Python – der Python-Interpreter selbst
  • NumPy – performante und flexible Array-Operationen
  • SciPy – Sammlung an wissenschaftlich-mathematischen Funktionen
  • pandas – Bibliothek zur effizienten Analyse von Zeitreihendaten
  • PyTables – hierarchische, performante Datenbanktechnologie
  • matplotlib – 2d und 3d Visualisierung
  • IPython – interaktive Umgebung für Datenanalyse und Ergebnispublikation

Am besten nutzt man die Anaconda Distribution oder die Web-basierte Data Analytics Platform Wakari. Beide bieten "vollständige" Python-Umgebungen für die Datenanalyse.

Analyse von Quantified Self-Daten

Alle Datensätze zur Verfügung gestellt von Andreas Schreiber. Mein Dank an dieser Stelle.

Die Daten – Schritte über ein Jahr

Zur Verfügung haben wir einen Datensatz aus einem Fitbit One Activity-Tracker. Die Daten liegen in dem Standardformat "Comma Separated Values" (CSV) vor (= ein Textfile).

Wie öffnen die Datei zum Lesen.

In [1]:
f = open('./data/schritte.csv', 'r')

Der Inhalt kann Zeile für Zeile gelesen und am Bildschirm ausgegeben werden.

In [2]:
for l in range(6):
    print f.readline(),
Aktivitäten
Datum,Verbrannte Kalorien,Schritte,Strecke,Stockwerke,Minuten im Sitzen,Minuten mit leichter Aktivität,Minuten mit relativ hoher Aktivität,Minuten mit sehr hoher Aktivität,Aktivitätskalorien
"01-04-2013","2.439","0","0","0","1.440","0","0","0","0"
"02-04-2013","2.083","3.871","2,85","4","1.273","109","48","10","604"
"03-04-2013","2.324","8.068","5,93","8","1.224","106","87","23","902"
"04-04-2013","2.805","17.190","12,63","23","1.135","113","128","64","1.485"

pandas ist eine speziell für derartige Daten entwickelte Python-Bibliothek.

In [3]:
import pandas as pd
  # pandas Bibliothek
import numpy as np
  # NumPy Bibliothek
%matplotlib inline
  # Plots in IPython Notebook

Daten Einlesen

Die Funktion read_csv erlaubt wertvolle Parametrisierungen für das Einlesen von Daten, die in unterschiedlichen Formaten vorliegen.

In [4]:
schritte = pd.read_csv('./data/schritte.csv', # filename
                       header=1,  # header Reihe
                       index_col=0,  # index Spalte
                       parse_dates=True,  # index als Datum
                       dayfirst=True,  # Tag zuerst (Europa)
                       decimal=',',  # Dezimalzeichen
                       thousands='.')  # Tausenderzeichen

Alle Daten sind nun als pandas DataFrame-Objekt im Speicher.

In [5]:
schritte.info()
<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 359 entries, 2013-04-01 00:00:00 to 2014-03-25 00:00:00
Data columns (total 9 columns):
Verbrannte Kalorien                    359 non-null int64
Schritte                               359 non-null int64
Strecke                                359 non-null float64
Stockwerke                             359 non-null int64
Minuten im Sitzen                      359 non-null int64
Minuten mit leichter Aktivität         359 non-null int64
Minuten mit relativ hoher Aktivität    359 non-null int64
Minuten mit sehr hoher Aktivität       359 non-null int64
Aktivitätskalorien                     359 non-null int64
dtypes: float64(1), int64(8)

Daten Ausgeben

Die Daten können nun z.B. tabellarisch ausgegeben werden.

In [6]:
schritte.ix[100:105]
Out[6]:
Verbrannte Kalorien Schritte Strecke Stockwerke Minuten im Sitzen Minuten mit leichter Aktivität Minuten mit relativ hoher Aktivität Minuten mit sehr hoher Aktivität Aktivitätskalorien
Datum
2013-07-10 2852 18182 13.36 18 1111 99 180 50 1565
2013-07-11 2550 13507 9.93 9 1188 77 140 35 1174
2013-07-12 2900 16558 12.17 17 1044 196 155 45 1690
2013-07-13 2626 14926 10.97 32 1161 106 138 35 1281
2013-07-14 3005 17537 12.89 46 1036 186 166 52 1804

5 rows × 9 columns

Häufig benötigte Statistiken können leicht abgefragt werden.

In [7]:
np.round(schritte.describe(), 1)
Out[7]:
Verbrannte Kalorien Schritte Strecke Stockwerke Minuten im Sitzen Minuten mit leichter Aktivität Minuten mit relativ hoher Aktivität Minuten mit sehr hoher Aktivität Aktivitätskalorien
count 359.0 359.0 359.0 359.0 359.0 359.0 359.0 359.0 359.0
mean 2593.0 12615.3 9.3 21.1 1152.4 118.3 114.9 36.4 1218.7
std 305.0 4735.4 3.5 23.9 143.7 49.1 43.3 21.3 427.4
min 1958.0 0.0 0.0 0.0 182.0 0.0 0.0 0.0 0.0
25% 2404.5 9703.5 7.1 10.0 1114.5 92.0 89.0 22.0 982.0
50% 2565.0 12639.0 9.3 15.0 1166.0 112.0 116.0 32.0 1209.0
75% 2768.0 15641.0 11.5 23.0 1218.5 131.5 145.5 46.5 1461.0
max 3659.0 28457.0 20.9 188.0 1440.0 348.0 239.0 135.0 2633.0

8 rows × 9 columns

Bestimmte Zeiträume lassen sich einfach selektieren.

In [8]:
np.round(schritte[(schritte.index > '2014/1/1')
                 & (schritte.index < '2014/1/31') ].describe())
  # nur Januar 2014 Daten
Out[8]:
Verbrannte Kalorien Schritte Strecke Stockwerke Minuten im Sitzen Minuten mit leichter Aktivität Minuten mit relativ hoher Aktivität Minuten mit sehr hoher Aktivität Aktivitätskalorien
count 29 29 29 29 29 29 29 29 29
mean 2462 10953 8 21 1212 98 98 30 1016
std 327 4443 3 23 89 45 44 19 403
min 1958 3942 3 3 947 33 8 0 130
25% 2342 8573 6 7 1166 75 76 17 899
50% 2405 10551 8 13 1208 89 93 28 995
75% 2582 13312 10 21 1253 106 115 38 1190
max 3286 23180 17 101 1399 241 239 77 2073

8 rows × 9 columns

Daten Visualisieren

Ebenso können die Daten nun sehr leicht visualisiert werden (I).

In [9]:
schritte[['Schritte', 'Strecke']].plot(subplots=True, style='b', figsize=(10, 5))
Out[9]:
array([<matplotlib.axes.AxesSubplot object at 0x107320150>,
       <matplotlib.axes.AxesSubplot object at 0x107348fd0>], dtype=object)

Ebenso können die Daten nun sehr leicht visualisiert werden (II).

In [10]:
schritte[['Schritte', 'Strecke']].hist(bins=30, figsize=(10, 4))
Out[10]:
array([[<matplotlib.axes.AxesSubplot object at 0x1074c5590>,
        <matplotlib.axes.AxesSubplot object at 0x10739ead0>]], dtype=object)

Die Daten lassen sich auch mit weiteren Zeitreihen, z.B. gleitenden Durchschnitten anreichern.

In [11]:
schritte['gldStrecke'] = pd.rolling_mean(schritte['Strecke'], 30)
schritte[['Strecke', 'gldStrecke']].plot(figsize=(10, 4), style=['g', 'r'])
Out[11]:
<matplotlib.axes.AxesSubplot at 0x1074b4a90>

Komplexere Analysen

Wir wollen nun die Aktivität in Abhängigkeit des Wochentags analysieren. Dazu reichern wir den Datensatz um Wochentage an (0 = Montag, 6 = Sonntag).

In [12]:
schritte['Wochentag'] = schritte.index.weekday
schritte[['Schritte', 'Wochentag']].ix[200:205]
Out[12]:
Schritte Wochentag
Datum
2013-10-18 14724 4
2013-10-19 14546 5
2013-10-20 9716 6
2013-10-21 18370 0
2013-10-22 14906 1

5 rows × 2 columns

Zur besseren "menschlichen" Lesbarkeit ergänzen wir die Zahlen durch Abkürzungen für den Wochentagnamen.

In [13]:
schritte['Wochentag'].replace(to_replace=[0, 1, 2, 3, 4, 5, 6],
                 value=['0_Mon', '1_Die', '2_Mit', '3_Don', '4_Fre', '5_Sam', '6_Son'],
                 inplace=True)
schritte[['Schritte', 'Wochentag']].ix[200:205]
Out[13]:
Schritte Wochentag
Datum
2013-10-18 14724 4_Fre
2013-10-19 14546 5_Sam
2013-10-20 9716 6_Son
2013-10-21 18370 0_Mon
2013-10-22 14906 1_Die

5 rows × 2 columns

Nun können wir nach Wochentragen gruppieren ...

In [14]:
grouped = schritte.groupby(['Wochentag'])

... und können uns z.B. den durchschnittlichen Wert je Wochentag ausgeben lassen.

In [15]:
np.round(grouped[['Schritte', 'Strecke']].mean(), 2)
Out[15]:
Schritte Strecke
Wochentag
0_Mon 12796.71 9.41
1_Die 12943.52 9.51
2_Mit 11857.86 8.72
3_Don 13721.80 10.09
4_Fre 12884.43 9.47
5_Sam 12192.94 8.96
6_Son 11899.67 8.75

7 rows × 2 columns

Auch diese Daten sind einfach zu visualisieren.

In [16]:
grouped['Aktivitätskalorien'].mean().plot(kind='barh', grid=True)
Out[16]:
<matplotlib.axes.AxesSubplot at 0x107fbb410>

Datenspeicherung

Mit pandas kann man ganze DataFrame-Objekte einfach speichern und bei Bedarf wieder einlesen.

In [17]:
# Schreiben der Daten als DataFrame
h5 = pd.HDFStore('./data/schritte.h5', 'w')
h5['schrittdaten'] = schritte
h5.close()
In [18]:
ll ./data/schr*
-rw-r--r--@ 1 yhilpisch  staff    26526 27 Mär 09:08 ./data/schritte.csv
-rw-r--r--  1 yhilpisch  staff  1095776 27 Mär 14:23 ./data/schritte.h5

In [19]:
# Einlesen der Daten
h5 = pd.HDFStore('./data/schritte.h5', 'r')
schritte = h5['schrittdaten']
h5.close()

Weitere Beispiele für Quantified Self-Daten

Gewicht

Ein weiterer Datensatz enthält Gewichtsangaben im Zeitablauf (von einer Withings WLAN-Waage).

In [20]:
f = open('./data/gewicht.csv', 'r')
for l in range(5):
    print f.readline(),
"Date","Gewicht (kg)","Fettmasse (kg)","Fettfreier Anteil (kg)","Kommentare"
"2014-03-23 21:35 Uhr","88.05","","",""
"2014-03-23 21:24 Uhr","85.95","","",""
"2014-03-21 8:30 Uhr","85.4","19.4","66",""
"2014-01-31 12:30 Uhr","86.05","","",""

Wiederum lesen wir die Daten mit Hilfe von pandas ein – ohne großartige Parametrisierung.

In [21]:
gewicht = pd.read_csv('./data/gewicht.csv')
In [22]:
gewicht.ix[:5]
Out[22]:
Date Gewicht (kg) Fettmasse (kg) Fettfreier Anteil (kg) Kommentare
0 2014-03-23 21:35 Uhr 88.05 NaN NaN NaN
1 2014-03-23 21:24 Uhr 85.95 NaN NaN NaN
2 2014-03-21 8:30 Uhr 85.40 19.4 66 NaN
3 2014-01-31 12:30 Uhr 86.05 NaN NaN NaN
4 2014-01-31 12:15 Uhr 84.35 NaN NaN NaN
5 2014-01-30 23:09 Uhr 85.50 NaN NaN NaN

6 rows × 5 columns

Wir bereiten die Datumsangaben etwas auf, damit pandas sie interpretieren kann.

In [23]:
gewicht['Date'] = [date.replace(' Uhr', '') for date in gewicht['Date']]
  # lösche " Uhr" in jeder Angabe
In [24]:
from dateutil.parser import parse
gewicht['Date'] = gewicht['Date'].apply(parse)
  # übersetze die Angabe in korrektes Datums-Zeit-Format
In [25]:
gewicht.set_index('Date', inplace=True)
  # mache die "Date" Spalte zum Index

Die Daten liegen wiederum in dem nun bekannten DataFrame-Format im Datenspeicher vor.

In [26]:
gewicht.info()
<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 135 entries, 2014-03-23 21:35:00 to 2012-06-13 14:00:00
Data columns (total 4 columns):
Gewicht (kg)              135 non-null float64
Fettmasse (kg)            99 non-null float64
Fettfreier Anteil (kg)    99 non-null float64
Kommentare                5 non-null object
dtypes: float64(3), object(1)
In [27]:
gewicht.columns = ['Gewicht', 'Fettmasse', 'Fettfrei', 'Kommentar']
  # Umbenennen der Spalten

pandas kümmert sich automatisch um fehlende Daten – Berechnungen sind problemlos auch mit unvollständigen Datensätzen möglich.

In [28]:
np.round(gewicht.describe(), 2)
Out[28]:
Gewicht Fettmasse Fettfrei
count 135.00 99.00 99.00
mean 80.18 19.36 59.82
std 2.44 1.57 1.72
min 76.60 15.56 57.10
25% 78.65 18.55 58.73
50% 79.55 19.45 59.56
75% 80.62 20.44 60.36
max 88.05 22.56 66.00

8 rows × 3 columns

Graphische Auswertungen sind natürlich nun auch wieder einfach möglich.

In [29]:
gewicht['Gewicht'].plot()
Out[29]:
<matplotlib.axes.AxesSubplot at 0x108b23e50>

Blutdruck

Wie bereits gewohnt, zuerst ein Blick in die Rohdaten für die Blutdruckwerte (von der Applikation BlutdruckBegleiter).

In [30]:
f = open('./data/blutdruck.csv', 'r')
for l in range(5):
    print f.readline(),
Datum,Uhrzeit,Systolisch,Diastolisch,Puls,Ort,Position,Geraet,Schlaf,Kommentar
21.04.2012,23:16,120,80,75,Rechter Arm,Sitzend,,,
21.04.2012,23:16,119,77,63,Linker Arm,Liegend,,,
22.04.2012,11:24,117,77,53,Linkes Handgelenk,Sitzend,boso medilife PC3,,
22.04.2012,15:04,122,75,54,Linkes Handgelenk,Sitzend,boso medilife PC3,,

Wir nutzen wiederum pandas als Allzweckwaffe für das Einlesen von Daten.

In [31]:
blutdruck = pd.read_csv('./data/blutdruck.csv',
            index_col=0,  # index Spalte
            parse_dates=[[0, 1]], # kombiniere Spalte 0 & 1
                                  # zu Datum-Zeit-Index
            dayfirst=True)  # Tag zuerst (Europa)

Wie löschen 3 Spalten und schauen uns die letzten Angaben an.

In [32]:
del blutdruck['Geraet']
del blutdruck['Kommentar']
blutdruck.tail()
Out[32]:
Systolisch Diastolisch Puls Ort Position Schlaf
Datum_Uhrzeit
2014-03-11 22:37:00 119 74 60 Linkes Handgelenk Sitzend 6,2
2014-03-15 13:06:00 114 72 61 Linkes Handgelenk Sitzend 9,1
2014-03-17 20:29:00 114 73 57 Linkes Handgelenk Sitzend 6,2
2014-03-19 16:25:00 129 87 65 Linkes Handgelenk Sitzend 7,2
2014-03-23 00:19:00 123 72 59 Linkes Handgelenk Sitzend NaN

5 rows × 6 columns

Wir können nun die wesentlichen Werte visualisieren.

In [33]:
blutdruck[['Systolisch', 'Diastolisch', 'Puls']].plot(
                            subplots=True, style=['b', 'r', 'g'],
                            figsize=(10, 6))
Out[33]:
array([<matplotlib.axes.AxesSubplot object at 0x109097d90>,
       <matplotlib.axes.AxesSubplot object at 0x1090d5f10>,
       <matplotlib.axes.AxesSubplot object at 0x109211b10>], dtype=object)

Um einfacher Trends feststellen zu können, können wir z.B. auch 2-Wochen gleitende Durchschnitte verwenden.

In [34]:
pd.rolling_mean(blutdruck[['Systolisch', 'Diastolisch']], 14).plot(
                            style=['b', 'r'], figsize=(10, 6))
Out[34]:
<matplotlib.axes.AxesSubplot at 0x109226450>

Warum Python für die Datenanalyse?

Am Beispiel der durchweg genutzten Bibliothek pandas kann man dies gut illustrieren:

  • Quellen: pandas kann Daten aus unterschiedlichen Quellen einlesen (SQL-Datenbanken, CSV-Dateien etc.)
  • Formate: pandas kann unterschiedliche Formate erkennen und einlesen (z.B. unterschiedlich formattierte CSV-Dateien)
  • Struktur: pandas erkennt Datenstrukturen und bildet diese intern entsprechend ab (z.B. Zeitindex für Zeitreihendaten sowie Spaltennamen)
  • Vollständigkeit: pandas behandelt fehlende Daten automatisch und elegant; man kann mit unvollständigen Datensätzen häufig so arbeiten wie mit vollständigen
  • Konvention/Interpretation: pandas kennt z.B. viele unterschiedliche Konventionen für Datumsangaben und interpretiert diese entsprechend
  • Performanz: die pandas Klassen, Methoden und Funktionen sind i.d.R. performant implementiert unter Nutzung von z.B. Cython als Python/C-Compiler

Python zeichnet sich darüber hinaus aber auch durch eine Reihe weiterer Merkmale positiv aus:

  • Allzweck: Python kann für alles, vom Prototyping bis in die Produktion eingesetzt werden
  • Bibliotheken: es gibt eine Masse an wertvollen Bibliotheken für nahezu alle Einsatzbereiche
  • Effizienz: nach kurzer Einarbeitung ist man in der Regel sehr effizient im Umgang mit der Sprache und Daten
  • Perfomanz: Python macht den Zugriff auf moderne High Performance-Technologie i.d.R. recht einfach (e.g. LLVM, multi-core CPUs, GPUs, clusters)
  • Interoparabilität: Python versteht sich gut mit anderen Technologien, Sprachen und Umgebungen
  • Interaktivität: Python erlaubt Interaktivität in Echtzeit sowie einfaches Lernen durch "Versuch und Irrtum"
  • Zusammenarbeit: Python und Technologien wie Wakari erlauben die einfache Zusammenarbeit von Mehreren und Teams an Datananalyse-Projekten

Wakari – Web- and Python-based Data Analytics for Teams

Wakari

http://enterprise.wakari.io

The Python Quants



The Python Quants GmbH – the company Web site

www.pythonquants.com

Dr. Yves J. Hilpisch – my personal Web site

www.hilpisch.com

Python for Finance – my NEW book (out as Early Release)

Python for Finance (O'Reilly)

Derivatives Analytics with Python – my new book

www.derivatives-analytics-with-python.com

Contact Us

yves@pythonquants.com | @dyjh