Dr. Yves J. Hilpisch
The Python Quants GmbH
Köln, 5. Quantified Self Meetup – 27. März 2014
Im Überblick:
Vgl. auch www.hilpisch.com.
Jeder der mit Daten arbeitet, trifft auf ein oder mehrere Probleme aus der folgenden Liste:
Eine grundlegende Python-Installation für die Datenanalyse umfasst mindestens die folgenden Pakete:
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.
Alle Datensätze zur Verfügung gestellt von Andreas Schreiber. Mein Dank an dieser Stelle.
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.
f = open('./data/schritte.csv', 'r')
Der Inhalt kann Zeile für Zeile gelesen und am Bildschirm ausgegeben werden.
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.
import pandas as pd
# pandas Bibliothek
import numpy as np
# NumPy Bibliothek
%matplotlib inline
# Plots in IPython Notebook
Die Funktion read_csv erlaubt wertvolle Parametrisierungen für das Einlesen von Daten, die in unterschiedlichen Formaten vorliegen.
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.
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)
Die Daten können nun z.B. tabellarisch ausgegeben werden.
schritte.ix[100:105]
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.
np.round(schritte.describe(), 1)
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.
np.round(schritte[(schritte.index > '2014/1/1')
& (schritte.index < '2014/1/31') ].describe())
# nur Januar 2014 Daten
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
Ebenso können die Daten nun sehr leicht visualisiert werden (I).
schritte[['Schritte', 'Strecke']].plot(subplots=True, style='b', figsize=(10, 5))
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).
schritte[['Schritte', 'Strecke']].hist(bins=30, figsize=(10, 4))
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.
schritte['gldStrecke'] = pd.rolling_mean(schritte['Strecke'], 30)
schritte[['Strecke', 'gldStrecke']].plot(figsize=(10, 4), style=['g', 'r'])
<matplotlib.axes.AxesSubplot at 0x1074b4a90>
Wir wollen nun die Aktivität in Abhängigkeit des Wochentags analysieren. Dazu reichern wir den Datensatz um Wochentage an (0 = Montag, 6 = Sonntag).
schritte['Wochentag'] = schritte.index.weekday
schritte[['Schritte', 'Wochentag']].ix[200:205]
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.
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]
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 ...
grouped = schritte.groupby(['Wochentag'])
... und können uns z.B. den durchschnittlichen Wert je Wochentag ausgeben lassen.
np.round(grouped[['Schritte', 'Strecke']].mean(), 2)
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.
grouped['Aktivitätskalorien'].mean().plot(kind='barh', grid=True)
<matplotlib.axes.AxesSubplot at 0x107fbb410>
Mit pandas kann man ganze DataFrame-Objekte einfach speichern und bei Bedarf wieder einlesen.
# Schreiben der Daten als DataFrame
h5 = pd.HDFStore('./data/schritte.h5', 'w')
h5['schrittdaten'] = schritte
h5.close()
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
# Einlesen der Daten
h5 = pd.HDFStore('./data/schritte.h5', 'r')
schritte = h5['schrittdaten']
h5.close()
Ein weiterer Datensatz enthält Gewichtsangaben im Zeitablauf (von einer Withings WLAN-Waage).
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.
gewicht = pd.read_csv('./data/gewicht.csv')
gewicht.ix[:5]
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.
gewicht['Date'] = [date.replace(' Uhr', '') for date in gewicht['Date']]
# lösche " Uhr" in jeder Angabe
from dateutil.parser import parse
gewicht['Date'] = gewicht['Date'].apply(parse)
# übersetze die Angabe in korrektes Datums-Zeit-Format
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.
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)
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.
np.round(gewicht.describe(), 2)
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.
gewicht['Gewicht'].plot()
<matplotlib.axes.AxesSubplot at 0x108b23e50>
Wie bereits gewohnt, zuerst ein Blick in die Rohdaten für die Blutdruckwerte (von der Applikation BlutdruckBegleiter).
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.
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.
del blutdruck['Geraet']
del blutdruck['Kommentar']
blutdruck.tail()
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.
blutdruck[['Systolisch', 'Diastolisch', 'Puls']].plot(
subplots=True, style=['b', 'r', 'g'],
figsize=(10, 6))
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.
pd.rolling_mean(blutdruck[['Systolisch', 'Diastolisch']], 14).plot(
style=['b', 'r'], figsize=(10, 6))
<matplotlib.axes.AxesSubplot at 0x109226450>
Am Beispiel der durchweg genutzten Bibliothek pandas kann man dies gut illustrieren:
Python zeichnet sich darüber hinaus aber auch durch eine Reihe weiterer Merkmale positiv aus:
Wakari – Web- and Python-based Data Analytics for Teams
The Python Quants GmbH – the company Web site
Dr. Yves J. Hilpisch – my personal Web site
Python for Finance – my NEW book (out as Early Release)
Derivatives Analytics with Python – my new book
www.derivatives-analytics-with-python.com
Contact Us