import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import scipy.stats as stats
Verschiedenes: Mini-Beispiele & Weiterführendes
Dieses Kapitel ist eine stetig wachsende Sammlung von nützlichen Mini-Beispielen und Techniken. Jedes Beispiel ist in sich abgeschlossen und kann als Referenz für häufig auftretende Aufgaben dienen.
Schleifen mit enumerate()
Die enumerate()
-Funktion in Python erlaubt es, Code zu sparen und eleganter durch Sammlungen zu iterieren - gegeben, dass man sowohl Index, als auch Wert benötigt:
# Eine Liste mit Früchten
= ["Apfel", "Banane", "Kirsche", "Dattel"]
fruits
# Mit enumerate()
print("Beispiel mit enumerate():")
for i, fruit in enumerate(fruits):
print(f"Index {i}: {fruit}")
# Ohne enumerate()
print("\nAlternative ohne enumerate():")
for i in range(len(fruits)):
= fruits[i]
fruit print(f"Index {i}: {fruit}")
Beispiel mit enumerate():
Index 0: Apfel
Index 1: Banane
Index 2: Kirsche
Index 3: Dattel
Alternative ohne enumerate():
Index 0: Apfel
Index 1: Banane
Index 2: Kirsche
Index 3: Dattel
Die Version mit enumerate()
ist kürzer und lesbarer, da sie direkt den Index und den Wert in einem Schritt bereitstellt, ohne dass wir eine separate Variable für den Index verwalten müssen.
DataFrame-Erstellung mit Schleifenergebnissen
Ein häufiges Szenario in der Datenanalyse ist das schrittweise Aufbauen eines DataFrames, wenn Ergebnisse aus einer Schleife gesammelt werden müssen (z.B. bei der Berechnung von Statistiken für verschiedene Teilmengen der Daten). Hier sind drei verschiedene Ansätze mit ihren jeweiligen Vor- und Nachteilen:
# Beispieldaten
= ["Set1", "Set2", "Set3"]
datasets = [0.123, 0.456, 0.789]
r_values = [0.01, 0.05, 0.001] p_values
Ansatz 1: Zeilenweise anhängen mit pd.concat()
# Leerer DataFrame zum Start
= pd.DataFrame()
results1
for i in range(len(datasets)):
# Temporären DataFrame mit einer Zeile erstellen
= pd.DataFrame({
temp_df 'Dataset': [datasets[i]],
'Pearson r': [round(r_values[i], 4)],
'p-value': [round(p_values[i], 4)]
})
# An bestehenden DataFrame anhängen
= pd.concat([results1, temp_df], ignore_index=True)
results1
print(results1)
Dataset Pearson r p-value
0 Set1 0.123 0.010
1 Set2 0.456 0.050
2 Set3 0.789 0.001
Ohne ignore_index=True
würden die Indexwerte des temporären DataFrames beibehalten.Dies würde zu doppelten Indexwerten führen (da in jedem Schleifendurchlauf ein neuer DataFrame mit Index 0 erstellt wird). Mit ignore_index=True
wird stattdessen ein neuer fortlaufender Index generiert (0, 1, 2, …)-
Ansatz 2: DataFrame mit bekannter Größe vorab erstellen und befüllen
# DataFrame vorab mit der bekannten Anzahl an Zeilen erstellen
= pd.DataFrame(columns=['Dataset', 'Pearson r', 'p-value'], index=range(len(datasets)))
results2
# Befüllen der Zeilen
for i in range(len(datasets)):
= [datasets[i], round(r_values[i], 4), round(p_values[i], 4)]
results2.loc[i]
print(results2)
Dataset Pearson r p-value
0 Set1 0.123 0.01
1 Set2 0.456 0.05
2 Set3 0.789 0.001
Bei diesem Ansatz wird der DataFrame bereits zu Beginn mit der korrekten Anzahl an Zeilen erstellt. Im Gegensatz zum ersten Ansatz wird keine Verkettung durchgeführt, sondern bestehende Zeilen werden direkt an ihren Positionen befüllt.
Ansatz 3: Daten sammeln und am Ende einen DataFrame erstellen
# Liste für Datensätze vorbereiten
= []
data_list
for i in range(len(datasets)):
data_list.append({'Dataset': datasets[i],
'Pearson r': round(r_values[i], 4),
'p-value': round(p_values[i], 4)
})
# Nach der Schleife einen DataFrame erstellen
= pd.DataFrame(data_list)
results3 print(results3)
Dataset Pearson r p-value
0 Set1 0.123 0.010
1 Set2 0.456 0.050
2 Set3 0.789 0.001
Bewertung der Ansätze
Alle drei Methoden erzeugen das gleiche Ergebnis, unterscheiden sich aber in Bezug auf Performance und Anwendungsfall:
Ansatz 1 (concat) ist einfach zu verstehen, kann aber bei vielen Iterationen ineffizient sein, da bei jedem Schritt ein neuer DataFrame erstellt und mit dem existierenden verkettet wird, was bei großen Datenmengen zu Performanceproblemen führen kann.
Ansatz 2 (vorab erstellen) ist effizient, da die Größe vorab bekannt ist und kein wiederholtes Verketten nötig ist. Er setzt allerdings voraus, dass man die endgültige Größe des DataFrames schon vor der Schleife kennt.
Ansatz 3 (Sammeln und am Ende erstellen) ist in der Regel am effizientesten, besonders bei großen Datensätzen, da nur einmal ein DataFrame erstellt wird. Diese Methode ist besonders empfehlenswert, wenn die Anzahl der Einträge vorab nicht bekannt ist oder wenn die Daten komplex strukturiert sind.
Für die meisten Anwendungsfälle wird Ansatz 3 empfohlen, da er eine gute Balance zwischen Lesbarkeit und Performance bietet.
Lambda-Funktionen für Inline-Operationen
Lambda-Funktionen sind kleine, anonyme Funktionen, die sich direkt dort definieren lassen, wo sie benötigt werden. Sie sind besonders nützlich, wenn man eine einfache Operation nur einmalig braucht und keine vollständige Funktionsdefinition rechtfertigt.
Ein typisches Anwendungsgebiet sind groupby
-Operationen, bei denen verschiedene Aggregationsfunktionen unterschiedliche Nachbearbeitungen benötigen. Betrachten wir ein Experiment mit Ertragsdaten:
# Beispieldaten erstellen
= {
experiment_data 'duenger': ['Toad', 'Toad', 'Yoshi', 'Yoshi'] * 6,
'sorte': ['Simba', 'Nala', 'Timon', 'Pumba'] * 6,
'ertrag': [6192, 6269, 7470, 7862, 5522, 4504, 7260, 1594,
7146, 6872, 7578, 6324, 5970, 5126, 6392, 1690,
6860, 6444, 7642, 6666, 6550, 4218, 6410, 2856]
}
= pd.DataFrame(experiment_data) df_experiment
Ein Beispiel-Problem: Unterschiedliche Rundungsanforderungen
Wenn wir Statistiken pro Gruppe berechnen, möchten wir z.B. Mittelwert und Standardabweichung auf eine Nachkommastelle runden, die Anzahl der Beobachtungen aber als ganze Zahl belassen:
# Ansatz 1: Erst groupby, dann nachträglich runden (umständlich)
= df_experiment.groupby(['duenger'])['ertrag'].agg(['mean', 'std', 'count'])
stats_raw = stats_raw.copy()
stats_rounded 'mean'] = stats_raw['mean'].round(1)
stats_rounded['std'] = stats_raw['std'].round(1)
stats_rounded[# 'count' bleibt unverändert
stats_rounded
mean std count
duenger
Toad 5972.8 944.8 12
Yoshi 5812.0 2349.5 12
Lösungsansatz mit eigenen Funktionen
Wir könnten auch separate Funktionen definieren:
# Ansatz 2: Eigene Funktionen definieren (für diesen Fall übertrieben)
def mean_rounded(x):
return round(x.mean(), 1)
def std_rounded(x):
return round(x.std(), 1)
= (
stats_functions 'duenger'])['ertrag']
df_experiment.groupby([=mean_rounded, std_rounded=std_rounded, count='count')
.agg(mean_rounded
)
stats_functions
mean_rounded std_rounded count
duenger
Toad 5972.8 944.8 12
Yoshi 5812.0 2349.5 12
Die elegante Lösung: Lambda-Funktionen
Lambda-Funktionen erlauben es, die Funktion direkt an Ort und Stelle zu definieren:
# Ansatz 3: Lambda-Funktionen (elegant und kompakt)
= (
stats_lambda 'duenger'])['ertrag']
df_experiment.groupby([
.agg(=lambda x: round(x.mean(), 1),
mean=lambda x: round(x.std(), 1),
std='count'
count
)
)
stats_lambda
mean std count
duenger
Toad 5972.8 944.8 12
Yoshi 5812.0 2349.5 12
Syntax und Funktionsweise
Die Syntax einer Lambda-Funktion ist:
lambda parameter: operation
In unserem Beispiel bedeutet lambda x: round(x.mean(), 1)
:
-
x
ist der Parameter (die Gruppe von Werten) -
round(x.mean(), 1)
ist die Operation, die ausgeführt wird - Das Ergebnis wird automatisch zurückgegeben
Lambda-Funktionen sind besonders praktisch, wenn:
- Die Operation einfach ist
- Sie nur an einer Stelle verwendet wird
- Eine vollständige Funktionsdefinition übertrieben wäre
Für komplexere Operationen oder wiederverwendbare Logik sind reguläre Funktionen nach wie vor die bessere Wahl.
Filtern mit Masken (True/False-Arrays)
Beim Arbeiten mit NumPy-Arrays oder Pandas-DataFrames taucht oft der Begriff Maske auf. Eine Maske ist nichts anderes als ein Array von True/False-Werten, das festlegt, welche Elemente ausgewählt werden.
# Beispiel-Daten
= np.array([5, 12, 18, 7, 3, 25])
numbers
# Eine Maske erstellen: Welche Zahlen sind größer als 10?
= numbers > 10
mask print("Maske:", mask)
# Mit der Maske filtern
print("Gefilterte Werte:", numbers[mask])
# Dasselbe mit einem DataFrame
= pd.DataFrame({"Name": ["Anna", "Ben", "Clara", "David"],
df "Alter": [23, 35, 29, 18]})
= df["Alter"] >= 30
mask_df print("\nMaske für df:\n", mask_df)
print("\nGefilterte Zeilen:\n", df[mask_df])
Maske: [False True True False False True]
Gefilterte Werte: [12 18 25]
Maske für df:
0 False
1 True
2 False
3 False
Name: Alter, dtype: bool
Gefilterte Zeilen:
Name Alter
1 Ben 35
Erklärung:
- Die Maske ist ein Array der gleichen Länge wie die Daten (True/False)
- Beim Anwenden (
numbers[mask]
oderdf[mask_df]
) werden nur die Zeilen/Elemente behalten, bei denen die MaskeTrue
ist
Warum nützlich?
- Sehr flexibel: Jede Bedingung kann zur Maske werden (
==
,>
,<
, Kombinationen mit&
oder|
) - Lesbarer als komplizierte Schleifen
- Lässt sich auch in Plots einsetzen, um nur bestimmte Datenpunkte darzustellen
Masken im Plot
# Beispiel-Daten für einen Plot
= np.linspace(0, 10, 30)
x = np.sin(x)
y
# Maske: Werte oberhalb der x-Achse
= y >= 0
mask
# Plot: erst alle Punkte grau, dann gefilterte Punkte grün (darüber gezeichnet)
= plt.subplots(layout='tight')
fig, ax ="lightgray", label="alle Punkte")
ax.scatter(x, y, color="green", label="y >= 0")
ax.scatter(x[mask], y[mask], color0, color="black", linewidth=1)
ax.axhline(
ax.legend()"Masken im Scatter-Plot")
ax.set_title(
plt.show()
print("=== y ===")
print(y)
print("=== mask ===")
print(mask)
print("=== y[mask] ===")
print(y[mask])
=== y ===
[ 0. 0.33803442 0.6362712 0.85959818 0.98172251 0.9882662
0.87845883 0.6652283 0.37367879 0.03813513 -0.30189827 -0.60638843
-0.83948697 -0.9737506 -0.99337213 -0.89604148 -0.69321762 -0.40877952
-0.07621478 0.26532292 0.57562349 0.81815446 0.96436206 0.9970329
0.91232056 0.72019844 0.44328555 0.11418355 -0.22836157 -0.54402111]
=== mask ===
[ True True True True True True True True True True False False
False False False False False False False True True True True True
True True True True False False]
=== y[mask] ===
[0. 0.33803442 0.6362712 0.85959818 0.98172251 0.9882662
0.87845883 0.6652283 0.37367879 0.03813513 0.26532292 0.57562349
0.81815446 0.96436206 0.9970329 0.91232056 0.72019844 0.44328555
0.11418355]
In diesem Beispiel werden alle Punkte hellgrau dargestellt, die Punkte oberhalb der x-Achse (Maske y >= 0
) zusätzlich grün hervorgehoben.
So sieht man: Masken sind nicht nur zum Filtern von Tabellen da, sondern auch ein elegantes Werkzeug für bedingte Visualisierungen.