import IPython
IPython.__version__
Die Informationen in diesem Kapitel bieten eine (subjektive) Auswahl der wichtigsten Punkte und sollen dazu dienen einen Überblick zum Thema Python zu bekommen. Bei detailierteren Fragestellungen sollten unbedingt weitere Quellen herangezogen werden.
Wieso verwenden wir Python und nicht eine andere Sprache?
Kaggle ist eine Online-Community, die sich an Datenwissenschaftler richtet. Kaggle ist im Besitz der Google LLC. Der Hauptzweck von Kaggle ist die Organisation von Data-Science-Wettbewerben. Die Anwendungspalette ist im Laufe der Zeit stetig vergrößert worden. Heute ermöglicht Kaggle es Anwendern unter anderem auch, Datensätze zu finden und zu veröffentlichen, Modelle in einer webbasierten datenwissenschaftlichen Umgebung zu erforschen und zu erstellen und mit anderen Datenwissenschaftlern und Ingenieuren des maschinellen Lernens zusammenzuarbeiten. Kaggle bietet auch eine öffentliche Datenplattform, eine Cloud-basierte Workbench für die Datenwissenschaft und eine Kurzform der KI-Ausbildung an.
Wichtige Eigenschaften der Sprache:
Beispiel Java (objektorientierte Sprache)
public class HelloWorld{
public static void main(String []args){
System.out.println("Hello World");
}
}
Beispiel Python
print('Hello World')
Beispiel Python (objektorientiert)
class HelloWorld:
def __init__(self):
print('Hello World')
verwenden wenn
nicht verwenden wenn
pip
conda
. Unterschiede zu pip:Installiere Anaconda für python 3 von https://www.anaconda.com/products/individual
Öffne den Anaconda Prompt und tippe folgende Befehle ein:
Zeige allg. Informationen:
conda info
Liste alle installierten Pakete:
conda list
Liste alle Umgebungen
conda env list
Erstelle eine neue Umgebung schulung mit Python 3
conda create --name schulung python=3
Aktiviere Umgebung schulung
conda activate schulung
Installiere Pakete
conda install numpy pandas
Lösche Umgebung
conda env remove --name schulung
Navigiere in den Ordner mit der Datei 'env.yml' und installiere vollständige Umgebung erneut (ca. 10 Minuten)
conda env create -f env.yml
Siehe auch das conda Cheat Sheet und den Userguide für mehr Details zu conda.
Achtung: es gibt zwei Versionen: Python 2 und Python 3. Python 3+ ist die aktuelle Version! Infos zur Portierung.)
Es gibt mehrere Umgebungen in denen Python verwendet werden kann
1 + 1
import pandas as pd
df = pd.DataFrame({'a': [1,2], 'b':[3,4]})
python __tmp/hello_world.py
%%writefile __tmp/hello_world.py
print('Hello world!')
allg. Infos, type ?
oder help
def say_this(text):
"""
I am the Docstring.
Prints text to screen.
"""
print('Someone told me I should print {}.'.format(text))
help(say_this)
oder say_this?
standard CLI für interaktive Arbeiten zB. data exploration
Out[1]
oder _i1
, _1
%command
: line magic , ausgeführt in einer Zeile, Zuweisung möglich%%command
: cell magic , ausgführt über mehrere Zeilen, keine Zuweisung möglich%run __tmp/hello_world.py
%alias
%timeit np.arange(1000)
%timeit [i for i in range(1000)]
%%timeit
!sys_command
run system commands -> Achtung: wird in local shell ausgeführt d.h. abhängig davon auf welchem OS (Win, Linux). Beispiele:
!dir
, Linux:!ls -l
!chdir
, Linux:!pwd
Verwendung: Kombination aus Dokumentation und Code zB Datenexploration, Dokumentation oder Präsentation
Notebook starten mit jupyter notebook
Aufzählung 1
1.2 Blubb
Code line: code has back-ticks
Code Block
def func():
"""
Example function.
"""
# function body
pass # this is used as placeholder
LaTeX Code: $ e^{i\pi} = -1$
Links: Mehr über Markdown
Formatierung: fett , kursiv , fett und kursiv
und noch mehr...
Zwei Modes:
Shell Kommandos im Notebook ausführen.
Unter Windows gibt es ein paar Probleme, weil
# !conda list
# %%bash
# conda --version
%%bash
# bash Kommandos ausführen
# ls -l
Die wichtigsten Eigenschaften von Python für die Praxis
Dynamische Typisierung -> keine explizite Typendeklaration -> Problematisch bei komplexen Datentypen zB. Return Wert an REST API. In diesem Fall erst Erkundung des Objects notwendig (in CLI, Dokumentation, Quellcode...).
weitere Punkte siehe unten
Lesender Zugriff auf Elemente iterierbarer Objekte i. d. R. nicht über den Index.
#id0001 live coding: naive loops
l1 = [1, 2, 3]
# non-pythionic
for i in range(len(l1)):
print(l1[i])
#id0002 live coding: pythionic loops
for z in l1:
print(z)
#id0003 live coding: zwei Listen
l2 = [10, 20, 30]
# gleichzeitig durch mehrere
# Listen iterieren
for (z1, z2) in zip(l1, l2):
print(z1, z2)
#id0004 live coding: dicts
# dictionaries
d1 = {'key1': 'value1', 'key2': 2}
for key, val in d1.items():
print('{}: {}'.format(key, val))
Kompakte und schnelle Schreibweise zur Erzeugung von Listen
#id0005 live coding: simple list comprehension
# simple Liste
l1 = [0 for z in range(3)]
l1
#id0006 live coding: conditional list comprehension
l2 = [z for z in range(6) if (z%2 == 0)]
l2
#id0007 live coding: mixed dtypes, complex condition
# vermischte Datentypen
# komplexere Abfrage
l3 = [z if (z%2 == 0) else 'ungerade' for z in range(6)]
l3
#id0008 live coding: func call
def func(x):
return 'hoi {}!'.format(x)
l4 = [func(x) for x in range(6)]
l4
Schreibe eine Funktion get_intersection(l1, l2)
die die Schnittmenge zweier Listen l1 und l2 berechnet.
x in some_list
um zu überprüfen ob x in der Liste ist.some_list.append(new_entry)
einer Liste hinzufügensome_list = []
Welches Ergebnis erhaltet ihr für die listen l1 = [1, 2, 3, 4, 5]
und l2 = [2, 4, 5, 7, 9]
?
# solution
def get_intersection1(l1, l2):
intersection = []
for elem in l1:
if elem in l2:
intersection.append(elem)
return intersection
def get_intersection2(l1, l2):
return [elem for elem in l1 if elem in l2]
# Achtung: sets speichern nicht die Reihenfolge der Elemente!
# "Being an unordered collection, sets do not record element
# position or order of insertion."
# https://docs.python.org/3.8/library/stdtypes.html#set-types-set-frozenset
#
def get_intersection3(l1, l2):
return set(l1).intersection(l2)
l1 = [1, 2, 3, 4, 5]
l2 = [2, 4, 5, 7, 9]
print(get_intersection1(l1, l2))
print(get_intersection2(l1, l2))
print(get_intersection3(l1, l2))
l1 = [0, 1, 2, 3]
#id0009 live coding: slicing start
l1[1:]
#id0010 live coding: slicing end
l1[:-1]
#id0011 live coding: slice it
l1[1:2]
#id0012 live coding: step
l1[::2]
#id0013 live coding: reverse
l1[::-1]
#id0014 live coding: str
'das ist ein String'[12:]
Grundlegende Datenstruktur für numerische Operationen.
#id0100 live coding: numpy intro
import numpy as np
a1 = np.array([1, 2, 3])
a2 = np.array([10, 20, 30])
# Vektoroperationen: elementweise Summe
a1 + a2
# vs Python (Konkatenation)
l1 = [1, 2, 3]
l2 = [10, 20, 30]
l1 + l2
a1 * a2
np.exp(a1)
np.linspace(0, 10, 11)
Funktionen von Pandas sind sehr umfangreich. Ein erschöpfende Behandlung ist hier nicht möglich. Weitere Informationen auf der Pandas Website bzw. im Internet.
import pandas as pd
#id0015 live coding: create df
data_dict = {'col1': [1, 2, 3],
'col2': ['a', 'b', 'c']}
df = pd.DataFrame(data_dict)
df
Was sehen wir?
import os
#id0016 live coding: os.path.join
# OS unabhängige Pfadangaben
dir_path = os.path.join('..', '__tmp')
dir_path
#id0017 live coding: os.path.join 2
file_path = os.path.join(dir_path, 'df_test.csv')
file_path
#id0018 live coding: csv export
# export nach CSV
df.to_csv(file_path)
#id0019 live coding: csv import
# import
pd.read_csv(file_path)
Typisches Problem von CSV: intensive Überprüfung der eingelesenen Daten notwendig! Index wird als extra Spalte eingelesen.
Das parsen des Texts ist ein relativ langsamer, speicherintensiver und fehleranfälliger Prozess.
Vorteil CSV: Textdateien sind universell kompatibel. Das hat allerdings seinen Preis!
#id0020 live coding: csv import 2
pd.read_csv(file_path, index_col=0)
file_path = os.path.join(dir_path, 'df_test.pkl')
file_path
#id0021 live coding: pkl export
# export to pickle format
df.to_pickle(file_path)
#id0022 live coding: pkl import
# import from pickle format
pd.read_pickle(file_path)
Das pickle Format ist das Standard binary Format für Python Objekte. Es ist schneller, und kleiner als CSV. Zudem speichert es den Datentyp.
Bessere Wahl für Interpythonaustausch.
Noch etwas performanter: feather
Wenn nicht die Portabilität von csv notwendig ist: binäres Format wählen!
Anpassung der Datentypen und Übeprüfung der Daten beim csv Format kann ein zeitintensiver Prozess sein.
Ist die Verwendung von pickle oder feather nicht möglich, ist das Excel Format u. U. zu empfehlen (wobei der Pandas Export zT. auch sehr langsam sein kann).
#id0023 live coding: df col selection
# Spalten selektieren
df['col1']
#id0024 live coding: object like access
# wenn Spaltenname Python Konvention folgt:
df.col1
#id0024 live coding: add col
# Spalte hinzufügen
df['col3'] = ['01.03.2020', '13.01.2020', '10.10.2010']
df
#id0025 live coding: add more cols
# mehrere Spalten selektieren
df[['col1', 'col3']]
#id0026 live coding: masking 1
# Masken
m1 = df['col1'] == 2
m1
df[m1]
#id0027 live coding: masking 2
# komplexere Masken
m21 = df['col1'] >= 2
m21
#id0028 live coding: masking 3
m22 = df['col3'].str.contains('2020')
m22
#id0029 live coding: masking 4
m2 = m21 & m22
m2
df[m2]
#id0030 live coding: masking 4 compact
# oder in einer Zeile
m3 = (df['col1'] >= 2) & (df['col3'].str.contains('2020'))
m3
#id0031 live coding: rows basics
# df.loc[Zeile, Spalte]
# einzelne Spalte auswählen
Zeile = 0
Spalte = 'col2'
df.loc[Zeile, Spalte]
#id0032 live coding: rows and cols
# mehrere Spalten auswählen
df.loc[0, ['col2', 'col3']]
#id0033 live coding: masking 5
# Zeilen über masking auswählen
df.loc[m1]
#id0034 live coding: masking and cols
# masking und Spaltenauswahl kombinieren
df.loc[m1, ['col1', 'col2']]
"Note that contrary to usual python slices, both the start and the stop are included, when present in the index!"
sehr viele Möglichkeiten, nur die wichtigsten hier
Mehr infos:
#id0035 live coding: recap simple list
# Wiederholung: slicing in Python
l = [1,2,3]
l
#id0036 live coding: recap simple list slicing
l[1:2]
#id0037 live coding: pd slicing 1
# pandas label basiertes slicing inkludiert Endpunkte,
# im Gegensatz zu Standardpython
df.loc[1:2]
#id0038 live coding: pd slicing 2
# aber bei indexbasiertem ist es wieder standard
df.iloc[1:2]
Referenzen müssen von Kopien unterschieden werden um Seiteneffekte zu vermeiden.
df1 = pd.DataFrame({'a': [0, 1]})
df1
#id0039 live coding: reference
df2 = df1
df2.loc[0, 'a'] = 42
df2
Wie sieht df1 aus?
df1
df1 und df2 zeigen auf dasselbe Objekt!
id(df1) == id(df2)
Wie bekomme ich eine Kopie?
#id0040 live coding: copy
df3 = df1.copy()
id(df3) == id(df1)
df3.loc[0, 'a'] = -10
df3
df1
df1 und df3 sind Referenzen auf unterschiedliche Objekte!
Ein view df2
auf ein array df1
ist ein array, dass auf eine Teilmenge der Daten von df1
zeigt.
Dieser Unterschied kann bei Schreibzugriffen zu unerwarteten Resultaten führen:
df1 = pd.DataFrame({'a': [0, 1]})
df1
#id0041 live coding: chained indexing
# chained indexing!
df1[df1['a']==1]['a']
Chained Assignment! Erst wird df1 maskiert, das Resultat kann ein View oder eine Kopie sein! Dann wird Spalte 'a' von diesem Objekt der Wert 13 zugewiesen. Ist bei der ersten Operation jedoch eine Kopie zurückgegeben worden, ist die Zuweisungsoperation nutzlos, da sie nur auf die temporäre Kopie angewendet wird.
# Das ist falsch! Nicht verwenden!
df1[df1['a']==1]['a'] = 13
df1
Wie lösen wir das Problem? Mit loc[]!
df1.loc[df1['a']==1, 'a'] = 12
df1
SettingWithCopyWarning entsteht bei Schreibzugriffen, diese können u.U. sehr weit entfernt von der ursächlichen Codezeile liegen.
#id0042 live coding: hidden chaining
tmp = df1[df1['a']==1]
# This can be a copy or a view!
tmp
# a lot of code
# hidden chaining
tmp.loc[tmp['a']==0] = 150
tmp
Das Problem ist hier, das tmp
ein View oder eine Kopie sein kann! Wie lösen wir das? Explizit eine Kopie anlegen!
cpy = df1[df1['a']==0].copy()
cpy
cpy.loc[cpy['a']==0] = 150
cpy
df1 = pd.DataFrame({'a':[1,2,3],
'b': [10,20,30]})
df1
df1.loc[df1['a']==1, ('a', 'b')]['b'] = -99 # no warning
df1
df1.loc[df1['a']==1]['b'] = -99
df1
Korrekt ist folgendes
df1.loc[df1['a']==1, 'b'] = -99
df1.loc[df1['a']==1, ('a', 'b')]
Für das debuggen der SettingsWithCopyWarning kann es hilfreich sein nicht nur eine Warnung anzuzeigen, sondern direkt eine Exception zu werfen. In diesem Fall wird sofort die Codezeile deutlich bei der die Warnung auftritt (jedoch nicht die ursächliche Codezeile). Dazu müssen die Einstellungen in Pandas geändert werden mit
pd.options.mode.chained_assignment = 'raise'
Weitere Informationen:
Gegeben ist der unten stehende DataFrame mit zwei Spalten a
und b
:
df = pd.DataFrame({'a': [1, 2, 3, 4],
'b': [1, 1, 1, 1]})
df
Aufgabe: Füge eine neue Spalte c
hinzu deren Werte folgendermaßen bestimmt werden:
a
gerade ist, ist der Wert in c
die Summe der Spalten a
und b
a
ungerade ist, ist der Wert in c
die Differenz der Spalten a
und b
.Berechne die Spalte c
einmal mittels einer for
Schleife und dann ohne eine for
Schleife zu verwenden (vektorisiert).
Lösung (naiv):
for i in range(len(df)):
a, b = df.loc[i, 'a'], df.loc[i, 'b']
df.loc[i, 'c1'] = a + b if a%2 == 0 else a - b
df
Lösung (vektorisiert, pandas):
m = (df['a'] % 2) == 0
df.loc[m, 'c2'] = df.loc[m, 'a'] + df.loc[m, 'b']
df
df.loc[~m, 'c2'] = df.loc[~m, 'a'] - df.loc[~m, 'b']
df
Lösung (vektorisiert, numpy):
Eine alternative evtl. kompaktere Schreibweise erlaubt numpy mittels np.select
condlist = [m, ~m]
choicelist = [df['a'] + df['b'],
df['a'] - df['b']]
df['c3'] = np.select(condlist, choicelist)
df
Weshalb sind die Werte in c2
floats und in c3
integers? Das liegt an dem Zwischenschritt bei der Berechnung von c2
bei dem NaN
Werte entstanden sind. Diese haben dtype float.
N = 1000
a = np.round(np.random.random(N) * 1e7,)
b = np.ones(N)
df2 = pd.DataFrame({'a':a, 'b': b}, dtype='int')
df2.head()
def add_even_loop(df):
for i in range(len(df)):
a, b = df.loc[i, 'a'], df.loc[i, 'b']
df.loc[i, 'c0'] = a + b if a%2 == 0 else a - b
return df
def add_even_pandas(df):
m = (df['a'] % 2) == 0
df.loc[m, 'c'] = df.loc[m, 'a'] + df.loc[m, 'b']
df.loc[~m, 'c'] = df.loc[~m, 'a'] - df.loc[~m, 'b']
return df
def add_even_numpy(df):
m = (df['a'] % 2) == 0
condlist = [m, ~m]
choicelist = [df['a'] + df['b'],
df['a'] - df['b']]
df['c2'] = np.select(condlist, choicelist)
return df
%timeit add_even_loop(df2)
%timeit add_even_pandas(df2)
%timeit add_even_numpy(df2)
Fazit:
Die vektorisierten Operationen sind um ein Vielfaches schneller!
# helper functions
def _color_groups(row, group_key='group_id'):
cdict = {1: 'red',
2: 'blue',
3: 'green'}
color = cdict.get(row[group_key], 'black')
return ['color: %s' % color] * len(row)
def cprint(df):
"""
Helper function which colors each group.
"""
group_key = 'group_id' if 'group_id' in \
df.columns else 'counter_id'
display(df.style.apply(lambda row: _color_groups(row, group_key),
axis=1))
Pandas ermöglicht es Daten in Tabellen zu gruppieren und Operationen auf diesen Gruppen auszuführen.
#id0043 live coding: groupby
df = pd.DataFrame({'group_id': [1, 2, 2, 3, 3],
'qty': [10, 20, 30, 20, np.nan]})
cprint(df)
DataFrameGroupBy Objekt generieren um Operationen auf den Gruppen auszuführen
grp = df.groupby('group_id')
grp
Verschiedene Funktionen können auf den Gruppen ausgeführt werden z. B.:
sum: Summe für jede Gruppe
cumsum: kumulative Summe für jede Gruppe
count: Anzahl nicht fehlender Werte für jede Gruppe
mean, median, std: Mittelwert, Median und Standardabweichung für jede Gruppe fehlende Werte ignorierend
Ergbnis ist pro Gruppe (group_id
ist index)
grp.sum()
Ergbnis für jeden Eintrag (Standardindex)
grp.cumsum()
Tabelle df_imp
enthält unplausible Ablesebelege. Hier der Einfacheit halber nur die IDs der Zählwerke (counter_id
).
#id0044 live coding: simple grouping mrv
df_imp = pd.DataFrame({'counter_id': [1,2,3]})
cprint(df_imp)
Die Tabelle df_hist
enthält die historischen Verbrauch daily_cons
zu jedem Gerät
df_hist = pd.DataFrame({'counter_id': [1, 2, 2, 3, 3],
'daily_cons': [10, 20, 30, 20, np.nan]})
cprint(df_hist)
Wir berechnen den summierten Verbrauch pro Gruppe.
grp = df_hist.groupby('counter_id')
res = grp.sum()
res
Der Name daily_cons
in res
ist falsch. Wir haben gerade die Summe darüber berechnet!
Wir können das aber einfach umbenennen:
res.rename(columns={'daily_cons': 'sum'}, inplace=True)
res
Wie weisen wir jetzt über die Gruppen in df_hist
ermittelte Werte in res
den Werten in df_imp
zu? Verwende join
!
df_imp = df_imp.join(res, on='counter_id', how='left')
cprint(df_imp)
Berechne den Mittelwert über alle Gruppen in df_hist
und join das Ergbnis in df_imp
. Achte darauf, dass die Ergebnissspalte den Namen 'mean' trägt. Versuche die Operationen in zwei Zeilen auszudrücken.
Lösung:
df_imp = df_imp.join(df_hist.groupby('counter_id').mean(),
on='counter_id', how='left')
df_imp.rename(columns={'daily_cons': 'mean'}, inplace=True)
cprint(df_imp)
Gehts es auch in einer Zeile? Ja, allerdings ist das nicht der Übersicht zuträglich. Beachte, dass bei der rename Operation inplace=False
gesetzt ist, da ansonsten nur None
zurückggeben wird.
df_imp = df_imp.join(df_hist.groupby('counter_id').mean(),
on='counter_id', how='left') \
.rename(columns={'daily_cons': 'mean2'},
inplace=False)
cprint(df_imp)
Hilfreich für die Analyse ist es, sich einzelene Gruppen anzeigen zu lassen. Das geht mit get_group(group_id)
:
selection = grp.get_group(2)
selection
selection
ist ein gewöhnlicher DataFrame auf dem normal Operationen ausgeführt werden können.
selection['daily_cons'].sum()
Diverse Tutorials im Internet und Bücher. Keine spezielle Empfehlung. Eine kleine Auswahl relevanter Quellen:
stackoverflow: Von Entwicklern für Entwickler. Umfangreiche Fragensammlung rund ums Programmieren.
Dive Into Python 3: frei online einsehbares Buch zu Python
Kompakte Python Übersicht, TU-Chemnitz, Holger Trapp