From 1410c6c99027337f1063be051ae6a6ef2feacc76 Mon Sep 17 00:00:00 2001 From: aaron Date: Thu, 28 May 2026 14:39:00 +0200 Subject: [PATCH] feature: update readme, add more output to ml-pipeline --- SL/aufgaben/workshop3/README.md | 66 ++++++++++++++++++++++++---- SL/aufgaben/workshop3/src/prepare.py | 2 + 2 files changed, 60 insertions(+), 8 deletions(-) diff --git a/SL/aufgaben/workshop3/README.md b/SL/aufgaben/workshop3/README.md index 4b467d2..f65221f 100644 --- a/SL/aufgaben/workshop3/README.md +++ b/SL/aufgaben/workshop3/README.md @@ -8,7 +8,7 @@ den in Workshop 2 erarbeiteten und konsolidierten Empfehlungen. - **Pipeline:** `src/prepare.py` Die Empfehlungen stammen aus der EDA in Workshop 2. Dieses README ist die -einzige Quelle der Wahrheit für die anzuwendenden Transformationen, das +einzige Quelle der Wahrheit für die anzuwendenden Transformationen — das ursprüngliche `WS_03_Empfehlungen.xlsx` muss nicht geöffnet werden. ## Aufgabenstellung @@ -16,7 +16,7 @@ ursprüngliche `WS_03_Empfehlungen.xlsx` muss nicht geöffnet werden. Das Dataset wurde in Workshop 2 mit Sicht auf Machine Learning untersucht. Die daraus abgeleiteten Empfehlungen (siehe unten) werden hier in einer deterministischen Pipeline `CSV rein → CSV raus` implementiert. Es findet -keine neue Exploration statt, die Entscheidungen sind bereits getroffen. +keine neue Exploration statt — die Entscheidungen sind bereits getroffen. ## Drehbuch (Transformationsliste) @@ -89,10 +89,60 @@ workshop3 python src/prepare.py ``` -## Offene Punkte / Selbstcheck +## Selbstcheck (geklärt) -- [ ] `YearBuilt == 1196` verifizieren (vermutlich Tippfehler für 1996) -- [ ] NA-Spalten prüfen -> sind NAs in der Target-Variable `Price`? - (Falls ja: Zeilen entfernen statt Median einsetzen.) -- [ ] Standardisieren bleibt hier aus -> relevant erst beim Training, - und modellabhängig (Bäume brauchen es nicht). +- [x] `YearBuilt == 1196` verifiziert: nächster Wert ist 1830, Sprung von 634 + Jahren, physikalisch unmöglich (Melbourne ~1835 gegründet) → Datenfehler, + eine Zeile entfernt. +- [x] NAs in Target `Price`? Nein — Spalte ist vollständig (18396 non-null). + Median-Imputation in E4 daher unkritisch. +- [x] Standardisieren bleibt aus — modellabhängig, gehört ans Training, nicht in + die Aufbereitung (Bäume brauchen es nie). + +## Ergebnis + +- 18'393 Zeilen (von 18'396, −3 in 1.1), 24 Spalten +- keine `object`-Spalten mehr -> alles numerisch (`int`, `float`, `bool`) +- Output: `data/melb_data_prep.csv` + +Spaltenrechnung zur Kontrolle: 16 nach NA-Imputation → +8/−2 durch One-Hot (22) +→ −1 `Date`/+3 Datumskomponenten (24). + +## Implementierungs-Notizen (Abweichungen von den Vorlagen) + +Bewusste Entscheidungen, die von `1.6 Implementation.ipynb` (Bank) bzw. der +WS-03-Musterlösung abweichen: + +- **Reine `df -> df`-Funktionen statt `inplace=True`.** Jede Transformation + gibt einen neuen DataFrame zurück. Vermeidet `SettingWithCopyWarning` auf + Slices und ist Copy-on-Write-sicher (pandas 3.x). Pipeline-Struktur: + `df = e1(df); df = e2(df); …`. +- **`.map({...})` statt `.replace([...], [...])`** beim Ordinal-Encoding von + `Type`. Vermeidet die Downcasting-`FutureWarning` und ist lesbarer + (Kategorie→Zahl direkt nebeneinander statt positionsabhängige Listen). +- **`.fillna()` statt `SimpleImputer`** für die NA-Imputation. KISS — kein + sklearn-Objekt nötig, da hier keine spätere Inference auf neuen Daten + stattfindet (kein Bedarf, die Imputationswerte zu persistieren). +- **Vektor-Operation `df.columns.str.replace(...)` statt `for`-Loop** beim + Bereinigen der Variablennamen. +- **`+ 1` statt `+ min + 1`** beim Logarithmieren. Flächen sind nie negativ + (Minimum 0), daher reicht `+1`, um `log10(0)` zu vermeiden. `Landsize` hat + 1942 Nullwerte (~10,5 %) — ohne `+1` wären das ebenso viele `-inf`. + +## Offene Punkte für ein echtes Projekt (hier bewusst nicht gemacht) + +Über das Drehbuch hinausgehende Überlegungen, fürs nächste Dataset: + +- **Zyklische Datums-Features.** `month` und `day_of_week` sind linear codiert + (Dez→Jan sieht als 12→1 wie ein grosser Sprung aus). Sauberer wäre eine + sin/cos-Zerlegung. Für baum-basierte Modelle irrelevant, für lineare relevant. +- **NA-Anteil bei `BuildingArea` (58 %) und `YearBuilt` (51 %).** Median-Füllung + über die halbe Spalte drückt deren Varianz stark. Kandidat für „Variable + droppen" oder modellbasiertes Imputieren statt pauschalem Median. +- **`CouncilArea`-Faktorisierung erzeugt Schein-Ordnung.** 33 nominale Levels + als 0..32 codiert — ein lineares Modell liest daraus eine Rangordnung, die + nicht existiert. Bewusster Trade-off gegen 33 One-Hot-Spalten. Für Bäume + unproblematisch. +- **Produktionsform.** Für eine wiederverwendbare Pipeline (auch auf neuen + Daten) wäre `sklearn` `Pipeline` + `ColumnTransformer` die nächste Stufe — + löst zugleich das Persistenz-Problem der Imputations-/Skalierungswerte. diff --git a/SL/aufgaben/workshop3/src/prepare.py b/SL/aufgaben/workshop3/src/prepare.py index 270cfaa..a03f1c6 100644 --- a/SL/aufgaben/workshop3/src/prepare.py +++ b/SL/aufgaben/workshop3/src/prepare.py @@ -27,6 +27,7 @@ def inspect(df: pd.DataFrame) -> None: def e1_remove_observations(df: pd.DataFrame) -> pd.DataFrame: """E1: Ausreisser und fehlerhafte Beobachtungen entfernen.""" + df.info() before = len(df) df = df[df.Price < 8000000] # nur Werte bis 8000000 berücksichtigen df = df[ @@ -134,6 +135,7 @@ def e41_construct(df: pd.DataFrame) -> pd.DataFrame: def e42_clean_names(df: pd.DataFrame) -> pd.DataFrame: """E4.2: Unerlaubte Zeichen in Spaltennamen durch _ ersetzen.""" df.columns = df.columns.str.replace(r"[^a-zA-Z0-9_]", "_", regex=True) + df.info() return df if __name__ == "__main__":