In [1]:
import IPython
IPython.__version__
Out[1]:
'7.12.0'

Session 2: Python

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.

Inhalte

  • Wieso Python?
  • Der Python Software Stack
  • Das Python Universum
  • Python in der Praxis
  • Pandas

Weshalb Python?

Wieso verwenden wir Python und nicht eine andere Sprache?

plang_regular plang_most_often plang_recommended

Was sehen wir in den Grafiken?

  • Python ist die populärste verwendete Sprache
  • Als Einstieg mit Abstand die beste Sprache

Info: Was ist kaggle?

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.

Python im Vergleich mit anderen Sprachen

Wichtige Eigenschaften der Sprache:

  • einfache, klare Syntax
  • open source
  • große community
  • vielseitig einsetzbar (nicht nur Datenwissenschaften)
  • portable

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')

Was sehen wir?

  • Pythonsyntax ist simpel:
    • wenig technischer Overhead
    • Blockbildung durch Einrückung -> forciert Übersichtlichkeit
    • dynamische Typisierung (Typ-Prüfung zur Laufzeit vs statische Typisierung: Typ-Prüfung beim Kompilieren)
  • python is flexibel - Unterstützung mehrerer Programmierparadigmen: zB funktional oder objektorientiert (mehr zu Programmierparadigmen)

Wann sollte ich Python verwenden, wann nicht?

  • verwenden wenn

    • Python zur Verfügung steht
    • Entwicklungszeit viel wichtiger ist als Performance
    • oder performante Libraries (zB numpy) die rechenintensiven Operationen übernehmen können. Python ist in dem Fall die high-level Verbindungssprach zu zB C.
  • nicht verwenden wenn

    • Performancemaximierung im Vordergrund steht
    • Spezialanforderungen wie genaues Speichermanagement bestehen (geringe Speicherkontrolle in Python)
    • evtl. wenn mobile Entwicklung im Vordergrund steht (im Bereich mobile Apps ist Verbreitung nicht so groß)

Python Software Stack

PyPI: Python Package Index

  • zentraler Paketpool für Pythonpakete mit +230 000 Projekten (Stand 06.2020)
  • Paketverwaltungsprogramm pip
  • Umgebungsverwaltung durch externe Programme (z. B. virtualenv, venv) oder kombinierte Tools (z. B. pipenv, poetry und hatch)

Anaconda

  • Open-Source Python Distribution mit Schwerpunkt auf Datenwissenschaften.
  • Kommerzielle Erweiterung erhältlich
  • Beeinhaltet Paketverwaltungstool conda. Unterschiede zu pip:
    • kombiniert Paket- und Umgebungsverwaltung
    • installiert binaries, keine Kompilierung erforderlich (wie u.U. bei pip)
    • installiert Pakete sprachunabhängig (zB. Python, R)
    • überprüft Paketabhängigkeiten
  • Anaconda repository enthält weit weniger Pakete (1500+, Stand 11.2018) als PyPI -> Kombination mit pip falls Paket nicht vorhanden

Aufgabe: Installiere Anaconda

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.

Das Python Universum

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

  • Python
  • IPython
  • Jupyter Notebook / Lab
  • klassische integrierte Entwicklungsumgebung (IDE), zB Spyder

Python CLI (command line interface)

  • use it as calculator: 1 + 1
  • normale Benutzung, zB. Paketimport:
    import pandas as pd
      df = pd.DataFrame({'a': [1,2], 'b':[3,4]})
    
  • run a script: python __tmp/hello_world.py
  • Hauptverwendung: Ausführung von .py Scripten
In [3]:
%%writefile __tmp/hello_world.py
print('Hello world!')
Overwriting __tmp/hello_world.py

IPython CLI

  • alles was man mit Python machen kann, plus Verbesserungen

  • allg. Infos, type ? oder help

    • Funktionen haben/sollten einen Docstring haben zB
      def say_this(text):
        """
        I am the Docstring.
        Prints text to screen.
        """
        print('Someone told me I should print {}.'.format(text))
      
    • read docstring with help(say_this) oder say_this?
  • standard CLI für interaktive Arbeiten zB. data exploration

  • syntax highlighting
  • In- Output caching: zB Out[1] oder _i1, _1
  • command line completion: tab
    • aussagekräftie Variablennamen einfach verwendbar
    • PEP 8 Style Guide: variables_are_lower_case_w_underscore
  • command history:
    • erste Buchstaben eingeben, dann Pfeiltasten verwenden
    • strg + r
  • magics

    • %command: line magic , ausgeführt in einer Zeile, Zuweisung möglich
    • %%command: cell magic , ausgführt über mehrere Zeilen, keine Zuweisung möglich
    • Beispiele:
      • %run __tmp/hello_world.py
      • sys commands, list available with %alias
      • timing:
        • %timeit np.arange(1000)
        • %timeit [i for i in range(1000)]
        • -> np version ist ca 27 mal schneller
        • gibt es auch als cell magic: %%timeit
  • !sys_command run system commands -> Achtung: wird in local shell ausgeführt d.h. abhängig davon auf welchem OS (Win, Linux). Beispiele:

Jupyter Notebook

  • Verwendung: Kombination aus Dokumentation und Code zB Datenexploration, Dokumentation oder Präsentation

  • Notebook starten mit jupyter notebook

  • Zellen enthalten Code oder Markdown

Markdown Demo

Level 2 Überschrift

Bullet points

  • Bullet points
    • Noch ein Bullet point

Aufzählung

  1. Aufzählung 1

    1.2 Blubb

  1. Aufzählung 2

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...

Die wichtigsten Tastenkombinationen

Zwei Modes:

  • edit mode (grüne Balken): Enter drücken oder in eine Zelle klicken.
    • shift + enter: Zelle ausführen und weiterspringen
    • strg + enter: Zelle ausführen
  • command mode (blaue Baken): Esc oder auf Bereich außerhalb der Zelle klicken.
    • m: Zelle als Markdown formatieren
    • a: Zelle hinzufügen
    • dd: Zelle löschen
Sonstige wichtige Befehle
  • Zellen kopieren, löschen
  • Undo delete Cells

more magic

Shell Kommandos im Notebook ausführen.

Unter Windows gibt es ein paar Probleme, weil

In [4]:
# !conda list
In [5]:
# %%bash
# conda --version
In [6]:
%%bash
# bash Kommandos ausführen
# ls -l

JupyterLab

  • Weiterentwicklung von Jupyter Notebooks
  • Web IDE like
  • Viele Features

Spyder

  • simple IDE
  • gute Debugging Möglichkeiten
  • Variable Explorer
  • Profiler
  • und vieles mehr

Hinweis

Bei der Installation von Spyder unter WSL kann es zu Problemen kommen. Folgende Links könnten helfen:

Python: Das Wichtigste

Die wichtigsten Eigenschaften von Python für die Praxis

  • Blockbildung durch Einrückungen (4 Leerzeichen)
  • 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...).

  • Alles ist ein Objekt

    • Alles in Python ist ein Objet und fast alles hat Attribute und Methoden
    • Was genau ein Objekt ist wird zT für die Sprachen unterschiedlich definiert. In jedem Fall ist alles ein Objekt in dem Sinne, dass eine Zuweisung zu einer Variable oder die Übergabe an eine Funktion möglich ist.
  • weitere Punkte siehe unten

Loops

Lesender Zugriff auf Elemente iterierbarer Objekte i. d. R. nicht über den Index.

In [17]:
#id0001 live coding: naive loops
In [18]:
l1 = [1, 2, 3]

# non-pythionic
for i in range(len(l1)):
    print(l1[i])
1
2
3
In [19]:
#id0002 live coding: pythionic loops
In [20]:
for z in l1:
    print(z)
1
2
3
In [21]:
#id0003 live coding: zwei Listen
In [22]:
l2 = [10, 20, 30]

# gleichzeitig durch mehrere
# Listen iterieren
for (z1, z2) in zip(l1, l2):
    print(z1, z2)
1 10
2 20
3 30
In [23]:
#id0004 live coding: dicts
In [24]:
# dictionaries
d1 = {'key1': 'value1', 'key2': 2}

for key, val in d1.items():
    print('{}: {}'.format(key, val))
key1: value1
key2: 2

List Comprehensions

Kompakte und schnelle Schreibweise zur Erzeugung von Listen

In [25]:
#id0005 live coding: simple list comprehension
In [26]:
# simple Liste
l1 = [0 for  z in range(3)]
l1
Out[26]:
[0, 0, 0]
In [27]:
#id0006 live coding: conditional list comprehension
In [28]:
l2 = [z for z in range(6) if (z%2 == 0)]
l2
Out[28]:
[0, 2, 4]
In [29]:
#id0007 live coding: mixed dtypes, complex condition
In [30]:
# vermischte Datentypen
# komplexere Abfrage

l3 = [z if (z%2 == 0) else 'ungerade' for z in range(6)]
l3
Out[30]:
[0, 'ungerade', 2, 'ungerade', 4, 'ungerade']
In [31]:
#id0008 live coding: func call
In [32]:
def func(x):
    return 'hoi {}!'.format(x)

l4 = [func(x) for x in range(6)]
l4
Out[32]:
['hoi 0!', 'hoi 1!', 'hoi 2!', 'hoi 3!', 'hoi 4!', 'hoi 5!']

Übung: Schnittmenge zweier Listen (15 Min)

Schreibe eine Funktion get_intersection(l1, l2) die die Schnittmenge zweier Listen l1 und l2 berechnet.

  • Verwende x in some_list um zu überprüfen ob x in der Liste ist.
  • Du kannst ein Element mit some_list.append(new_entry) einer Liste hinzufügen
  • Eine leere Liste erzugst du mit some_list = []
  • Implementiere die Funktion einmal mit und einmal ohne list comprehension.

Welches Ergebnis erhaltet ihr für die listen l1 = [1, 2, 3, 4, 5] und l2 = [2, 4, 5, 7, 9]?

In [33]:
# 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)
In [34]:
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))
[2, 4, 5]
[2, 4, 5]
{2, 4, 5}

Slicing

In [ ]:
l1 = [0, 1, 2, 3]
In [ ]:
#id0009 live coding: slicing start
In [ ]:
l1[1:]
In [ ]:
#id0010 live coding: slicing end
In [ ]:
l1[:-1]
In [ ]:
#id0011 live coding: slice it
In [ ]:
l1[1:2]
In [ ]:
#id0012 live coding: step
In [ ]:
l1[::2]
In [ ]:
#id0013 live coding: reverse
In [ ]:
l1[::-1]
In [ ]:
#id0014 live coding: str
In [ ]:
'das ist ein String'[12:]

Numpy

Grundlegende Datenstruktur für numerische Operationen.

In [ ]:
#id0100 live coding: numpy intro
In [ ]:
import numpy as np
In [ ]:
a1 = np.array([1, 2, 3])
In [ ]:
a2 = np.array([10, 20, 30])
In [ ]:
# Vektoroperationen: elementweise Summe
a1 + a2
In [ ]:
# vs Python (Konkatenation)
l1 = [1, 2, 3]
l2 = [10, 20, 30]
l1 + l2
In [ ]:
a1 * a2
In [ ]:
np.exp(a1)
In [ ]:
np.linspace(0, 10, 11)

Pandas

Überblick

  • pandas DataFrame erzeugen
  • Daten selektieren
    • Spalten
    • masken
    • iloc und loc
  • head(), dtypes, describe(), value_counnts()
  • Funktionen anwenden
    • std, mean, median
    • apply
    • gruppieren

Funktionen von Pandas sind sehr umfangreich. Ein erschöpfende Behandlung ist hier nicht möglich. Weitere Informationen auf der Pandas Website bzw. im Internet.

In [ ]:
import pandas as pd

DataFrame erzeugen

In [ ]:
#id0015 live coding: create df
In [ ]:
data_dict = {'col1': [1, 2, 3],
             'col2': ['a', 'b', 'c']}
df = pd.DataFrame(data_dict)
df

Was sehen wir?

  • im Prinzip wie eine Tabelle
  • Es gibt einen Index
  • Spaltennamen

Importieren und Exportieren

In [ ]:
import os
In [ ]:
#id0016 live coding: os.path.join
In [ ]:
# OS unabhängige Pfadangaben
dir_path = os.path.join('..', '__tmp')
dir_path
In [ ]:
#id0017 live coding: os.path.join 2
In [ ]:
file_path = os.path.join(dir_path, 'df_test.csv')
file_path
In [ ]:
#id0018 live coding: csv export
In [ ]:
# export nach CSV
df.to_csv(file_path)
In [ ]:
#id0019 live coding: csv import
In [ ]:
# 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!

In [ ]:
#id0020 live coding: csv import 2
In [ ]:
pd.read_csv(file_path, index_col=0)
In [ ]:
file_path = os.path.join(dir_path, 'df_test.pkl')
file_path
In [ ]:
#id0021 live coding: pkl export
In [ ]:
# export to pickle format
df.to_pickle(file_path)
In [ ]:
#id0022 live coding: pkl import
In [ ]:
# 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

  • Vergleich verschiedener Formate

Best practice

  • 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).

Datenzugriff

In [ ]:
#id0023 live coding: df col selection
In [ ]:
# Spalten selektieren
df['col1']
In [ ]:
#id0024 live coding: object like access
In [ ]:
# wenn Spaltenname Python Konvention folgt:
df.col1
In [ ]:
#id0024 live coding: add col
In [ ]:
# Spalte hinzufügen
df['col3'] = ['01.03.2020', '13.01.2020', '10.10.2010']
In [ ]:
df
In [ ]:
#id0025 live coding: add more cols
In [ ]:
# mehrere Spalten selektieren
df[['col1', 'col3']]
In [ ]:
#id0026 live coding: masking 1
In [ ]:
# Masken
m1 = df['col1'] == 2
m1
In [ ]:
df[m1]
In [ ]:
#id0027 live coding: masking 2
In [ ]:
# komplexere Masken
m21 = df['col1'] >= 2
m21
In [ ]:
#id0028 live coding: masking 3
In [ ]:
m22 = df['col3'].str.contains('2020')
m22
In [ ]:
#id0029 live coding: masking 4
In [ ]:
m2 = m21 & m22
m2
In [ ]:
df[m2]
In [ ]:
#id0030 live coding: masking 4 compact
In [ ]:
# oder in einer Zeile
m3 = (df['col1'] >= 2) & (df['col3'].str.contains('2020'))
m3

Zeilenzugriff

  • Merkspruch (wie bei Matrizen): "Zeilen zuerst, Spalten später"
  • Im Prinzip sehr viele Zugriffsmöglichkeiten, hier nur die wichtigsten
In [ ]:
#id0031 live coding: rows basics
In [ ]:
# df.loc[Zeile, Spalte]

# einzelne Spalte auswählen
Zeile = 0
Spalte = 'col2'
df.loc[Zeile, Spalte]
In [ ]:
#id0032 live coding: rows and cols
In [ ]:
# mehrere Spalten auswählen
df.loc[0, ['col2', 'col3']]
In [ ]:
#id0033 live coding: masking 5
In [ ]:
# Zeilen über masking auswählen
df.loc[m1]
In [ ]:
#id0034 live coding: masking and cols
In [ ]:
# masking und Spaltenauswahl kombinieren
df.loc[m1, ['col1', 'col2']]

Unterschied slicing Python und Pandas

In [ ]:
#id0035 live coding: recap simple list
In [ ]:
# Wiederholung: slicing in Python
l = [1,2,3]
l
In [ ]:
#id0036 live coding: recap simple list slicing
In [ ]:
l[1:2]
In [ ]:
#id0037 live coding: pd slicing 1
In [ ]:
# pandas label basiertes slicing inkludiert Endpunkte,
# im Gegensatz zu Standardpython
df.loc[1:2]
In [ ]:
#id0038 live coding: pd slicing 2
In [ ]:
# aber bei indexbasiertem ist es wieder standard
df.iloc[1:2]

Referenz vs Kopie

Referenzen müssen von Kopien unterschieden werden um Seiteneffekte zu vermeiden.

In [ ]:
df1 = pd.DataFrame({'a': [0, 1]})
df1
In [ ]:
#id0039 live coding: reference
In [ ]:
df2 = df1
In [ ]:
df2.loc[0, 'a'] = 42
df2

Wie sieht df1 aus?

In [ ]:
df1

df1 und df2 zeigen auf dasselbe Objekt!

In [ ]:
id(df1) == id(df2)

Wie bekomme ich eine Kopie?

In [ ]:
#id0040 live coding: copy
In [ ]:
df3 = df1.copy()
In [ ]:
id(df3) == id(df1)
In [ ]:
df3.loc[0, 'a'] = -10
df3
In [ ]:
df1

df1 und df3 sind Referenzen auf unterschiedliche Objekte!

Views und chained Indexing

Ein view df2 auf ein array df1 ist ein array, dass auf eine Teilmenge der Daten von df1 zeigt.

Grafik von dataquest.io

Dieser Unterschied kann bei Schreibzugriffen zu unerwarteten Resultaten führen:

Grafik von dataquest.io

In [ ]:
df1 = pd.DataFrame({'a': [0, 1]})
df1
In [ ]:
#id0041 live coding: chained indexing
In [ ]:
# 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.

In [ ]:
# Das ist falsch! Nicht verwenden!
df1[df1['a']==1]['a'] = 13
In [ ]:
df1

Wie lösen wir das Problem? Mit loc[]!

In [ ]:
df1.loc[df1['a']==1, 'a'] = 12
df1

Hidden chaining

SettingWithCopyWarning entsteht bei Schreibzugriffen, diese können u.U. sehr weit entfernt von der ursächlichen Codezeile liegen.

In [ ]:
#id0042 live coding: hidden chaining
In [ ]:
tmp = df1[df1['a']==1]

# This can be a copy or a view!
tmp
In [ ]:
# a lot of code
In [ ]:
# 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!

In [ ]:
cpy = df1[df1['a']==0].copy()
cpy
In [ ]:
cpy.loc[cpy['a']==0] = 150
cpy
Falsch negativ Fall (keine Warnung obwohl SettingsWithCopy Fall vorliegt)

Bug on Github

In [ ]:
df1 = pd.DataFrame({'a':[1,2,3],
                    'b': [10,20,30]})
df1
In [ ]:
df1.loc[df1['a']==1, ('a', 'b')]['b'] = -99 # no warning
df1
In [ ]:
df1.loc[df1['a']==1]['b'] = -99
df1

Korrekt ist folgendes

In [ ]:
df1.loc[df1['a']==1, 'b'] = -99
df1.loc[df1['a']==1, ('a', 'b')]
Tipps

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:

Übung: Vektoroperationen

Gegeben ist der unten stehende DataFrame mit zwei Spalten a und b:

In [ ]:
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:

  • wenn der Wert in a gerade ist, ist der Wert in c die Summe der Spalten a und b
  • wenn der Wert in 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):

In [ ]:
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):

In [ ]:
m = (df['a'] % 2) == 0
df.loc[m, 'c2'] = df.loc[m, 'a'] + df.loc[m, 'b']
df
In [ ]:
df.loc[~m, 'c2'] = df.loc[~m, 'a'] - df.loc[~m, 'b']
In [ ]:
df

Lösung (vektorisiert, numpy):

Eine alternative evtl. kompaktere Schreibweise erlaubt numpy mittels np.select

In [ ]:
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.

Geschwindigkeitsvergleich

In [ ]:
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()
In [ ]:
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
In [ ]:
%timeit add_even_loop(df2)
In [ ]:
%timeit add_even_pandas(df2)
In [ ]:
%timeit add_even_numpy(df2)

Fazit:

Die vektorisierten Operationen sind um ein Vielfaches schneller!

Gruppieren

In [ ]:
# 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.

In [ ]:
#id0043 live coding: groupby
In [ ]:
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

In [ ]:
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)

In [ ]:
grp.sum()

Ergbnis für jeden Eintrag (Standardindex)

In [ ]:
grp.cumsum()

Beispiel Zählerstandsvalidierung (vereinfacht)

Tabelle df_imp enthält unplausible Ablesebelege. Hier der Einfacheit halber nur die IDs der Zählwerke (counter_id).

In [ ]:
#id0044 live coding: simple grouping mrv
In [ ]:
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

In [ ]:
df_hist = pd.DataFrame({'counter_id': [1, 2, 2, 3, 3],
                        'daily_cons': [10, 20, 30, 20, np.nan]})
cprint(df_hist)
sum

Wir berechnen den summierten Verbrauch pro Gruppe.

In [ ]:
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:

In [ ]:
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!

In [ ]:
df_imp = df_imp.join(res, on='counter_id', how='left')
cprint(df_imp)

Übung: Mittelwertsberechnung

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:

In [ ]:
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)
In [ ]:
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.

In [ ]:
df_imp = df_imp.join(df_hist.groupby('counter_id').mean(),
                     on='counter_id', how='left') \
               .rename(columns={'daily_cons': 'mean2'},
                       inplace=False)
In [ ]:
cprint(df_imp)

Gruppen analysieren

Hilfreich für die Analyse ist es, sich einzelene Gruppen anzeigen zu lassen. Das geht mit get_group(group_id):

In [ ]:
selection = grp.get_group(2)
selection

selection ist ein gewöhnlicher DataFrame auf dem normal Operationen ausgeführt werden können.

In [ ]:
selection['daily_cons'].sum()

Literatur

Diverse Tutorials im Internet und Bücher. Keine spezielle Empfehlung. Eine kleine Auswahl relevanter Quellen: