182 lines
7.4 KiB
Markdown
182 lines
7.4 KiB
Markdown
# 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)
|
||
```
|