Bedingte Berechnungen in Pandas

Neue Spalten in einem Data Frame anzulegen, die sich durch bedingte Berechnungen ableiten, ist ein sehr häufiger Task im Datenmanagementprozess. Für Data Frames lassen sich bedingte Berechnungen auf 3 Arten durchführen:

  1. Die Anwendung der numpy-Klassenmethode where.
  2. List-Comprehension mit Conditional Expression.
  3. Mapping-Objekt mit den Berechnungsvorschriften als anonyme (lambda-) Funktionen.

Welche Technik eingesetzt werden sollte, hängt im Wesentlichen davon ab, ob die Berechnung genau 2 oder mehr als 2 Bedingungen enthält. Für die konkrete Umsetzung ist außerdem von Bedeutung, ob 1 Spalte oder mehr als 1 Spalte aus dem DataFrame für die Berechnung benötigt wird.

Inhalt

Um die Techniken vorzuführen, erstellen wir uns zunächst einen kleinen Beispieldatensatz:

In [106]:
import pandas as pd
import numpy as np
df = pd.DataFrame({'Name' : ["Peter", "Karla", "Anne", "Nino", "Andrzej"],
                   'Geschlecht': ['M','W','W','M','M'],
                   'Alter': [34, 53, 16, 22, 61],
                   'Nationalität': ["deutsch", "schweizerisch", "deutsch", "italienisch", "polnisch"],
                   'Gehalt': [3400, 4000, 0, np.NaN, 2300]}, 
                  index = ['ID-123', 'ID-462', 'ID-111', 'ID-997', 'ID-707'])

df
Out[106]:
Alter Gehalt Geschlecht Name Nationalität
ID-123 34 3400.0 M Peter deutsch
ID-462 53 4000.0 W Karla schweizerisch
ID-111 16 0.0 W Anne deutsch
ID-997 22 NaN M Nino italienisch
ID-707 61 2300.0 M Andrzej polnisch

Es existieren genau 2 Bedingungen

Die Berechnung benötigt eine Spalte des DataFrames

Nehmen wir folgendes Beispiel an: Es soll eine Spalte Lebenserwartung angelegt werden. Wenn die Person Männlich ist, steht in der Spalte der Wert 79 (die derzeitige statistische Lebenserwartung für Männer in Deutschland), wenn die Person Weiblich ist, wird in die Spalte der Wert 84 eingetragen. Wir haben es in diesem Szenario also mit 2 Bedingungen zu tun:

  1. Wenn der Wert ‚Männlich‘ ist, dann schreibe 79.
  2. Wenn der Wert ‚Weiblich‘ ist, dann schreibe 84.

Für das Anlegen der Spalte Lebenserwartung wird also lediglich die Information über das Geschlecht der Personen benötigt.

Möglichkeit 1: Klassenmethode where aus numpy

Die Klassenmethode where folgt einer intuitiven Logik nach dem Schema where(Test, True, False). Das 2 Argument enthält dementsprechend eine Expression mit der Anweisung darüber, was mit einem positiven Testfall geschehen soll. Das 3 Argument enthält eine Anweisung für den negativen Testfall. Gegenüber den anderen Methoden ist die Umsetzung einer bedingten Berechnung mit where die performanteste.

df['Lebenserwartung'] = np.where(df['Geschlecht'] == 'M', 79, 84)

Möglichkeit 2: List Comprehension über Series mit Conditional Expression

Hier wird eine List-Comprehension verwendet und über die entsprechende Series iteriert. Eine if-else-Bedingung als sog. Conditional Expression sorgt für die Unterscheidung zwischen den Geschlechtern. [Hier erfahren Sie mehr über Listen und Conditional Expressions]

df['Lebenserwartung'] = [79 if (i == "M") else 84 for i in df['Geschlecht']]

Möglichkeit 3: Mapper + List Comprehension über Series

Hier wird ein Dictionary erstellt, welches ein Mapping von Bedingung zu Expression enthält. Dieses wird anschließend in der List-Comprehension eingesetzt.

mapper = {'M': 79,
          'W': 84}

df['Lebenserwartung'] = [mapper[i] for i in df['Geschlecht']]

Die Berechnung benötigt mehr als eine Spalte des DataFrames

Die oben beschriebenen Techniken werden leicht abgewandelt eingesetzt, wenn wir mehr als eine Spalte des DataFrames benötigen um die neue Information zu berechnen.

Angenommen wir wollen berechnen, wie viel Lebenszeit den Personen im statistischen Sinn auf dieser schönen Erde bleibt und dies als Variable an den Datensatz anfügen (verzeihen Sie mir bitte dieses zynische Beispiel). Für die Berechnung benötigen wir 2 Spalten aus dem DataFrame. Zum einen die Information des Geschlechts (hier ist die Bedingung enthalten) und zum anderen die Information über das Alter der Personen.

Möglichkeit 1: Klassenmethode where aus numpy

Die Methode where iteriert über den Integer-Zeilenindex. Dadurch kann in der Funktion auch auf andere Spalten zugegriffen werden. Syntaktisch unterscheidet sich die Anwendung nicht sonderlich von dem oben durchgeführten Beispiel.

df['Verbleibende Lebenserwartung'] = np.where(df['Geschlecht'] == 'M', 79-df['Alter'], 84-df['Alter'])

Möglichkeit 2: List Comprehension über DataFrame mit Conditional Expression

Die Interationen der List-Comprehensions müssen beim Zugriff auf verschiedene Spalten des DataFrame eingesetzt werden. Mit der Methode iterrows() kann zeilenweise über den gesamten DataFrame iteriert werden.

df['Verbleibende Lebenserwartung'] = [79 - row['Alter'] if (row['Geschlecht'] == 'M') else 84 - row['Alter'] for index, row in df.iterrows()]

Möglichkeit 3: Mapper + List Comprehension über DataFrame

Auch hier wird über den gesamten DataFrame iteriert. Das Mapping Objekt enthält außerdem die Berechnungsanweisungen als lambda-Funktion.

mapper = {'M': lambda x: 79 - x,
          'W': lambda x: 84 - x}

df['Verbleibende Lebenserwartung'] = [mapper[row['Geschlecht']](row['Alter']) for index, row in df.iterrows()]

Es existieren > 2 Bedingungen

Existieren mehr als 2 Bedingungen, sollte sich aus diesen beiden Herangehensweisen für eine entschieden werden:

  1. Vekettete Klassenmethode where aus numpy [oder]
  2. Anlegen eines Mappers + List Comprehension

Angenommen wir wollen das Nettoeinkommen der Personen in ihren jeweiligen Ländern berechnen. Den fiktiven Steuersatz eines Landes halten wir in der Form (1-Steuersatz) fest und multiplizieren diesen Wert mit dem Gehalt.

Möglichkeit 1: Verkettete Klassenmethode where aus numpy

Wie wir sehen existiert dieser verschachtelte Lösungsansatz – auch wenn er aufgrund der schlechten Lesbarkeit nicht zu empfehlen ist. Ich führe ihn an dieser Stelle explizit auf, weil insbesondere Python-Einsteiger ihn verwenden – tun Sie es aber bitte nicht!

df['Nettogehalt'] = np.where(df['Nationalität'] == 'deutsch', 
                     df['Gehalt'] * 0.62, 
                     np.where(df['Nationalität'] == 'schweizerisch', 
                              df['Gehalt'] * 0.7,
                             np.where(df['Nationalität'] == 'italienisch', 
                                      df['Gehalt'] * 0.58,
                                     np.where(df['Nationalität'] == 'polnisch', 
                                             df['Gehalt'] * 0.6,
                                             np.nan))))

Wenn unbedingt mit der Methode where gearbeitet werden soll, dann lieber auf die Art wie im folgenden Codeblock dargestellt. Hier ist der Code deutlich besser lesbar.

Zur Erläuterung: Im ersten Aufruf von np.where die Spalte Nettogehalt mit np.nan für eine beliebige unzutreffende Bedingung initialisiert. Anschließend wird bei jedem Aufruf von np.where df[‚Nettogehalt‘] verwendet, wenn die Bedingung nicht zutrifft.

df['Nettogehalt'] = np.where(df['Nationalität'] == 'deutsch', 
                             df['Gehalt'] * 0.62,
                             np.nan)
df['Nettogehalt'] = np.where(df['Nationalität'] == 'schweizerisch', 
                             df['Gehalt'] * 0.7,
                             df['Nettogehalt'])
df['Nettogehalt'] = np.where(df['Nationalität'] == 'italienisch', 
                             df['Gehalt'] * 0.58,
                             df['Nettogehalt'])
df['Nettogehalt'] = np.where(df['Nationalität'] == 'polnisch', 
                             df['Gehalt'] * 0.6,
                             df['Nettogehalt'])

Möglichkeit 3: Mapper + List Comprehension über DataFrame

Die Technik einen Mapper einzusetzen ist aufgrund des schlanken Quellcodes die empfehlenswerte.

mapper = {'deutsch': lambda x: x * 0.62,
          'schweizerisch': lambda x: x * 0.7,
          'italienisch': lambda x: x * 0.58,
          'polnisch': lambda x: x * 0.6}

[mapper[row['Nationalität']](row['Gehalt']) for index, row in df.iterrows()]
In [ ]: