Feature Engineering - Die Daten-Küche
Standalone-Kapitel für den Data-Science-Lernpfad.
Feature Engineering - Die Daten-Küche
Rohdaten müssen gewaschen, sortiert, portioniert und in ein verarbeitbares Rezept gebracht werden, bevor ein Modell sinnvoll lernen kann.
Warum dieses Kapitel jetzt passt
Wenn Zutaten fehlen, andere Einheiten nutzen oder Worte statt Zahlen vorliegen, kann der Koch-Automat kein gutes Gericht bauen. Genau so stolpern ML-Modelle über unvorbereitete Daten.
Du erkennst Merkmalstypen, vermeidest Data Leakage, kombinierst Vorverarbeitungsschritte in einer Pipeline und kannst eine kleine Projekt-Routine begründen.
Warum brauchen wir Feature Engineering?
Analogie: Die Kochbox kommt roh. Erst waschen, schneiden und portionieren, dann kann der Ofen etwas Sinnvolles daraus machen.
ML-Workflow im Überblick
Feature Engineering sitzt mitten zwischen Datenverstehen und Modelltraining. Es ist die Brücke zwischen Rohdaten und lernbaren Signalen.
Warum ist der Schritt so groß?
In vielen Projekten fällt der größte Teil der Zeit nicht auf das Modell, sondern auf Datenvorbereitung. Das ist keine Nebensache, sondern der eigentliche Hebel für gute oder schlechte Vorhersagen.
Immer erst Train-Test-Split, dann Feature Engineering. Sonst schaut das Modell bei der Prüfung ab.
Techniken auf einen Blick
| Technik | Was sie macht | Küchen-Analogie |
|---|---|---|
| Imputation | Fehlende Werte füllen | Fehlende Zutat ersetzen |
| Categorical Encoding | Worte in Zahlen übersetzen | Farbcodes für Mülltrennung |
| Feature Scaling | Werte auf vergleichbare Skalen bringen | Gramm und Liter nebeneinander lesbar machen |
| Discretization | Zahlen in Gruppen einteilen | Kleidergrößen statt exakter Maße |
| Feature Expansion | Neue Merkmale ableiten | Gewürzmischung aus Einzelgewürzen |
| Pipeline | Alles reproduzierbar automatisieren | Thermomix mit festen Programmen |
Palmer Penguins als Beispiel
Ziel: Die Körpermasse `body_mass_g` vorhersagen. In den Features liegen Textspalten, Zahlen und fehlende Werte gemischt vor.
| species | island | bill_length_mm | bill_depth_mm | flipper_length_mm | sex | body_mass_g |
|---|---|---|---|---|---|---|
| Adelie | Torgersen | 39.1 | 18.7 | 181 | Male | 3750 |
| Adelie | Torgersen | 39.5 | 17.4 | 186 | Female | 3800 |
| Chinstrap | Dream | 50.0 | 19.5 | NaN | Female | 3600 |
| Gentoo | Biscoe | 46.1 | 13.2 | 211 | NaN | 4500 |
| Gentoo | Biscoe | 50.0 | 16.3 | 230 | Male | 5700 |
Best Practice als Ablauf
Code-Startpunkt
# Daten laden
import pandas as pd
df = pd.read_csv("penguins.csv")
# Features und Ziel trennen
numerical_features = ["bill_length_mm", "bill_depth_mm", "flipper_length_mm"]
categorical_features = ["species", "island", "sex"]
target_variable = "body_mass_g"
X = df[numerical_features + categorical_features]
y = df[target_variable]
# Train-Test-Split (immer zuerst!)
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.25, random_state=42, shuffle=True
)
🗣️ Übersetze den Output in Alltagssprache
Was ist gemessen? In unserem Fall noch kein Modell-Erfolg, sondern ob Daten, Ziel und ehrliche Prüfung sauber getrennt wurden.
So liest man den Wert: Ein späterer Test-Score bedeutet nur dann etwas, wenn dieser Split vorher sauber passiert ist.
Was ist hier der schlimmste Fehler? Data Leakage: Das Modell sieht beim Vorbereiten schon etwas aus dem späteren Test.
✅ Mini-Check (30 Sekunden)
- Welche Spalten sind in unserem Fall Features und welche ist das Ziel?
- Was wäre ein fairer Testsatz als ganzer Satz?
- Welche Stellschraube würdest du zuerst prüfen, wenn ein Test-Score verdächtig gut aussieht?
Quiz: Was passiert mit rohen Daten im Modell?
Imputation: Fehlende Zutaten ergaenzen
Analogie: Wenn beim Kochen Salz fehlt, ersetzt du es nicht zufaellig, sondern mit einer plausiblen Regel.
Strategien auf einen Blick
| Strategie | Beschreibung | Wann? | Küche |
|---|---|---|---|
mean | Durchschnitt | Normalere Zahlenwerte | Durchschnittliche Gewuerzmenge |
median | Mittlerer Wert | Bei Ausreißern | Mittlere Backzeit |
most_frequent | Haeufigster Wert | Text und Kategorien | Beliebteste Beilage |
constant | Fester Ersatz | Wenn Standard bekannt ist | Immer 200 Grad |
.fit() nur auf Trainingsdaten. Auf Testdaten nur .transform(). Sonst landet Wissen aus der Prüfung schon heimlich in der Vorbereitung.
SimpleImputer in Worten
.fit() = Der Imputer schaut auf Trainingsdaten und merkt sich den Ersatzwert.
.transform() = Der gemerkte Wert wird spaeter eingesetzt. Auf Testdaten willst du genau das: anwenden, aber nichts Neues lernen.
.fit_transform() = Beides zusammen, aber nur auf Trainingsdaten sinnvoll. Sonst lernt dein Verfahren schon etwas über den Testsatz.
Median-Formel oeffnen
Sortiere zuerst alle vorhandenen Werte. Bei ungerader Anzahl nimmst du den echten Mittelwert der Positionen. Bei gerader Anzahl mittelt man die beiden mittleren Werte.
Interaktive Demo: Fehlende Flossenlänge ersetzen
Wähle eine Strategie und führe die Imputation auf einem kleinen Trainingsausschnitt aus.
| # | flipper_length_mm |
|---|
🗣️ Übersetze den Output in Alltagssprache
Was ist gemessen? In unserem Fall, welcher Ersatzwert für fehlende Flossenlängen gelernt und eingesetzt wird.
So liest man den Wert: Wenn hier zum Beispiel 182 steht, heißt das: Genau dieser gelernte Wert füllt die Lücke in unserem Trainingsausschnitt.
Was ist hier der schlimmste Fehler? Den Ersatzwert aus Train und Test zusammen zu lernen. Dann wirkt die spätere Prüfung künstlich besser.
✅ Mini-Check (30 Sekunden)
- Ist der Ersatzwert in unserem Fall ein echter Messwert oder eine gelernte Regel?
- Wie würdest du den imputierten Wert als ganzen Satz erklären?
- Welche Stellschraube würdest du ändern, wenn Ausreißer den Durchschnitt zu stark verziehen?
🏋️ Übung: Fehlende Werte mit dem Median auffüllen
Baue einen kleinen Datensatz mit einer Lücke und ergänze die fehlende Zeile so, dass der Median als Ersatzwert gelernt und eingesetzt wird. So übst du genau das Muster aus dem Notebook in einer handlichen Mini-Version.
📓 Öffne dein Jupyter Notebook oder Google Colab und probiere es selbst aus.
from sklearn.impute import SimpleImputer
X_train = [[180.0], [182.0], [185.0], [None]]
imputer = SimpleImputer(strategy="median")
# ??? DEINE LÖSUNG ???
print(filled[-1][0])
💡 Tipp anzeigen
Der Imputer braucht erst fit_transform auf den Trainingsdaten, damit die Lücke durch den Median ersetzt wird.
✅ Lösung anzeigen
from sklearn.impute import SimpleImputer
X_train = [[180.0], [182.0], [185.0], [None]]
imputer = SimpleImputer(strategy="median")
filled = imputer.fit_transform(X_train)
print(filled[-1][0])
Erwartete Ausgabe: 182.0
Code-Beispiel
from sklearn.impute import SimpleImputer
# Imputer für Kategorien
sex_imputer = SimpleImputer(strategy="most_frequent")
sex_imputer.fit(X_train[["sex"]])
X_train["sex"] = sex_imputer.transform(X_train[["sex"]])
X_test["sex"] = sex_imputer.transform(X_test[["sex"]])
# Imputer für Zahlen
num_imputer = SimpleImputer(strategy="median")
num_imputer.fit(X_train[["flipper_length_mm", "bill_depth_mm"]])
X_train[["flipper_length_mm", "bill_depth_mm"]] = num_imputer.transform(
X_train[["flipper_length_mm", "bill_depth_mm"]]
)
Quiz: Wie imputiert man Testdaten?
Categorical Encoding
Analogie: Mülltrennung, Schulnoten und Größenklassen zeigen, dass manche Kategorien keine Reihenfolge haben und andere schon.
OneHotEncoder oder OrdinalEncoder?
Vorher / Nachher: One-Hot-Encoding
| # | species |
|---|---|
| 1 | Adelie |
| 2 | Chinstrap |
| 3 | Gentoo |
drop='first' erkennt man Adelie daran, dass beide neuen Spalten 0 sind. Die Referenzkategorie ist also nicht leer, sondern der Fall ohne aktiven Dummy.🗣️ Übersetze den Output in Alltagssprache
Was ist gemessen? In unserem Fall, wie eine Textkategorie in mehrere Ja/Nein-Spalten übersetzt wird.
So liest man den Wert: Eine 1 heißt hier: „Diese Kategorie trifft zu.“ Zwei Nullen können trotzdem eine echte Kategorie bedeuten, nämlich die Referenz.
Was ist hier der schlimmste Fehler? Einer Kategorie eine künstliche Reihenfolge zu geben, obwohl sie inhaltlich keine hat.
✅ Mini-Check (30 Sekunden)
- Was bedeutet die Referenzkategorie in unserem Fall als ganzer Satz?
- Warum ist eine 0 in einer Dummy-Spalte nicht automatisch „fehlend“?
- Welche Stellschraube würdest du ändern, wenn eine Kategorie wirklich eine natürliche Reihenfolge hat?
🏋️ Übung: Kategorien in Dummy-Spalten umwandeln
Wandle drei Tierarten in Dummy-Spalten um und lasse eine Referenzkategorie weg. Ergänze die Lücke so, dass du die Form der neuen Tabelle kontrollieren kannst.
📓 Öffne dein Jupyter Notebook oder Google Colab und probiere es selbst aus.
import pandas as pd
species = pd.Series(["Adelie", "Chinstrap", "Gentoo"])
# ??? DEINE LÖSUNG ???
print(encoded.shape)
💡 Tipp anzeigen
Nutze pd.get_dummies und setze drop_first=True, damit eine Referenzkategorie übrig bleibt.
✅ Lösung anzeigen
import pandas as pd
species = pd.Series(["Adelie", "Chinstrap", "Gentoo"])
encoded = pd.get_dummies(species, drop_first=True)
print(encoded.shape)
Erwartete Ausgabe: (3, 2)
Code-Beispiel
from sklearn.preprocessing import OneHotEncoder, OrdinalEncoder
species_encoder = OneHotEncoder(
drop="first",
handle_unknown="ignore",
sparse_output=False
)
species_encoder.fit(X_train[["species"]])
species_encoded = species_encoder.transform(X_train[["species"]])
edu_encoder = OrdinalEncoder(
categories=[["Hauptschule", "Realschule", "Abitur", "Bachelor", "Master", "Doktor"]],
dtype=int
)
Drag-and-Drop: Nominal oder Ordinal?
Ziehe jede Variable in den passenden Kasten. Erst danach prüfen.
Ablage
Nominal
Ordinal
Feature Scaling
Analogie: Einkommen in Euro und Alter in Jahren darf das Modell nicht nur wegen der Größe der Zahlen ungleich gewichten.
Skalierungsarten und Formeln
MinMaxScaler
Alle Werte werden auf den Bereich zwischen 0 und 1 gestaucht. Das ist anschaulich, aber ausreisser-empfindlich.
StandardScaler
Der Mittelwert wird 0, die Standardabweichung 1. Das passt oft gut, wenn die Verteilung halbwegs normal wirkt.
RobustScaler
Median und Interquartilsabstand reagieren viel weniger empfindlich auf extreme Ausreißer.
Log-Transformation
Hilft bei schiefen Verteilungen, funktioniert aber nur für positive Werte.
Wann ist Scaling wichtig?
Interaktive Vorher/Nachher-Visualisierung
Die Basiswerte enthalten absichtlich einen Ausreißer. Wechsle den Scaler und beobachte Histogramm und Kennzahlen.
🗣️ Übersetze den Output in Alltagssprache
Was ist gemessen? In unserem Fall, wie stark rohe Zahlen zusammengedrückt, zentriert oder robuster vergleichbar gemacht werden.
So liest man den Wert: Ein skalierter Wert wie 0.5 heißt nicht „halb so wichtig“, sondern nur: Diese Beobachtung liegt auf der neuen Skala ungefähr in der Mitte.
Was ist hier der schlimmste Fehler? Große Zahlen dominieren die Rechnung nur wegen ihrer Einheit, nicht wegen ihrer fachlichen Bedeutung.
✅ Mini-Check (30 Sekunden)
- Was wurde in unserem Fall verändert: die Bedeutung des Merkmals oder nur seine Zahlenskala?
- Wie würdest du einen Wert von 0.5 nach dem MinMaxScaler als Satz erklären?
- Welche Stellschraube würdest du ändern, wenn ein Ausreißer die Skala zu stark mitzieht?
🏋️ Übung: Einen Zahlenwert auf 0 bis 1 skalieren
Skaliere drei einfache Werte mit dem MinMaxScaler. Ergänze die Lücke so, dass du den mittleren Wert nach der Transformation ablesen kannst.
📓 Öffne dein Jupyter Notebook oder Google Colab und probiere es selbst aus.
from sklearn.preprocessing import MinMaxScaler
values = [[10.0], [20.0], [30.0]]
scaler = MinMaxScaler()
# ??? DEINE LÖSUNG ???
print(scaled[1][0])
💡 Tipp anzeigen
Wende fit_transform auf die Liste an. Der mittlere Rohwert landet genau in der Mitte der neuen Skala.
✅ Lösung anzeigen
from sklearn.preprocessing import MinMaxScaler
values = [[10.0], [20.0], [30.0]]
scaler = MinMaxScaler()
scaled = scaler.fit_transform(values)
print(scaled[1][0])
Erwartete Ausgabe: 0.5
Code-Beispiel
from sklearn.preprocessing import MinMaxScaler, StandardScaler, RobustScaler
minmax = MinMaxScaler(feature_range=(0, 1))
minmax.fit(X_train[["bill_length_mm", "flipper_length_mm"]])
X_train_scaled = minmax.transform(X_train[["bill_length_mm", "flipper_length_mm"]])
standard = StandardScaler()
standard.fit(X_train[["bill_length_mm"]])
robust = RobustScaler()
robust.fit(X_train[["bill_length_mm"]])
Quiz: Welcher Scaler passt zum Ausreißer?
Feature Expansion und Discretization
Analogie: Aus Mehl und Wasser wird Teig, und aus exakten Maßen werden Größenklassen wie S, M, L.
Discretization
KBinsDiscretizer verwandelt kontinuierliche Zahlen in Gruppen. Das kann Rauschen reduzieren oder lineare Modelle flexibler machen.
uniform setzt gleich breite Intervalle.
PolynomialFeatures
Mit degree=2 entstehen Quadrate und Interaktionsterme. Damit kann ein lineares Modell gekrümmtere Zusammenhänge indirekt lernen.
Interaktive Demo: Bins und neue Features
| bill_length_mm | zugewiesener Bin |
|---|
🗣️ Übersetze den Output in Alltagssprache
Was ist gemessen? In unserem Fall, in welche Gruppe ein Zahlenwert fällt und welche neuen Kombinationsmerkmale zusätzlich entstehen.
So liest man den Wert: Ein Bin ist keine neue Messung, sondern eine Sammelschublade. Ein Interaktionsterm heißt: Zwei Merkmale wirken gemeinsam.
Was ist hier der schlimmste Fehler? Zu grobe Gruppen oder explodierende Zusatzmerkmale, die mehr Verwirrung als Nutzen bringen.
✅ Mini-Check (30 Sekunden)
- Was bedeutet ein Bin in unserem Fall als ganzer Satz?
- Was sagt dir ein Interaktionsterm wie A*B in Alltagssprache?
- Welche Stellschraube würdest du zuerst ändern: Zahl der Bins oder Grad der Expansion?
Code-Beispiele
from sklearn.preprocessing import KBinsDiscretizer, PolynomialFeatures
binner = KBinsDiscretizer(
n_bins=5,
encode="onehot-dense",
strategy="quantile"
)
binner.fit(X_train[["bill_length_mm"]])
poly = PolynomialFeatures(
degree=2,
interaction_only=False,
include_bias=False
)
# Eingabe: [bill_length, flipper_length]
# Ausgabe: [bill, flipper, bill^2, bill*flipper, flipper^2]
Quiz: Was erzeugt interaction_only=True?
Pipeline und ColumnTransformer
Analogie: Ein Küchenteam arbeitet parallel an unterschiedlichen Zutaten, während die Pipeline die Schritte für jede Zutat nacheinander organisiert.
Klickbare Architektur
Gesamt-Pipeline
Ein einziges Objekt verwaltet Vorverarbeitung und Modell gemeinsam. Dadurch bleibt der Ablauf reproduzierbar: .fit(X_train, y_train) lernt alles auf den Trainingsdaten, .predict(X_test) transformiert nur und sagt dann vorher.
🗣️ Übersetze den Output in Alltagssprache
Was ist gemessen? In unserem Fall die Reihenfolge, in der Daten vorbereitet und erst danach ans Modell übergeben werden.
So liest man den Wert: Eine Pipeline-Ausgabe wie ['preprocessing', 'regression'] heißt: Erst sauber vorbereiten, dann vorhersagen.
Was ist hier der schlimmste Fehler? Schritte zu vertauschen oder auf Testdaten erneut zu fitten. Dann ist der ganze Ablauf nicht mehr ehrlich reproduzierbar.
✅ Mini-Check (30 Sekunden)
- Was macht in unserem Fall der Vorverarbeiter vor dem Modell?
- Wie wuerdest du die Pipeline als Satz einem Kollegen erklaeren?
- Welche Stellschraube würdest du zuerst prüfen, wenn Training und Test unterschiedlich vorbereitet werden?
Warum ColumnTransformer und Pipeline zusammen?
fit()-Aufrufen auf Testdaten.
Code-Beispiel
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.linear_model import LinearRegression
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import OneHotEncoder, RobustScaler
sex_pipeline = Pipeline(steps=[
("imputer", SimpleImputer(strategy="most_frequent")),
("ohe", OneHotEncoder(drop="first", sparse_output=False))
])
flipper_pipeline = Pipeline(steps=[
("imputer", SimpleImputer(strategy="median")),
("scaler", RobustScaler())
])
preprocessor = ColumnTransformer(transformers=[
("sex", sex_pipeline, ["sex"]),
("species", OneHotEncoder(drop="first", sparse_output=False), ["species"]),
("flipper", flipper_pipeline, ["flipper_length_mm"]),
("bill", RobustScaler(), ["bill_length_mm"])
])
model = Pipeline(steps=[
("preprocessing", preprocessor),
("regression", LinearRegression())
])
🏋️ Übung: Eine vollständige Modell-Pipeline zusammensetzen
Baue die letzten beiden Schritte einer Modell-Pipeline zusammen. Ergänze die Lücken so, dass zuerst der Vorverarbeiter und danach die lineare Regression in der Pipeline stehen.
📓 Öffne dein Jupyter Notebook oder Google Colab und probiere es selbst aus.
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LinearRegression
preprocessor = ColumnTransformer([
("num", StandardScaler(), ["temp"])
])
model = Pipeline(steps=[
# ??? DEINE LÖSUNG ???
])
print(list(model.named_steps.keys()))
💡 Tipp anzeigen
Die Schritt-Namen sollen preprocessing und regression heißen, genau in dieser Reihenfolge.
✅ Lösung anzeigen
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LinearRegression
preprocessor = ColumnTransformer([
("num", StandardScaler(), ["temp"])
])
model = Pipeline(steps=[
("preprocessing", preprocessor),
("regression", LinearRegression())
])
print(list(model.named_steps.keys()))
Erwartete Ausgabe: ['preprocessing', 'regression']
Drag-and-Drop: Bringe die Schritte in die richtige Reihenfolge
Ordne die vier Schritte vom sauberen Start bis zur Testbewertung.
Die Daten-Küche - Zusammenfassung
Wenn alle Module abgeschlossen sind, leuchten alle Sterne auf. Der rote Faden bleibt derselbe: erst Typ erkennen, dann passende Vorbereitung, dann reproduzierbar in eine Pipeline packen.
| Technik | Kernfrage | Warnung |
|---|---|---|
| Imputation | Wie gehe ich mit Lücken um? | Nie auf Testdaten fitten. |
| Encoding | Hat die Kategorie eine Reihenfolge? | Ordinal nur nutzen, wenn Reihenfolge echt ist. |
| Scaling | Dominieren große Zahlen die Logik? | Ausreißer können einfache Skalen verzerren. |
| Discretization | Hilft Gruppierung mehr als exakter Wert? | Zu grobe Bins kosten Details. |
| Expansion | Brauche ich Interaktionen oder Kruemmung? | Erst skalieren, dann erweitern. |
| Pipeline | Wie halte ich den Ablauf sauber? | Einzelschritte ohne Kapselung sind fehleranfaellig. |
Snowman-Übung
Baue für diesen Datensatz eine komplette Pipeline. Ziel ist height_snowman_cm.
| temp | lunch | dinner | precipitation | height_snowman_cm |
|---|---|---|---|---|
| -3 | soup | pizza | yes | 100 |
| 5 | sandwich | pizza | no | 0 |
| 0 | soup | noodles | yes | 75 |
| 7 | burger | NaN | yes | 0 |
| NaN | salad | fishsticks | yes | 35 |
| -6 | sandwich | noodles | yes | 170 |
1. Teile die Spalten in binär, kategorisch und numerisch.
2. Entscheide pro Spaltengruppe, welche Imputation passt.
3. Encodiere Textspalten sauber und skaliere numerische Spalten bei Bedarf.
4. Fasse alles in einem ColumnTransformer plus Pipeline zusammen.
5. Begründe, wo du besonders auf Data Leakage achten musst.