Skip to content

10. Datenanalyse mit NumPy und Pandas

Strukturierte Daten effizient verarbeiten, analysieren und transformieren.

Python hat sich zu einer der wichtigsten Sprachen für Datenanalyse und wissenschaftliches Arbeiten entwickelt. Einen wesentlichen Anteil daran haben NumPy und Pandas.

NumPy stellt leistungsfähige mehrdimensionale Arrays bereit und ermöglicht numerische Berechnungen mit hoher Performance. Viele wissenschaftliche Bibliotheken bauen direkt auf NumPy auf.

Pandas erweitert diese Möglichkeiten um DataFrames und komfortable Werkzeuge zur Analyse, Transformation und Bereinigung von Daten. Dadurch lassen sich auch große Datenbestände effizient verarbeiten.

In diesem Kapitel lernen Sie die wichtigsten Konzepte von NumPy und Pandas kennen und erfahren, wie typische Aufgaben der Datenanalyse umgesetzt werden.

Warum NumPy und Pandas?

Verstehen, welche Probleme diese Bibliotheken lösen und warum sie in der Datenanalyse so weit verbreitet sind.

Warum NumPy und Pandas?

In der Welt der Datenanalyse sind effiziente Datenstrukturen und Operationen auf großen Datenmengen essenziell. Python selbst bietet mit Listen, Dictionaries und anderen eingebauten Typen zwar eine solide Basis, doch stoßen diese bei numerisch intensiven oder tabellarischen Daten schnell an ihre Grenzen – sowohl in Bezug auf Performance als auch auf Ausdruckskraft.

Die Lücke, die NumPy schließt

NumPy ist das Fundament für numerische Berechnungen in Python. Es bietet:

  • Homogene, mehrdimensionale Arrays: Im Gegensatz zu Python-Listen, die heterogene Elemente erlauben und intern als verkettete Objekte verwaltet werden, sind NumPy-Arrays dicht gepackt und in C implementiert. Das erlaubt schnellen, speichereffizienten Zugriff und Operationen.

  • Vektor- und Matrixoperationen: Statt expliziter Schleifen wie in Java oder C++ können Sie mit NumPy ganze Arrays auf einmal bearbeiten. Das entspricht dem Paradigma der Vektorprogrammierung und nutzt optimierte, native Bibliotheken (BLAS, LAPACK).

  • Broadcasting: NumPy ermöglicht Operationen zwischen Arrays unterschiedlicher Form, ohne explizites Kopieren oder Schleifen. Das ist ein Paradigmenwechsel gegenüber typischen for-Schleifen in C# oder Java.

  • Nahtlose Integration in den Python-Ökosystem: Viele Bibliotheken für Machine Learning, Visualisierung und wissenschaftliches Rechnen bauen auf NumPy auf.

Ein kurzes Beispiel, das die Performance und Ausdrucksstärke illustriert:

import numpy as np

# Erzeugen eines großen Arrays von Zufallszahlen
data: np.ndarray = np.random.rand(1_000_000)

# Berechnung des Mittelwerts ohne explizite Schleife
mean_value: float = np.mean(data)
print(f"Mittelwert: {mean_value:.4f}")
Mittelwert: 0.5001

Warum Pandas?

Während NumPy für numerische Arrays optimiert ist, adressiert Pandas das Problem der tabellarischen Datenanalyse, wie sie in Datenbanken, Excel oder CSV-Dateien vorkommt. Pandas bietet:

  • Flexible Datenstrukturen: Series (1D) und DataFrame (2D) mit heterogenen Datentypen pro Spalte – ähnlich einer Tabelle oder einem DataTable in C#.

  • Beschriftetes Indexing: Anders als bei NumPy-Arrays können Zeilen und Spalten mit Labels statt nur numerischen Indizes adressiert werden. Das erleichtert den Umgang mit realen Daten.

  • Leistungsstarke Datenmanipulation: Gruppierungen, Pivot-Tabellen, fehlende Werte behandeln, Filtern und Aggregieren sind direkt und idiomatisch möglich.

  • Integration mit Ein-/Ausgabeformaten: CSV, Excel, SQL und viele weitere Formate können unkompliziert geladen und gespeichert werden.

Ein Beispiel, das den Umgang mit tabellarischen Daten zeigt:

sales.csv:

| product    | revenue |
| ---------- | ------: |
| Laptop     |    1200 |
| Smartphone |     800 |
| Laptop     |    1500 |
| Tablet     |     600 |
| Smartphone |     900 |
import pandas as pd

# CSV-Datei laden
sales_data: pd.DataFrame = pd.read_csv('sales.csv')

# Umsatz pro Produkt gruppieren
revenue_per_product: pd.Series = sales_data.groupby('product')['revenue'].sum()
print(revenue_per_product)
product
Laptop        2700
Smartphone    1700
Tablet         600
Name: revenue, dtype: int64

Python-Denkweise und Designphilosophie

Im Vergleich zu Java oder C# ist Python weniger strikt typisiert und setzt stärker auf Lesbarkeit und Ausdruckskraft. NumPy und Pandas folgen diesem Prinzip, indem sie komplexe Operationen mit minimalem und klar lesbarem Code ermöglichen. Statt expliziter Schleifen und Boilerplate-Code nutzen Sie vektorisierten Code und deklarative Datenmanipulation.

Das bedeutet auch, dass Sie als erfahrener Entwickler Ihre Denkweise anpassen sollten: Statt Schritt-für-Schritt-Iteration denken Sie in Operationen auf ganzen Datenstrukturen. Das führt zu kürzerem, wartbarem und performanterem Code.

Zusammenfassung

  • NumPy ist die Grundlage für effiziente numerische Berechnungen mit homogenen Arrays und Vektoroperationen.
  • Pandas baut darauf auf und bietet flexible, beschriftete Datenstrukturen für tabellarische Daten.
  • Beide Bibliotheken sind integraler Bestandteil des Python-Datenanalyse-Ökosystems und ermöglichen idiomatischen, performanten Code.
  • Der Paradigmenwechsel von expliziter Iteration zu vektorisierter und deklarativer Datenmanipulation ist zentral für den produktiven Einsatz.

Diese Bibliotheken sind deshalb nicht nur Werkzeuge, sondern auch Ausdruck der Python-Denkweise in der Datenanalyse.

NumPy Arrays

Mehrdimensionale Arrays erstellen und die Unterschiede zu Python- Listen verstehen. Shape, Dimensionen und Datentypen.

Einführung in NumPy Arrays

NumPy ist die fundamentale Bibliothek für numerische Berechnungen in Python. Im Kern steht dabei der ndarray (n-dimensionales Array), eine effiziente, homogene Datenstruktur für große Datenmengen. Im Gegensatz zu den Standard-Python-Listen, die heterogene und dynamisch wachsende Container sind, sind NumPy-Arrays typisiert und haben eine feste Größe. Diese Eigenschaften ermöglichen signifikante Performancegewinne bei numerischen Operationen.

Unterschiede zu Python-Listen

  • Homogenität: NumPy-Arrays speichern Elemente eines einzigen Datentyps, was Speicherplatz und Rechenzeit optimiert. Python-Listen können beliebige Objekte enthalten, was Flexibilität, aber auch Overhead bedeutet.
  • Speicherlayout: NumPy-Arrays verwenden kontinuierlichen Speicher (C-Style), was CPU-Caches besser ausnutzt. Listen sind Arrays von Zeigern auf Objekte.
  • Operationen: NumPy unterstützt vektorielle Operationen direkt auf Arrays, ohne explizite Schleifen. Python-Listen erfordern Schleifen oder List-Comprehensions.

Diese Unterschiede sind entscheidend, wenn es um Performance und Skalierbarkeit in datenintensiven Anwendungen geht.

Erstellen von Arrays

Arrays können aus Python-Listen oder Tupeln erzeugt werden. Dabei bestimmt NumPy automatisch den Datentyp, kann aber auch explizit gesetzt werden.

import numpy as np

# 1D-Array aus Liste
arr1d: np.ndarray = np.array([10, 20, 30, 40])

# 2D-Array aus Liste von Listen
arr2d: np.ndarray = np.array([[1, 2, 3], [4, 5, 6]])

# Expliziter Datentyp
arr_float: np.ndarray = np.array([1, 2, 3], dtype=np.float64)

print(arr1d)
print(arr2d)
print(arr_float.dtype)  # float64
[10 20 30 40]

[[1 2 3]
 [4 5 6]]

float64

Dimensionen und Shape

Jedes ndarray besitzt Attribute, die seine Struktur beschreiben:

  • ndarray.ndim: Anzahl der Dimensionen
  • ndarray.shape: Tuple mit der Länge jeder Dimension
  • ndarray.size: Gesamtanzahl der Elemente

Beispiel:

print(arr2d.ndim)   # 2
print(arr2d.shape)  # (2, 3)
print(arr2d.size)   # 6
2
(2, 3)
6

Diese Eigenschaften sind vergleichbar mit Arrays in C++ oder Java, aber NumPy erlaubt beliebige Dimensionen, nicht nur 1D oder 2D.

Datentypen (dtype)

NumPy verwendet dtype-Objekte, um den Typ der Array-Elemente zu definieren. Das ist vergleichbar mit primitiven Datentypen in Java oder C#, aber flexibler:

  • Unterstützt neben Standardtypen (int32, float64) auch komplexe Typen und benutzerdefinierte Strukturen
  • Ermöglicht explizite Kontrolle über Speicherlayout und Präzision

Ein häufiger Fehler beim Umstieg ist, anzunehmen, dass NumPy-Arrays dynamisch wachsen oder verschiedene Typen enthalten können. Stattdessen sind sie statisch und homogen, was eine andere Denkweise erfordert.

Praktische Hinweise

  • Verwenden Sie NumPy-Arrays immer für numerische Daten, da sie deutlich effizienter sind als Listen.
  • Für heterogene Daten oder gemischte Typen sind Pandas DataFrames geeigneter.
  • NumPy-Arrays sind unveränderlich in ihrer Größe; für dynamische Datenstrukturen sind andere Lösungen nötig.

Zusammenfassung

NumPy-Arrays sind die Grundlage für effiziente numerische Verarbeitung in Python. Sie unterscheiden sich in Homogenität, Speicherlayout und Operationen deutlich von Python-Listen. Das Verständnis von shape, ndim und dtype ist essenziell, um mit mehrdimensionalen Daten strukturiert und performant zu arbeiten.

Im nächsten Schritt lernen Sie, wie man mit diesen Arrays mathematische Operationen und Transformationen idiomatisch durchführt.

Operationen auf Arrays und Reshaping

Mathematische Berechnungen effizient auf ganze Arrays anwenden. Broadcasting, ufuncs und Reshaping.

Universelle Operationen mit ufuncs

NumPy bietet universelle Funktionen (ufuncs), die elementweise Operationen auf Arrays effizient ausführen.

import numpy as np

arr = np.array([1, 2, 3, 4], dtype=np.float64)
# Elementweise Addition
result = np.add(arr, 10)
print(result)  # [11. 12. 13. 14.]

# Elementweise Quadratwurzel
sqrt_arr = np.sqrt(arr)
print(sqrt_arr)  # [1. 1.41421356 1.73205081 2.]

Diese ufuncs sind nicht nur auf eindimensionale Arrays beschränkt, sondern funktionieren auch auf mehrdimensionalen Arrays, was komplexe Berechnungen in wenigen Zeilen ermöglicht.

Broadcasting: Eleganz statt Schleifen

Broadcasting ist ein Kernkonzept in NumPy, das es erlaubt, Operationen auf Arrays unterschiedlicher Form durchzuführen, ohne explizite Schleifen oder Kopien. Dabei wird ein kleineres Array so erweitert, dass es die Dimensionen des größeren Arrays virtuell annimmt.

In Sprachen wie Java oder C# muss man für solche Operationen oft manuell dimensionieren und Schleifen schreiben. Python mit NumPy abstrahiert das elegant und performant.

# Beispiel: Addition eines Skalars zu einem 2D-Array
matrix = np.array([[1, 2, 3], [4, 5, 6]])
scalar = 10
result = matrix + scalar  # scalar wird auf jedes Element angewandt
print(result)
# [[11 12 13]
#  [14 15 16]]

# Broadcasting mit Arrays unterschiedlicher Form
vector = np.array([1, 0, 1])
result2 = matrix + vector  # vector wird auf jede Zeile angewandt
print(result2)
# [[2 2 4]
#  [5 5 7]]

Broadcasting folgt klar definierten Regeln, die sicherstellen, dass Dimensionen kompatibel sind. Ein häufiger Fehler von Umsteigern ist, zu erwarten, dass jede Form automatisch kompatibel ist – hier hilft das Verständnis der Regeln.

Reshaping: Daten neu strukturieren ohne Kopien

Im Gegensatz zu Java-Arrays, die eine feste Dimension haben, ermöglicht NumPy das flexible Umformen (reshaping) von Arrays ohne Datenkopie. Dies ist besonders nützlich, wenn Daten für Algorithmen oder APIs in einer bestimmten Form vorliegen müssen.

arr = np.arange(12)  # 1D-Array mit 12 Elementen
reshaped = arr.reshape((3, 4))  # 3 Zeilen, 4 Spalten
print(reshaped)
# [[ 0  1  2  3]
#  [ 4  5  6  7]
#  [ 8  9 10 11]]

# Flatten zurück zu 1D
flattened = reshaped.ravel()
print(flattened)  # [ 0  1  2  3  4  5  6  7  8  9 10 11]

reshape erzeugt eine Ansicht auf die Originaldaten, sofern möglich, was Speicher spart. Methoden wie ravel() oder flatten() unterscheiden sich darin, ob sie eine Kopie erzeugen.

Praxis-Tipp: Vermeide unnötige Kopien

In datenintensiven Anwendungen ist es wichtig, Kopien zu vermeiden, um Speicher und Performance zu optimieren. Nutze reshape und view anstelle von copy, wenn möglich.

Zusammenfassung

  • ufuncs ermöglichen performante, elementweise Operationen ohne explizite Schleifen.
  • Broadcasting erlaubt Operationen auf unterschiedlich geformten Arrays durch intelligente Erweiterung.
  • Reshaping bietet flexible Umstrukturierung von Daten ohne Kopien.

Diese Konzepte sind zentrale Bausteine für effiziente numerische Berechnungen in Python und unterscheiden sich grundlegend von der typischen Array-Verarbeitung in Java oder C#.

Indexing und Slicing

Daten gezielt auswählen und Teilbereiche effizient bearbeiten.

Grundlagen von Indexing und Slicing in NumPy

NumPy-Arrays bieten im Vergleich zu Python-Listen leistungsfähigere und flexiblere Möglichkeiten, Daten gezielt auszuwählen und zu bearbeiten. Anders als in Java oder C#, wo Arrays meist statisch und eindimensional sind, sind NumPy-Arrays mehrdimensional und unterstützen komplexe Indexierungs- und Slicing-Operationen, die effizient auf C-Ebene ausgeführt werden.

Ein wichtiger Unterschied ist, dass NumPy-Slicing Views erzeugt, keine Kopien. Änderungen an einem Slice wirken sich direkt auf das Originalarray aus. Dieses Verhalten ist für Performance und Speicheroptimierung entscheidend, erfordert aber bewusstes Arbeiten, um unerwartete Seiteneffekte zu vermeiden.

Einfache Indexierung

Die einfachste Form ist die Auswahl eines einzelnen Elements über seine Indizes:

import numpy as np

arr: np.ndarray = np.array([[10, 20, 30], [40, 50, 60], [70, 80, 90]])

# Zugriff auf Element in Zeile 1, Spalte 2 (0-basiert)
element: int = arr[1, 2]  # 60

Im Vergleich zu Java oder C# entspricht dies dem Zugriff arr[1][2].

Slicing von Arrays

Slicing erlaubt das Extrahieren von Teilarrays mit der Syntax start:stop:step pro Dimension:

# Auswahl der ersten beiden Zeilen und aller Spalten
sub_arr: np.ndarray = arr[0:2, :]

# Jede zweite Spalte der letzten beiden Zeilen
sub_arr2: np.ndarray = arr[1:, ::2]

Wichtig ist, dass Slicing eine View zurückgibt, keine Kopie. Änderungen wirken sich auf das Original aus:

sub_arr[0, 0] = 999
print(arr[0, 0])  # Ausgabe: 999

Boolean Indexing

Neben numerischem Indexing unterstützt NumPy auch boolesche Arrays zur Auswahl von Elementen, ähnlich LINQ- oder Stream-Filter-Operationen in C# bzw. Java:

mask: np.ndarray = arr > 50
filtered: np.ndarray = arr[mask]  # 1D-Array mit allen Werten > 50

Dies ist eine sehr mächtige Methode, um Daten selektiv zu extrahieren, ohne explizite Schleifen.

Fancy Indexing

Fancy Indexing erlaubt die Auswahl beliebiger Elemente mit Index-Arrays:

rows: np.ndarray = np.array([0, 2])
cols: np.ndarray = np.array([1, 0])
selected: np.ndarray = arr[rows, cols]  # array([20, 70])

Zusammenfassung

  • NumPy-Indexing unterstützt Mehrfachindizes, Slicing mit start:stop:step, Boolean und Fancy Indexing.
  • Slicing erzeugt Views, keine Kopien – wichtig für Performance und Seiteneffekte.

Aggregation mit NumPy

Statistische Kennzahlen und Zusammenfassungen berechnen.

Grundlegende Aggregationsfunktionen

NumPy bietet eine Vielzahl von effizienten, vektorisierten Aggregationsfunktionen, die auf Arrays angewendet werden können. Diese Funktionen sind in C implementiert und daher deutlich performanter als manuelle Schleifen, wie man sie aus Java oder C# kennt. Zu den wichtigsten zählen:

  • np.sum() – Summe aller Elemente
  • np.mean() – Durchschnitt
  • np.median() – Median
  • np.min() / np.max() – Minimum und Maximum
  • np.std() / np.var() – Standardabweichung und Varianz

Diese Funktionen akzeptieren optional den Parameter axis, um Aggregationen entlang bestimmter Achsen eines mehrdimensionalen Arrays durchzuführen.

import numpy as np

# Beispiel: Umsätze eines Webshops pro Tag und Produktkategorie
sales = np.array([[120, 150, 100], [130, 160, 90], [110, 140, 95]])  # shape (3, 3)

# Gesamtsumme aller Umsätze
total_sales: float = np.sum(sales)

# Durchschnittlicher Umsatz pro Tag (axis=1)
daily_avg: np.ndarray = np.mean(sales, axis=1)

# Maximaler Umsatz je Kategorie (axis=0)
max_per_category: np.ndarray = np.max(sales, axis=0)

print(f"Total Sales: {total_sales}")
print(f"Daily Average: {daily_avg}")
print(f"Max per Category: {max_per_category}")
Total Sales: 1095
Daily Average: [123.33333333 126.66666667 115.        ]
Max per Category: [130 160 100]

Axis-Parameter: Schlüssel zum effizienten Arbeiten

Der axis-Parameter ist essenziell, um Aggregationen gezielt auf Zeilen, Spalten oder andere Dimensionen anzuwenden. Anders als in Java oder C#, wo man oft explizit über Indizes iteriert, erlaubt NumPy so eine deklarative und performante Ausdrucksweise.

  • axis=0: Aggregation entlang der ersten Dimension (Spalten bei 2D)
  • axis=1: Aggregation entlang der zweiten Dimension (Zeilen bei 2D)

Ein häufiger Fehler ist, axis zu verwechseln oder wegzulassen, was zu unerwarteten Ergebnissen führt.

Beispiel: Umsatzanalyse mit Axis

# Summe je Produktkategorie (über alle Tage)
sum_per_category = np.sum(sales, axis=0)

# Summe je Tag (über alle Kategorien)
sum_per_day = np.sum(sales, axis=1)

print(f"Sum per Category: {sum_per_category}")
print(f"Sum per Day: {sum_per_day}")
Sum per Category: [360 450 285]
Sum per Day: [370 380 345]

Mehrdimensionale Aggregationen

Für Arrays mit mehr als zwei Dimensionen funktioniert das Prinzip analog. So kann man z.B. in einem 3D-Array (z.B. [Jahr, Monat, Tag]) Aggregationen entlang beliebiger Achsen durchführen.

Aggregation mit Masken und Bedingungen

NumPy erlaubt auch Kombinationen mit booleschen Masken, um z.B. nur bestimmte Werte zu aggregieren – ein Muster, das in Java oder C# mit Schleifen und if-Bedingungen realisiert wird.

# Nur Umsätze über 120 summieren
mask = sales > 120
sum_high_sales = np.sum(sales[mask])
print(f"Sum of sales > 120: {sum_high_sales}")

Ausgabe:

Sum of sales > 120: 580

Best Practice: Vermeide explizite Schleifen

Nutze immer die eingebauten Aggregationsfunktionen von NumPy statt eigener Schleifen. Das ist nicht nur performanter, sondern entspricht auch der Python-Philosophie, Probleme auf höherer Abstraktionsebene zu lösen.

Zusammenfassung

  • NumPy bietet effiziente, vektorbasierte Aggregationsfunktionen
  • axis steuert die Dimension der Aggregation und ist zentral für korrekte Ergebnisse
  • Masken ermöglichen bedingte Aggregationen
  • Idiomatischer Python-Code vermeidet explizite Schleifen zugunsten vektorisierter Funktionen

Diese Fähigkeiten bilden die Grundlage für komplexere Datenanalysen mit Pandas und darüber hinaus.

Pandas Series und DataFrames

Einführung in Series

Die Series ist die einfachste Datenstruktur von Pandas und ähnelt einem eindimensionalen Array mit Achsenbeschriftung (Index). Im Gegensatz zu einem reinen NumPy-Array bringt eine Series eine explizite Indexierung mit, die nicht nur numerisch sein kann, sondern auch Strings oder andere unveränderliche Typen erlaubt. Dies ermöglicht eine semantisch reichere Datenhaltung, ähnlich wie ein assoziatives Array oder ein Dictionary, aber mit den Vorteilen eines homogenen, performant verarbeiteten Arrays.

import pandas as pd

# Series mit numerischem Index
s1: pd.Series[int] = pd.Series([10, 20, 30, 40])

# Series mit benutzerdefiniertem Index
s2: pd.Series[str] = pd.Series(["apple", "banana", "cherry"], index=["a", "b", "c"])

print(s2["b"])  # Zugriff über Indexlabel
banana

Im Vergleich zu Java oder C# ist die Series kein reines Array, sondern eher eine Kombination aus Array und Map. Die Indexlabels sind nicht nur zur Laufzeit verfügbar, sondern integraler Bestandteil der Datenstruktur, was viele Operationen eleganter und sicherer macht.

DataFrames: Tabellarische Daten mit Spalten

Ein DataFrame ist eine zweidimensionale, tabellarische Datenstruktur, die aus mehreren Series mit demselben Index besteht. Man kann sich einen DataFrame als eine Tabelle vorstellen, bei der jede Spalte eine Series mit einem eigenen Datentyp ist.

# Erstellen eines DataFrames aus einem Dictionary
data: dict[str, list] = {
    "name": ["Alice", "Bob", "Charlie"],
    "age": [25, 32, 37],
    "city": ["Berlin", "München", "Hamburg"]
}
df: pd.DataFrame = pd.DataFrame(data)

print(df)
      name  age      city
0    Alice   25    Berlin
1      Bob   32   München
2  Charlie   37   Hamburg

Index und Spalten: Flexibilität und Bedeutung

Der Index eines DataFrames ist vergleichbar mit dem Primärschlüssel in einer Datenbanktabelle, er definiert die Zeilenlabels. Standardmäßig ist er numerisch, kann aber beliebige Werte annehmen, auch mehrstufige Indizes (MultiIndex) sind möglich. Die Spalten sind benannte Series und können unterschiedliche Datentypen enthalten, was eine flexible und typensichere Datenhaltung ermöglicht.

Zugriff und Manipulation

Der Zugriff auf Daten erfolgt über .loc (label-basiert) und .iloc (positionsbasiert). Dies unterscheidet sich von Java oder C#, wo typischerweise nur positionsbasierte Indizes verwendet werden.

# Zugriff auf Zeile mit Label 1
row: pd.Series = df.loc[1]

# Zugriff auf erste Zeile (Position 0)
row_pos: pd.Series = df.iloc[0]

# Zugriff auf einzelne Zelle
age_of_bob: int = df.loc[1, "age"]

# Neue Spalte hinzufügen
df["age_plus_10"] = df["age"] + 10

Warum Pandas so mächtig ist

Pandas abstrahiert viele komplexe Operationen auf tabellarischen Daten. Die Kombination aus Label-Indexierung, heterogenen Datentypen und nahtloser Integration mit NumPy macht Pandas zum bevorzugten Werkzeug für Datenanalyse in Python.

Best Practices

  • Verwenden Sie .loc für label-basierten Zugriff und .iloc für positionellen Zugriff, um Verwechslungen zu vermeiden.
  • Nutzen Sie die Möglichkeit, benutzerdefinierte Indizes zu definieren, um Daten semantisch sinnvoll zu strukturieren.
  • Vermeiden Sie das manuelle Iterieren über DataFrames; Pandas-Methoden sind vektorisierte und deshalb performant.

Zusammenfassung

  • Series ist eine eindimensionale, indexierte Datenstruktur, die Array- und Dictionary-ähnliche Eigenschaften kombiniert.
  • DataFrame ist eine zweidimensionale, tabellarische Datenstruktur aus mehreren Series mit gemeinsamen Index.
  • Der idiomatische Umgang mit Pandas setzt auf vektorisierten, label-basierten Zugriff und vermeidet explizite Schleifen.

Daten importieren und exportieren

Daten aus CSV-Dateien, Excel-Dateien und anderen Quellen laden. Analyseergebnisse in verschiedene Formate schreiben.

CSV-Dateien lesen und schreiben

CSV (Comma-Separated Values) ist das universellste Datenformat für tabellarische Daten. Pandas bietet mit read_csv und to_csv einfache und leistungsfähige Funktionen, um CSV-Dateien zu laden und zu speichern.

import pandas as pd

# CSV-Datei einlesen
df: pd.DataFrame = pd.read_csv('data/sales_data.csv', delimiter=',', encoding='utf-8')

# CSV-Datei schreiben
# index=False vermeidet das Speichern des DataFrame-Index als Spalte
df.to_csv('output/sales_export.csv', index=False, sep=';')

Warum nicht einfach Python's built-in csv-Modul?

  • Pandas übernimmt automatisch Typkonvertierungen.
  • Komfortable Handhabung fehlender Werte.
  • Direkte Integration in DataFrame-Operationen.

Excel-Dateien verarbeiten

Excel ist in Unternehmen weit verbreitet. Pandas nutzt openpyxl oder xlrd als Backend, um Excel-Dateien zu lesen und zu schreiben.

# Excel-Datei lesen
excel_df: pd.DataFrame = pd.read_excel('data/financial_report.xlsx', sheet_name='Q1')

# Excel-Datei schreiben
excel_df.to_excel('output/financial_report_export.xlsx', sheet_name='Q1', index=False)

Hinweis: - Excel-Dateien können mehrere Tabellenblätter enthalten, daher ist sheet_name wichtig. - Für große Excel-Dateien ist das Einlesen langsamer als bei CSV.

Weitere Datenquellen

Neben CSV und Excel unterstützt Pandas viele Formate: - JSON (read_json, to_json) - SQL-Datenbanken (read_sql_query, to_sql) - Parquet, HDF5, Feather für effiziente Speicherformate

Diese Formate sind besonders in Big-Data- oder datenbanknahen Anwendungen relevant.

Umgang mit Encoding und Fehlern

Encoding-Probleme sind häufige Stolperfallen beim Datenimport. Standard ist UTF-8, aber viele Dateien nutzen andere Encodings (z.B. Latin-1).

# Beispiel: Latin-1 Encoding
df = pd.read_csv('data/legacy_data.csv', encoding='latin1')

Fehlerhafte Zeilen können mit error_bad_lines=False (in älteren Pandas-Versionen) oder on_bad_lines='skip' übersprungen werden.

Performance-Tipps beim Einlesen

  • Für sehr große Dateien kann chunksize helfen, Daten in Portionen zu laden.
  • dtype-Angaben verbessern die Ladegeschwindigkeit und verhindern unerwünschte Typkonvertierungen.
chunk_iter = pd.read_csv('data/large_file.csv', chunksize=100000, dtype={'id': int, 'value': float})
for chunk in chunk_iter:
    process(chunk)  # Beispielhafte Verarbeitung

Zusammenfassung

  • pd.read_csv und df.to_csv sind die Standardwerkzeuge für CSV.
  • Excel-Dateien werden mit read_excel und to_excel behandelt, inklusive Mehrblatt-Unterstützung.
  • Encoding und Fehlermanagement sind wichtige Aspekte beim Datenimport.
  • Für große Datenmengen empfiehlt sich das chunkweise Einlesen.
  • Pandas unterstützt viele weitere Formate, was den Datenzugriff flexibel macht.

Diese Funktionen sind essenziell, um Daten aus realen Quellen effizient in Python-Workflows zu integrieren.

Daten explorieren

Struktur, Qualität und Eigenschaften von Datensätzen analysieren. Zeilen und Spalten gezielt auswählen und bearbeiten.

Überblick über Datensatzstrukturen

In der Datenanalyse mit Pandas ist das Verständnis der grundlegenden Datenstrukturen essenziell. Die beiden Haupttypen sind Series (eindimensionale, beschriftete Arrays) und DataFrame (zweidimensionale, tabellarische Datenstrukturen). Ein DataFrame ähnelt einer Tabelle mit Zeilen und Spalten, wobei jede Spalte eine Series ist. Anders als in Java oder C# sind diese Strukturen dynamisch typisiert und unterstützen heterogene Datentypen pro Spalte.

Erste Exploration: Kopf- und Fußzeilen

Um schnell einen Eindruck vom Datensatz zu bekommen, sind die Methoden .head() und .tail() hilfreich. Sie zeigen standardmäßig die ersten bzw. letzten fünf Zeilen an:

import pandas as pd

df = pd.read_csv('data.csv')
print(df.head())  # Erste 5 Zeilen
print(df.tail(3))  # Letzte 3 Zeilen

Diese Methoden sind in Python idiomatisch, da sie ohne explizite Indizes arbeiten und so die Daten schnell greifbar machen.

Struktur und Typen prüfen

Die Methode .info() liefert eine kompakte Übersicht über die Spalten, deren Datentypen und fehlende Werte:

print(df.info())

Im Gegensatz zu statisch typisierten Sprachen wie Java oder C# ist hier die Typisierung flexibel, was die Datenanalyse erleichtert, aber auch zur Laufzeit Fehler verursachen kann, wenn Datentypen unerwartet variieren.

Statistische Kennzahlen mit .describe()

Für numerische Spalten bietet .describe() eine schnelle Zusammenfassung wichtiger statistischer Kennzahlen wie Mittelwert, Standardabweichung, Min/Max und Quartile:

print(df.describe())

Das ist ein idiomatischer Weg, um sich ein erstes Bild von Verteilung und Streuung zu machen, ohne explizite Schleifen oder Aggregationen schreiben zu müssen.

Selektives Auswählen von Spalten und Zeilen

Pandas bietet mehrere Möglichkeiten, Daten gezielt auszuwählen:

  • Spaltenzugriff:
names = df['Name']  # Series mit Namen
subset = df[['Name', 'Age']]  # DataFrame mit zwei Spalten
  • Zeilenauswahl mit .loc und .iloc:
first_row = df.iloc[0]  # erste Zeile per Position
row_by_label = df.loc[42]  # Zeile mit Indexlabel 42

Pandas unterstützt sowohl integerbasierte Positionen (iloc) als auch labelbasierte Indizes (loc), was flexiblere und ausdrucksstärkere Datenzugriffe erlaubt.

Filtern von Daten

Filterausdrücke sind in Pandas sehr natürlich und nutzen boolesche Masken:

adults = df[df['Age'] >= 18]  # Alle Erwachsenen

# Mehrere Bedingungen
adults_female = df[(df['Age'] >= 18) & (df['Gender'] == 'Female')]

Dieser Stil unterscheidet sich von typischen Java-Streams oder LINQ-Abfragen, da er direkt auf DataFrames arbeitet und keine expliziten Iteratoren oder Lambdas benötigt.

Umgang mit fehlenden Werten

Fehlende Werte sind in Pandas als NaN (Not a Number) gekennzeichnet. Sie lassen sich mit .isna() erkennen:

missing_ages = df['Age'].isna()
print(df[missing_ages])

Das Erkennen und Behandeln fehlender Werte ist ein zentraler Schritt in der Datenexploration, der später im Kapitel vertieft wird.

Zusammenfassung

Die Explorationsphase ist in Python stark auf deklarative und ausdrucksstarke Methoden fokussiert, die sich von der eher imperativen Herangehensweise in Java oder C# unterscheiden. Die Kombination aus flexiblen Datenstrukturen, intuitiven Selektionsmechanismen und schnellen Übersichtsfunktionen macht Pandas zum bevorzugten Werkzeug für den Einstieg in die Datenanalyse.

Aggregation und Sortieren mit Pandas

Zusammenfassungen, Statistiken und Kennzahlen berechnen. Daten strukturieren und gruppenbezogene Auswertungen durchführen.

Aggregationen mit Pandas

Pandas bietet leistungsfähige Methoden zur Berechnung von Zusammenfassungen und statistischen Kennzahlen, die in der Datenanalyse unverzichtbar sind. Anders als in Java oder C#, wo man oft explizit Schleifen oder Streams verwendet, sind Pandas-Methoden auf DataFrames und Series vektorbasiert und optimiert, was zu klarerem und performanterem Code führt.

Die häufigsten Aggregationsfunktionen sind:

  • sum(), mean(), median(), std(), var() für grundlegende Statistik
  • min(), max() für Extremwerte
  • count() für die Anzahl nicht fehlender Werte

Diese Methoden können direkt auf DataFrames oder Series angewendet werden und liefern standardmäßig aggregierte Werte für jede Spalte:

import pandas as pd

data = pd.DataFrame({
    'sales': [250, 150, 400, 300],
    'expenses': [200, 180, 300, 250],
    'region': ['North', 'South', 'North', 'South']
})

# Durchschnittlicher Umsatz pro Spalte
print(data.mean(numeric_only=True))
sales       275.0
expenses    232.5
dtype: float64

Gruppierung und gruppenbezogene Aggregation

Die Methode groupby() ist ein zentrales Werkzeug, um Daten nach Kategorien zu segmentieren und gruppenweise Auswertungen durchzuführen. Dies entspricht in etwa dem groupBy-Konzept aus Java Streams oder LINQ in C#, ist aber in Pandas durch die enge Integration mit DataFrames noch intuitiver.

# Durchschnittlicher Umsatz und Kosten je Region
grouped = data.groupby('region').mean()
print(grouped)
        sales  expenses
region                 
North   325.0     250.0
South   225.0     215.0

groupby() erzeugt ein GroupBy-Objekt, das wie ein Iterator über Gruppen funktioniert. Die Aggregationsmethoden werden dann auf jede Gruppe angewandt.

Mehrere Aggregationen gleichzeitig

Pandas erlaubt es, mit agg() mehrere Aggregationen auf einmal auszuführen, was den Code kompakt und übersichtlich hält:

# Mehrere Kennzahlen pro Gruppe
result = data.groupby('region').agg({
    'sales': ['mean', 'sum'],
    'expenses': ['min', 'max']
})
print(result)
         mean  sum      min  max
region                          
North   325.0  650      200  300
South   225.0  450      180  250

Sortieren von Daten

Das Sortieren von DataFrames erfolgt über sort_values() für Spaltenwerte oder sort_index() für den Index. Im Vergleich zu Java oder C# ist die Syntax sehr prägnant und unterstützt komplexe Sortierkriterien mit mehreren Spalten und unterschiedlichen Sortierreihenfolgen:

# Nach Umsatz absteigend sortieren
sorted_data = data.sort_values(by='sales', ascending=False)
print(sorted_data)
   sales  expenses region
2    400       300  North
3    300       250  South
0    250       200  North
1    150       180  South
# Nach Region aufsteigend, dann nach Ausgaben absteigend
sorted_multi = data.sort_values(by=['region', 'expenses'], ascending=[True, False])
print(sorted_multi)
   sales  expenses region
2    400       300  North
0    250       200  North
3    300       250  South
1    150       180  South

Best Practices

  • Verwenden Sie groupby() frühzeitig, um Daten logisch zu segmentieren, bevor Sie Aggregationen ausführen.
  • Nutzen Sie agg() für mehrere Aggregationen, um redundanten Code zu vermeiden.
  • Sortieren Sie Daten nach Aggregationen, um die Ergebnisse besser interpretieren zu können.
  • Achten Sie auf fehlende Werte, da diese Aggregationen beeinflussen können; Methoden wie dropna() oder fillna() helfen hier.
sorted_multi.plot(kind="bar")
plt.show()

Sales vs Expenses

Fehlende Werte behandeln und Daten bereinigen

Unvollständige Daten erkennen und geeignete Strategien anwenden. Inkonsistente Daten erkennen und bereinigen.

Fehlende Werte erkennen

In der Datenanalyse sind fehlende Werte (Missing Values) ein allgegenwärtiges Problem. Anders als in statisch typisierten Sprachen wie Java oder C#, wo fehlende Werte oft durch Nullreferenzen oder spezielle Wrapper-Klassen wie Optional behandelt werden, verwendet Pandas das Konzept von NaN (Not a Number) für numerische Daten und None oder NaT für andere Datentypen. Dies ermöglicht eine einheitliche Handhabung fehlender Daten in DataFrames und Series.

Um fehlende Werte zu identifizieren, bietet Pandas die Methoden isna() bzw. isnull() (alias) an, die für jedes Element einen booleschen Wert zurückgeben:

import pandas as pd

data = pd.DataFrame({
    'user_id': [101, 102, 103, 104],
    'email': ['alice@example.com', None, 'charlie@example.com', ''],
    'age': [25, None, 30, 22]
})

missing_mask = data.isna()
print(missing_mask)
import pandas as pd
   user_id  email    age
0    False  False  False
1    False   True   True
2    False  False  False
3    False  False  False

Die Ausgabe zeigt, wo Werte fehlen. Wichtig ist, dass leere Strings ('') nicht als fehlend gelten, was häufig zu Verwirrung führt. Dies ist ein bewusster Unterschied zu vielen statisch typisierten Sprachen, wo null und leere Strings oft vermischt werden.

Fehlende Werte behandeln

Es gibt verschiedene Strategien, fehlende Werte zu behandeln, abhängig vom Kontext und der Analyse:

  • Entfernen: Mit dropna() können Zeilen oder Spalten mit fehlenden Werten entfernt werden. Dies ist oft die einfachste Lösung, aber nicht immer sinnvoll, wenn viele Daten fehlen.

  • Ersetzen (Imputation): Fehlende Werte können durch sinnvolle Ersatzwerte gefüllt werden, z.B. Mittelwerte, Median, Modus oder spezifische Konstanten. Pandas bietet fillna() dafür an.

  • Spezielle Behandlung: Für Zeitreihen oder kategorische Daten können auch Vorwärts- oder Rückwärtsfüllung (ffill, bfill) sinnvoll sein.

Beispiel für Imputation:

# Alter fehlende Werte mit dem Median ersetzen
median_age = data['age'].median()
data['age_filled'] = data['age'].fillna(median_age)

Inkonsistente Daten erkennen und bereinigen

Neben fehlenden Werten gibt es oft inkonsistente oder fehlerhafte Daten, z.B. unterschiedliche Schreibweisen, falsche Datentypen oder unplausible Werte. Im Gegensatz zu statisch typisierten Sprachen, wo Typfehler oft früh erkannt werden, müssen in Python und insbesondere bei Datenanalyse diese Probleme explizit geprüft werden.

Beispiel: Leere Strings als fehlende Werte interpretieren

# Leere Strings als fehlende Werte markieren
import numpy as np

data['email_clean'] = data['email'].replace('', np.nan)

Anschließend kann man erneut isna() verwenden, um diese Fälle zu erfassen.

Best Practices

  • Explizite Behandlung: Verlasse dich nicht auf implizite Konvertierungen oder Annahmen. Explizites Erkennen und Behandeln fehlender Werte erhöht die Wartbarkeit.

  • Konsistenz sicherstellen: Vereinheitliche fehlende Werte (z.B. None, NaN, leere Strings) vor der Analyse.

  • Dokumentation der Strategie: Halte fest, wie fehlende Werte behandelt wurden, um Reproduzierbarkeit und Nachvollziehbarkeit sicherzustellen.

  • Vorsicht bei Aggregationen: Viele Pandas-Aggregationsfunktionen ignorieren NaN automatisch, was zu unerwarteten Ergebnissen führen kann, wenn fehlende Werte nicht bewusst behandelt wurden.

Zusammenfassung

Das Handling fehlender und inkonsistenter Daten ist ein zentraler Schritt in der Datenanalyse. Pandas bietet dafür flexible und leistungsfähige Werkzeuge, die sich in ihrer Philosophie deutlich von den typischen Null-Referenz-Mechanismen in Java oder C# unterscheiden. Die Python-typische Offenheit und Dynamik erfordern jedoch eine bewusste und explizite Behandlung, um saubere und aussagekräftige Analysen zu gewährleisten.

Praxisprojekt: Analyse des Palmer-Penguins-Datensatzes

Einführung in das Praxisprojekt

Im abschließenden Praxisprojekt wenden wir NumPy und Pandas gemeinsam an, um den Palmer-Penguins-Datensatz zu analysieren. Dieser Datensatz ist ein beliebtes Beispiel für multivariate Datenanalyse und bietet eine gute Gelegenheit, typische Datenanalyse-Aufgaben zu durchlaufen: Datenimport, Exploration, Bereinigung, Aggregation und Visualisierung.

Die Wahl des Palmer-Penguins-Datensatzes statt klassischer Beispiele wie Iris unterstreicht den Fokus auf realistischere, komplexere Datenstrukturen. Anders als in stark typisierten Sprachen wie Java oder C#, wo Daten oft strikt modelliert werden, erlaubt Python mit Pandas eine flexible, explorative Herangehensweise.

Datenimport und erste Exploration

Wir starten mit dem Laden der Daten. Pandas bietet dafür eine einfache Schnittstelle, die CSV-Dateien direkt in DataFrames lädt. Die Daten liegen in tabellarischer Form vor, was sich gut mit Pandas abbilden lässt.

import pandas as pd
import numpy as np

# Datensatz laden
url = "https://raw.githubusercontent.com/allisonhorst/palmerpenguins/master/inst/extdata/penguins.csv"
penguins: pd.DataFrame = pd.read_csv(url)

# Überblick über die Daten
print(penguins.info())
print(penguins.head(3))
<class 'pandas.DataFrame'>
RangeIndex: 344 entries, 0 to 343
Data columns (total 8 columns):
 #   Column             Non-Null Count  Dtype  
---  ------             --------------  -----  
 0   species            344 non-null    str    
 1   island             344 non-null    str    
 2   bill_length_mm     342 non-null    float64
 3   bill_depth_mm      342 non-null    float64
 4   flipper_length_mm  342 non-null    float64
 5   body_mass_g        342 non-null    float64
 6   sex                333 non-null    str    
 7   year               344 non-null    int64  
dtypes: float64(4), int64(1), str(3)
memory usage: 27.6 KB
  species     island  bill_length_mm  bill_depth_mm  flipper_length_mm  body_mass_g     sex  year
0  Adelie  Torgersen            39.1           18.7              181.0       3750.0    male  2007
1  Adelie  Torgersen            39.5           17.4              186.0       3800.0  female  2007
2  Adelie  Torgersen            40.3           18.0              195.0       3250.0  female  2007

Die Methode info() gibt Auskunft über Datentypen und fehlende Werte – ein wichtiger erster Schritt, um Datenqualität einzuschätzen. Die head()-Methode zeigt die ersten Zeilen, was die Struktur und die enthaltenen Variablen schnell erfassbar macht.

Umgang mit fehlenden Werten

Im Gegensatz zu vielen statisch typisierten Sprachen, wo null-Werte oft problematisch sind, bietet Pandas flexible Methoden, um fehlende Werte zu erkennen und zu behandeln.

# Fehlende Werte zählen
missing_counts = penguins.isnull().sum()
print(missing_counts)
species               0
island                0
bill_length_mm        2
bill_depth_mm         2
flipper_length_mm     2
body_mass_g           2
sex                  11
year                  0
dtype: int64
# Beispiel: Zeilen mit fehlenden Werten entfernen
penguins_clean = penguins.dropna()

Das Entfernen fehlender Werte ist oft der einfachste Weg, kann aber Datenverlust bedeuten. Alternativ sind Imputation oder spezifische Filter möglich, je nach Analyseziel.

Datenaggregation und Gruppierung

Ein zentraler Anwendungsfall ist das Berechnen von Kennzahlen je Gruppe, z.B. durchschnittliche Körpermaße pro Pinguinart. In Pandas geschieht dies sehr idiomatisch mit groupby.

# Durchschnittliche Flossenlänge je Art
mean_flipper_length = penguins_clean.groupby('species')['flipper_length_mm'].mean()
print(mean_flipper_length)
species
Adelie       190.102740
Chinstrap    195.823529
Gentoo       217.235294
Name: flipper_length_mm, dtype: float64

Das groupby-Konzept ist vergleichbar mit LINQ in C# oder Streams in Java, aber in Python sehr kompakt und ausdrucksstark.

NumPy für numerische Operationen

Obwohl Pandas auf NumPy aufbaut, lohnt es sich, für numerisch intensive Operationen direkt NumPy-Arrays zu verwenden, da sie performanter sind.

# Flossenlänge als NumPy-Array
flipper_lengths: np.ndarray = penguins_clean['flipper_length_mm'].to_numpy()

# Standardabweichung mit NumPy
std_flipper_length = np.std(flipper_lengths)
print(f"Standard deviation of flipper length: {std_flipper_length:.2f}")
Standard deviation of flipper length: 13.99

Diese Trennung von Datenhaltung (Pandas) und numerischer Berechnung (NumPy) ist typisch für Python-Datenanalyse und unterscheidet sich von manchen statisch typisierten Umgebungen, in denen oft ein Framework beide Aufgaben übernimmt.

Visualisierung und weitere Analyse

Zur Veranschaulichung der Verteilung und Zusammenhänge empfiehlt sich eine Visualisierung, z.B. ein Scatter Plot der Körpermaße nach Art.

import matplotlib.pyplot as plt

# Scatter Plot: Schnabellänge vs. Flossenlänge, farblich nach Art
colors = {'Adelie': 'red', 'Chinstrap': 'green', 'Gentoo': 'blue'}

for species, group in penguins_clean.groupby('species'):
    plt.scatter(group['bill_length_mm'], group['flipper_length_mm'],
                label=species, color=colors[species], alpha=0.7)

plt.xlabel('Bill Length (mm)')
plt.ylabel('Flipper Length (mm)')
plt.title('Penguin Bill vs Flipper Length by Species')
plt.legend()
plt.show()

Penguins

Zusammenfassung

Dieses Praxisprojekt zeigt exemplarisch, wie Python mit NumPy und Pandas eine flexible, performante und idiomatische Datenanalyse ermöglicht. Die Kombination aus tabellarischer Datenstruktur, einfacher Handhabung fehlender Werte, leistungsfähigen Gruppierungsfunktionen und nahtloser Integration numerischer Berechnungen ist ein großer Vorteil gegenüber vielen anderen Sprachen.

Für erfahrene Entwickler bedeutet dies, dass sie nicht nur Syntax lernen, sondern vor allem eine neue Denkweise: Daten als flexible, aber strukturierte Objekte zu betrachten, die sich in wenigen Zeilen transformieren und analysieren lassen.