Kategoriale Daten
Kategoriale Daten in Python kodieren mit Label Encoding, Ordinal Encoding, One-Hot Encoding und pd.get_dummies – mit scikit-learn-Beispielen.
Kategoriale Daten sind Daten, die eine begrenzte, feste Menge von Werten annehmen – zum Beispiel "red", "blue", "green" in einer Farbspalte oder "low", "medium", "high" für eine Schweregradskala. Die meisten Machine-Learning-Algorithmen arbeiten mit Zahlen, daher müssen kategoriale Spalten vor dem Training in eine numerische Darstellung umgewandelt werden.
Dieses Kapitel erläutert die wichtigsten Encoding-Strategien, wann welche eingesetzt werden sollte und wie man sie in Python mit pandas und scikit-learn korrekt anwendet, ohne dabei Informationen aus dem Testset in das Modell einfließen zu lassen.
Warum Encoding wichtig ist
Wenn man rohe String-Werte an einen scikit-learn-Schätzer übergibt, wird ein ValueError ausgelöst. Selbst wenn eine Spalte bereits Zahlen enthält – wie 1, 2, 3 für "small", "medium", "large" – wird ein Algorithmus, der Merkmalswerte als kontinuierliche Zahlen behandelt, eine falsche Beziehung ableiten (z. B. dass "large" dreimal so groß wie "small" ist). Encoding ermöglicht es, die tatsächliche Beziehung korrekt darzustellen.
Die Wahl des Encodings hängt von zwei Fragen ab:
- Gibt es eine natürliche Reihenfolge? Farben haben keine natürliche Reihenfolge (nominal). T-Shirt-Größen haben eine natürliche Reihenfolge (ordinal). Das richtige Encoding bewahrt die Reihenfolge, wenn sie vorhanden ist, und ignoriert sie, wenn nicht.
- Wie viele verschiedene Werte (Kardinalität) hat die Spalte? Spalten mit hoher Kardinalität (Hunderte einzigartiger Städte, Produkt-IDs) können beim One-Hot Encoding Tausende von Dummy-Spalten erzeugen, was sowohl den Speicherbedarf als auch die Modellleistung beeinträchtigt.
Einen Beispieldatensatz vorbereiten
Die folgenden Beispiele verwenden einen kleinen Kleidungsdatensatz, sodass die Ausgabe genau nachvollzogen werden kann.
import pandas as pd
data = {
"color": ["red", "green", "blue", "green", "red"],
"size": ["S", "M", "L", "S", "M"],
"price": [10, 20, 30, 10, 20],
"in_stock": [True, True, False, True, False],
}
df = pd.DataFrame(data)
print(df)Ausgabe:
color size price in_stock
0 red S 10 True
1 green M 20 True
2 blue L 30 False
3 green S 10 True
4 red M 20 FalseLabel Encoding
Label Encoding ersetzt jede Kategorie durch eine ganze Zahl. scikit-learns LabelEncoder weist die Ganzzahlen alphabetisch zu.
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
df["color_encoded"] = le.fit_transform(df["color"])
print(df[["color", "color_encoded"]])
print("Classes:", list(le.classes_))Ausgabe:
color color_encoded
0 red 2
1 green 1
2 blue 0
3 green 1
4 red 2
Classes: ['blue', 'green', 'red']blue → 0, green → 1, red → 2 (alphabetische Reihenfolge).
Wann verwenden: Label Encoding ist für die Zielvariable (y) vorgesehen, nicht für Eingabemerkmale. Auf eine nominale Merkmalsspalte angewendet, implizieren die kodierten Ganzzahlen eine Ordnung, die nicht existiert, was baumbasierte Modelle in die Irre führt und für lineare Modelle schädlich ist.
Encoding rückgängig machen:
decoded = le.inverse_transform([0, 1, 2])
print(decoded) # ['blue' 'green' 'red']Ordinal Encoding
Ordinal Encoding ähnelt dem Label Encoding, erlaubt aber die genaue Angabe der Reihenfolge der Kategorien. Zu verwenden bei Merkmalen, bei denen die Reihenfolge bedeutsam ist.
from sklearn.preprocessing import OrdinalEncoder
# Define the order explicitly: S < M < L
oe = OrdinalEncoder(categories=[["S", "M", "L"]])
df["size_encoded"] = oe.fit_transform(df[["size"]])
print(df[["size", "size_encoded"]])Ausgabe:
size size_encoded
0 S 0.0
1 M 1.0
2 L 2.0
3 S 0.0
4 M 1.0Das Modell kann nun korrekt ableiten, dass L (2) > M (1) > S (0) gilt.
Umgang mit unbekannten Kategorien zur Vorhersagezeit:
# Use handle_unknown='use_encoded_value' with unknown_value=-1
oe_safe = OrdinalEncoder(
categories=[["S", "M", "L"]],
handle_unknown="use_encoded_value",
unknown_value=-1,
)
oe_safe.fit(df[["size"]])
print(oe_safe.transform([["XL"]])) # [[-1.]]One-Hot Encoding
One-Hot Encoding erzeugt eine binäre Spalte pro Kategorie. Eine 1 in einer Spalte bedeutet, dass die Zeile zu dieser Kategorie gehört; alle anderen Spalten erhalten den Wert 0. Dies ist die Standardwahl für nominale (ungeordnete) Merkmale, die linearen Modellen, SVMs und neuronalen Netzen übergeben werden.
from sklearn.preprocessing import OneHotEncoder
import numpy as np
ohe = OneHotEncoder(sparse_output=False, handle_unknown="ignore")
color_encoded = ohe.fit_transform(df[["color"]])
# Build a labelled DataFrame from the result
col_names = ohe.get_feature_names_out(["color"])
color_df = pd.DataFrame(color_encoded, columns=col_names, dtype=int)
print(color_df)Ausgabe:
color_blue color_green color_red
0 0 0 1
1 0 1 0
2 1 0 0
3 0 1 0
4 0 0 1handle_unknown='ignore' füllt unbekannte Kategorien mit lauter Nullen, anstatt bei neuen Daten zur Vorhersagezeit einen Fehler auszulösen.
Eine Spalte weglassen, um Multikollinearität zu vermeiden
Bei drei Kategorien erhält man drei binäre Spalten, aber die dritte ist aus den anderen beiden vollständig vorhersagbar (blue = 1 − green − red). Diese Dummy-Variablen-Falle kann bei linearen Modellen zu Problemen führen. Eine Spalte mit drop='first' weglassen:
ohe_nodrop = OneHotEncoder(sparse_output=False, drop="first", handle_unknown="ignore")
reduced = ohe_nodrop.fit_transform(df[["color"]])
print(pd.DataFrame(reduced, columns=ohe_nodrop.get_feature_names_out(["color"]), dtype=int))Ausgabe (eine Spalte weggelassen):
color_green color_red
0 0 1
1 1 0
2 0 0
3 1 0
4 0 1Baumbasierte Modelle (Entscheidungsbäume, Random Forests, Gradient Boosting) sind gegen die Dummy-Variablen-Falle immun, daher ist das Weglassen einer Spalte bei ihnen optional.
pd.get_dummies — Die schnelle pandas-Alternative
Für explorative Arbeit ist pd.get_dummies() der schnellste Weg, einen DataFrame per One-Hot zu kodieren:
dummies = pd.get_dummies(df[["color", "size"]], dtype=int)
print(dummies)Ausgabe:
color_blue color_green color_red size_L size_M size_S
0 0 0 1 0 0 1
1 0 1 0 0 1 0
2 1 0 0 1 0 0
3 0 1 0 0 0 1
4 0 0 1 0 1 0Einschränkung: pd.get_dummies() ist kein trainierter Transformer. Es kann nicht garantieren, dass zwischen Trainings- und Testaufteilungen dieselbe Spaltenstruktur entsteht, und es unterstützt kein handle_unknown. Für Produktions-Pipelines sollte OneHotEncoder innerhalb einer scikit-learn Pipeline bevorzugt werden.
Datenlecks vermeiden
Datenlecks (Data Leakage) entstehen, wenn Informationen aus dem Testset die Vorbereitung der Trainingsdaten beeinflussen. Das Ergebnis ist ein übermäßig optimistischer Evaluierungswert, der die reale Leistung nicht widerspiegelt.
Das korrekte Vorgehen ist:
- Die Daten zunächst in Trainings- und Testsets aufteilen.
- Jeden Encoder ausschließlich auf dem Trainingsset trainieren (fit).
transform()(nichtfit_transform()) auf das Testset anwenden.
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import OneHotEncoder
X = df[["color", "size"]]
y = df["price"]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.4, random_state=42)
ohe = OneHotEncoder(sparse_output=False, handle_unknown="ignore")
ohe.fit(X_train) # fit on training data only
X_train_enc = ohe.transform(X_train) # transform training set
X_test_enc = ohe.transform(X_test) # transform test set using training-fit encoderWeitere Details zur Trainings-/Testaufteilung finden sich im Kapitel Train/Test Split.
Pipeline zur Kombination von Encoding und Modell verwenden
Eine scikit-learn Pipeline verkettet einen Transformer und einen Schätzer. Dadurch wird sichergestellt, dass der Encoder immer nur auf den Trainingsdaten trainiert wird, auch bei der Kreuzvalidierung.
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder
from sklearn.linear_model import LinearRegression
from sklearn.compose import ColumnTransformer
from sklearn.model_selection import train_test_split
categorical_cols = ["color", "size"]
numeric_cols = ["in_stock"]
X_full = df[categorical_cols + numeric_cols]
y_full = df["price"]
X_tr, X_te, y_tr, y_te = train_test_split(X_full, y_full, test_size=0.4, random_state=42)
preprocessor = ColumnTransformer(transformers=[
("ohe", OneHotEncoder(handle_unknown="ignore"), categorical_cols),
("pass", "passthrough", numeric_cols),
])
pipe = Pipeline(steps=[
("preprocessor", preprocessor),
("model", LinearRegression()),
])
pipe.fit(X_tr, y_tr)
print("Test predictions:", pipe.predict(X_te))Der ColumnTransformer wendet in einem Durchgang unterschiedliche Vorverarbeitungsschritte auf verschiedene Spalten an. Die Pipeline ist das empfohlene Muster für alle produktionsreifen Machine-Learning-Workflows.
Das richtige Encoding wählen
| Situation | Empfohlenes Encoding |
|---|---|
| Zielvariable (y) | LabelEncoder |
| Ordinales Merkmal (natürliche Reihenfolge vorhanden) | OrdinalEncoder mit expliziten categories |
| Nominales Merkmal, niedrige Kardinalität | OneHotEncoder (oder pd.get_dummies für Erkundung) |
| Nominales Merkmal, hohe Kardinalität | Target Encoding oder Frequency Encoding (siehe Hinweis unten) |
| Produktions-Pipeline | OneHotEncoder innerhalb einer Pipeline / ColumnTransformer |
Target Encoding ersetzt jede Kategorie durch den Mittelwert der Zielvariable für diese Kategorie. Es bewältigt hohe Kardinalität gut, ist aber besonders anfällig für Datenlecks – immer mit Kreuzvalidierungs-Folds anwenden oder eine Bibliotheksimplementierung verwenden (z. B. category_encoders.TargetEncoder), die dies automatisch handhabt.
Verwandte Kapitel
- Scale — numerische Merkmale vor der Modellierung normalisieren und standardisieren
- Train/Test Split — Daten vor jedem Vorverarbeitungsschritt korrekt aufteilen
- Linear Regression — ein Modell, das von korrektem kategorialem Encoding profitiert
- Cross Validation — Modelle zuverlässig evaluieren, wenn sie mit Encoding-Pipelines kombiniert werden
- Confusion Matrix — Klassifikationsmodell-Performance nach der Kodierung von Zielvariablen messen
- Pandas Tutorial — pandas-Grundlagen einschließlich DataFrame-Erstellung und -Manipulation