diff --git a/SL/notizen/L3_Notizen.md b/SL/notizen/L3_Notizen.md new file mode 100644 index 0000000..4beb4fe --- /dev/null +++ b/SL/notizen/L3_Notizen.md @@ -0,0 +1,181 @@ +# Notizen SL Lektion 3 + +> Thema: Feature Engineering +> Datum: 28.05.2026 +> Dozentin: Violeta Vogel + +## Recap + +- Sandbox Prinzip + - Daten werden immer als Kopie bearbeitet (`data = ori_data.copy()`), damit Transformationsschritte sich nicht gegenseitig beeinflussen und die Ausgangsdaten nicht neu geladen werden müssen +- hohe Kardinalität + - bezeichnet eine hohe Anzahl an eindeutigen Werten + +## Arten von Variablen + +- Numerische Variablen + - messbare Zahlen + - **Stetig** (kontinuierlich) + - Können jeden beliebigen Wert annehmen und unendlich fein unterteilt werden. + - Beispiele: Körpergröße, Gewicht, Temperatur, Zeit + - **Diskret** + - Können nur in bestimmten, meist ganzzahligen Schritten gezählt werden. + - Beispiele: Anzahl der Kinder, Autoverkäufe, Besucher eines Konzerts +- Kategoriale Variablen + - Gruppen oder Eigenschaften + - Diese Variablen teilen Beobachtungen in verschiedene Gruppen oder Kategorien ein. Sie geben eine Eigenschaft an. + - **Nominal** + - Die Kategorien haben keine logische Reihenfolge. + - Beispiele: Geschlecht, Haarfarbe, Augenfarbe, Postleitzahlen. + - **Ordinal** + - Die Kategorien haben eine natürliche Rangordnung oder Reihenfolge. + - Beispiele: Zufriedenheitsgrade (sehr zufrieden, zufrieden, unzufrieden), Schulnoten (1 bis 6) + +## Transformationen: Numerisieren Kategorialer Variablen + +- Numerisieren + - Numerische Darstellung einer Variablen, damit ML damit rechnen kann + - Verschiedene Methoden + - Faktorisieren + - Ordinal Encodieren + - Nominal Encodieren + - Faustregel: nominal → One-Hot, ordinal → Ordinal Encoding, reines Label ohne Bedeutung → Faktorisieren + +### Faktorisieren + +- Jeder Kategorie einer kategorialen Variablen wird ein Integer-Wert zugeordnet, beginnend bei 0 +- `data.job = pd.factorize(data.job)[0]` + - `pd.factorize()` gibt ein Tupel zurück: + - `[0]` → faktorisierte Werte (`numpy.ndarray`), beginnend bei 0 + - `[1]` → Index mit der Zuordnung der Werte zu den Ausgangswerten +- Werte werden per Default in **Reihenfolge des Auftretens** im Dataset vergeben (nicht sortiert) +- mit `sort=True` werden sie lexikografisch (bezogen auf die Ausgangswerte) vergeben +- **Schwäche:** die numerische Zuordnung ist im Grunde willkürlich (Reihenfolge im Datensatz) → es wird eine Ordnung impliziert, die inhaltlich keine ist. Taugt sauber nur für nominale Daten, wo die Zahl reines Label ist. + +### Ordinal Encodieren + +- Behebt die Faktorisier-Schwäche: bei einer **tatsächlich ordinalen** Variable werden die Zahlen gezielt der natürlichen Rangordnung zugeordnet +- Beispiel `education`: + - `illiterate → 0` + - `unknown → 0` + - `basic.4y → 1` + - `basic.6y → 2` + - `basic.9y → 3` + - `professional.course → 4` + - `high.school → 5` + - `university.degree → 6` +- Umsetzung über `.replace()` mit einem (verschachtelten) Dictionary, das vorher definiert wird: + ```python + data.replace(replace_nums, inplace=True) + ``` +- Hinweis: `sklearn.preprocessing.OrdinalEncoder` macht in Wahrheit nur eine Faktorisierung — echtes ordinales Mapping erfordert deutlich aufwändigere Parametrisierung. + +#### Spezialfall: 0-1 Encodieren + +- Bei nur zwei Kategorien reicht `np.where`: + ```python + data['contact'] = np.where(data.contact == 'cellular', 1, 0) + ``` +- Achtung: alles, was *nicht* `cellular` ist, wird 0 (inkl. NAs) +- Danach ggf. Spalte umbenennen für Transparenz: + ```python + data.rename(columns={'contact': 'contact_cellular'}, inplace=True) + ``` + +### Nominal Encodieren (One-Hot) + +- Für Variablen **ohne** Rangordnung — hier wäre eine ordinale Zahl irreführend +- `pd.get_dummies()` erstellt pro Kategorie eine neue Dummy-Variable (0/1) **und entfernt die Ausgangsvariable** +- Wichtige Parameter: + - `drop_first=True` → eine Dummy weniger als Kategorien (vermeidet perfekte Multikollinearität / Dummy-Trap) + - `prefix='marital'` → benennt die neuen Spalten mit Präfix (sinnvoll bei mehreren Variablen) + - `columns=[...]` → mehrere Variablen auf einmal +- Trick für alle kategorialen Spalten außer Target: + ```python + target = 'y' + sel_vars = data.select_dtypes(include=['object']).columns.drop(target) + data = pd.get_dummies(data, columns=sel_vars, drop_first=True) + ``` + +## Transformationen: Numerische Variablen + +> Hinweis: Normalisieren und Standardisieren sind **Unterarten** von Skalieren, nicht drei gleichrangige Methoden. + +- Methoden + - Skalieren + - Normalisieren + - Standardisieren + - Binning + +### Skalieren + +- Bringt numerische Variablen auf vergleichbare Wertebereiche +- Nur sinnvoll, wenn auf **alle** relevanten Variablen gleich angewendet +- Muss ggf. für spätere neue Daten gespeichert werden → daher in der Praxis `sklearn.preprocessing` statt Handformel: + - `MinMaxScaler` → Normalisierung + - `StandardScaler` → Standardisierung +- `.set_output(transform="pandas")` behält den DataFrame, statt ein numpy-Array zurückzugeben: + ```python + from sklearn.preprocessing import MinMaxScaler + scaler = MinMaxScaler().set_output(transform="pandas") + data = scaler.fit_transform(data) + ``` + +#### Normalisieren + +- Skaliert auf festen Bereich [0, 1] +- Formel: `(x - min) / (max - min)` +- min → 0, max → 1 + +#### Standardisieren + +- Zentriert auf Mittelwert 0, Standardabweichung 1 (z-Transformation) +- Formel: `(x - mean) / std` +- danach: mean ≈ 0, std = 1 + +> **Wichtig:** Keines der beiden Verfahren ändert die *Form* der Verteilung — nur die Skala der x-Achse. Schiefe bleibt schief. + +### Binning + +- Numerische Variable in Klassen/Bins zusammenfassen → wird quasi (ordinal-)kategorial +- **Equal Binning:** teilt min–max in gleich breite Bereiche + ```python + bins = 10 + data.age = pd.cut(data.age, bins=bins, labels=list(range(1, bins + 1))) + ``` +- **Custom Bins:** eigene Grenzen, nützlich bei stark schiefen Verteilungen + ```python + data.campaign = pd.cut( + data.campaign, + bins=[0, 1, 2, 3, 4, 5, 10, 1000], + labels=[1, 2, 3, 4, 5, '6-10', '>10']) + ``` +- Zur optimalen Bin-Anzahl gibt es keinen Konsens (Freedman-Diaconis, Sturges, …) → experimentell ermitteln + +## Konstruktion + +- Neue Variablen aus bestehenden ableiten +- Ziele: Komplexität reduzieren, Korrelationen vermeiden +- Beispiel **zyklische Daten** (Windrichtung): 359° und 1° sind nah beieinander, numerisch aber weit weg → Zerlegung in sin/cos-Komponenten löst das + ```python + data['x'] = np.sin(data.direction * np.pi / 180) * data.speed + data['y'] = np.cos(data.direction * np.pi / 180) * data.speed + ``` + - allgemein relevant für alles Zyklische (Stunden, Monate, Winkel) +- Beispiel **Datum**: String → `pd.to_datetime()`, dann Komponenten extrahieren + ```python + data['date_dt'] = pd.to_datetime(data.Date, format="%d/%m/%Y") + data['year'] = data.date_dt.dt.year + data['month'] = data.date_dt.dt.month + data['day'] = data.date_dt.dt.day + ``` + - oder Differenzen zu einem Startdatum (`(data.date_dt - start_date).dt.days`) + +## Bereinigen von Variablennamen + +- Nach One-Hot entstehen Namen mit Leerzeichen / Bindestrichen / Punkten (`job_blue collar`, `emp.var.rate`), die manche ML-Frameworks als Bezeichner ablehnen +- Erlaubte Zeichen: `a-z`, `A-Z`, `0-9`, `_` +- Per Regex unerlaubte Zeichen durch `_` ersetzen: + ```python + new_names = old_names.str.replace('[^a-zA-Z0-9_]', '_', regex=True) + ```