# Notizen SL Lektion 2 > Thema: Datenverständnis, Explorative Datenanalyse, Feature Engineering > Datum: 21.05.2026 > Dozentin: Violeta Vogel ## Überwachtes Lernen vs. Unüberwachtes Lernen - beim Überwachten Lernen kennt das Modell die richtigen Antworten (Labels) und das Modell lernt diese vorherzusagen - beim unüberwachten Lernen gibt es keine Labels, das Modell sucht selber Strukturen und Muster in den Daten ## Datenverständnis 1. Sammeln der Daten - Beschaffen der in den Projektressourcen aufgeführten Daten - Ersten Datenerfassungsbericht erstellen (Datenkatalog bilden) 2. Daten beschreiben - Erkennen wie jedes einzelne Feature aussieht - In welchem Format sind die Daten? - Wie viele Daten habe ich, wie gross ist der Datensatz? 3. Daten erkunden - Wie sind die Daten verteilt? - Gibt es Beziehungen zwischen den Daten? - Müssen eventuell Bereinigungen oder Aggregationen gemacht werden? - Datenexplorationsbericht erstellen 4. Datenqualität prüfen - Sind die Daten vollständig? - Datenqualitätsbericht erstellen ## EDA: Ziele und Methoden - Mustererkennung - Datenbereinigung - Visualisierung - Hypothesengenerierung ## EDA: Explorative Datenanalyse - Anomalien - Ausreisser, Datenpunkte die stark von der Norm abweichen - Mögliche Anomalien - Ausreisser → einzelne Datenpunkte die signifikant von Rest abweichen - Kontextbezogene Anomalien → Daten die nur in einem bestimmten Kontext ungewöhnlich sind - Kollektive Anomalien → eine Gruppe von Datenpunkten die gemeinsam abweichen, auch wenn sie einzeln normal wirken - mögliche Anomalien nach Variablenart - nicht numerische Daten - fehlende Werte - Duplikate - Kategorien Variablen - hohe Kardinalität (viele eindeutige Werte) - nicht balancierte Daten - numerische Variablen - schiefe Verteilung - Ausreisser - Korrelationen - diskrete Werte mit geringer Kardinalität (wenig eindeutige Werte) ## Klassierung > Die Klassierung in der deskriptiven Statistik ordnet viele, unterschiedliche Rohdaten in wenige, überschaubare Klassen (Intervalle) ein. - Zweck: Reduzierung der Datenkomplexität (Muster und Trends erkennen) - Vorgehen: Festlegung von Klassengrenzen - Darstellung: Histogramme - Nachteile: Durch Gruppierungen geht die exakte Messgenauigkeit verloren, da Einzelwerte nicht mehr erkennbar sind # Aufbau eines Data Frames - Objekte / Beobachtungen sind in den Zeilen (rows) - Merkmale / Attribute sind in den Spalten (columns) angegeben - Spalten enthalten sprechende Namen, über welche sie angesprochen werden können - pro Spalte ist ein Datentyp festgelegt, unterschiedliche Spalten können aber unterschiedliche Typen aufweisen ## Skalenniveaus (Workshop 1) ### Nominal > Das ist eine reine Kategorisierung. Werte sind nur Labels ohne jede Ordnung. Du kannst sagen "Rot ≠ Blau", aber nicht "Rot > Blau". - Was geht: - Gleichheit prüfen (= oder ≠) - Was nicht geht: - Reihenfolge - Abstände - Rechnen - Beispiele: - Geschlecht - Postleitzahl - Häusertyp (h/u/t im Melbourne-Dataset) - Programmiersprache - MAC-Adresse - Sinnvolle Statistik: - Modus - Häufigkeiten - Chi-Quadrat - Mittelwert ist Unsinn ("durchschnittliche Postleitzahl"...) - Stolperfalle: - Wenn Kategorien als Zahlen codiert sind (z.B. Postcode = 3000), sieht's numerisch aus, ist aber nominal. Pandas wird's als int einlesen – die Klassifikation musst du selbst machen. ### Ordinal > Ordnung ohne definierte Abstände. Du kannst Werte in eine sinnvolle Reihenfolge bringen, aber die Abstände dazwischen sind nicht definiert oder nicht gleich. - Was geht: - Gleichheit + Reihenfolge (<, >) - Was nicht geht: - Abstände interpretieren - Rechnen - Beispiele: - Schulnoten (ist der Abstand zwischen 4 und 5 derselbe wie zwischen 5 und 6? Nicht wirklich) - Likert-Skalen ("stimme zu" bis "stimme nicht zu") - Militärränge - T-Shirt-Grössen (S/M/L/XL) - Bildungsabschluss - Sinnvolle Statistik: - Median - Quantile - Rangkorrelationen (Spearman) - Stolperfalle: - Likert-Skalen werden in der Praxis ständig wie metrische Daten behandelt (Mittelwert von "3.7 auf 5er-Skala") – formal falsch, aber pragmatisch verbreitet. Eine Dauerdebatte in der Sozialforschung. ### Metrisch > Echte Zahlen mit definierten Abständen - Was geht: - Alles bisherige - Abstände und Verhältnisse berechnen - Hier wird's manchmal weiter unterteilt: - **Intervall**: gleiche Abstände, aber kein echter Nullpunkt. Verhältnisse sind sinnlos. Beispiel: Temperatur in °C – 20°C ist nicht "doppelt so warm" wie 10°C, weil der Nullpunkt willkürlich gesetzt ist. Andere Beispiele: Kalenderjahre, IQ. - **Ratio (Verhältnis)**: gleiche Abstände plus echter Nullpunkt. Verhältnisse sind sinnvoll. Beispiel: Preis (0 € heisst tatsächlich "nichts"), Länge, Gewicht, Anzahl Zimmer. - Sinnvolle Statistik: - Mittelwert - Standardabweichung - Pearson-Korrelation - alle parametrischen Tests ### Ergebnisse Workshop 1 – Melbourne Housing Dataset | Nr. | Column | Dtype | nominal | ordinal | metrisch | |----:|--------|-------|:-------:|:-------:|:--------:| | 0 | Unnamed: 0 | int64 | | | x | | 1 | Suburb | object | x | | | | 2 | Address | object | x | | | | 3 | Rooms | int64 | | | x | | 4 | Type | object | x | | | | 5 | Price | float64 | | | x | | 6 | Method | object | x | | | | 7 | SellerG | object | x | | | | 8 | Date | object | | x | x | | 9 | Distance | float64 | | | x | | 10 | Postcode | float64 | x | | | | 11 | Bedroom2 | float64 | | | x | | 12 | Bathroom | float64 | | | x | | 13 | Car | float64 | | | x | | 14 | Landsize | float64 | | | x | | 15 | BuildingArea | float64 | | | x | | 16 | YearBuilt | float64 | | x | x | | 17 | CouncilArea | object | x | | | | 18 | Lattitude | float64 | | | x | | 19 | Longtitude | float64 | | | x | | 20 | Regionname | object | x | | | | 21 | Propertycount | float64 | | | x | > Anmerkung zu `Date` und `YearBuilt`: Beide sind als ordinal **und** metrisch markiert. Das ist je nach Lesart vertretbar — ein Datum/Jahr hat eine klare Reihenfolge (ordinal) und gleiche Abstände ohne echten Nullpunkt (Intervall-metrisch). `Unnamed: 0` ist formal ein Index/Label und für ML eigentlich wertlos, auch wenn es als int64 "metrisch" aussieht (vgl. Nominal-Stolperfalle). --- # EDA in der Praxis (pandas) ## Umgebung vorbereiten ```python import pandas as pd import numpy as np data = pd.read_csv('bank_data.csv', sep=';') # Indizes der kategorialen bzw. numerischen Variablen für späteren Gebrauch cat_vars = data.select_dtypes(include=['object']).columns num_vars = data.select_dtypes(exclude=['object']).columns ``` - Merksatz Dtypes: `float64`, `int64` → numerisch; `object` → nicht numerisch (kategorial) ## Übersicht über den Data Frame | Befehl | Zweck | |--------|-------| | `type(data)` | Objekttyp (`pandas.core.frame.DataFrame`) | | `data.shape` | Dimensionen `(rows, columns)` | | `data.info()` | Typ, Index, Dtypes, Non-Null-Count, Speicherbedarf | | `data.iloc[0:6, 0:6]` | erste paar rows/columns (Position-basiert) | | `data.head()` / `data.tail()` | erste / letzte rows | ## Missing Values (NAs) ```python data.isna().sum().sum() # Anzahl NAs insgesamt data.shape[0] - data.dropna().shape[0] # Anzahl rows mit mind. 1 NA s = data.isna().sum(); print(s[s > 0]) # NAs pro column ``` - Wichtig: Anzahl NAs insgesamt ≠ Anzahl betroffener rows (eine row kann mehrere NAs haben) ## Duplikate - Duplikate = Beobachtungen, die in **allen** Variablen denselben Wert aufweisen - `data.duplicated().sum()` → Anzahl - `.duplicated()` gibt eine boolesche Serie zurück → zum Anzeigen oder Entfernen nutzbar - ob Duplikate als Fehler gelten, ist eine **fachliche** Entscheidung ## Kategoriale Variablen – Kennzahlen - `data.education.describe()` liefert bei kategorialen Variablen: - `count`: Anzahl nicht-Missing Values - `unique`: Anzahl unterschiedliche Werte (Kategorien / Levels) - `top`: häufigster Wert (→ Modalwert) - `freq`: Häufigkeit des Modalwertes - `data[cat_vars].describe()` → für alle kategorialen Variablen auf einmal - `data.education.value_counts()` → Frequenztabelle, sortiert nach **abnehmender** Häufigkeit (Modalwert zuerst); NAs werden per Default nicht ausgewiesen - `data[cat_vars].nunique()` → Anzahl Kategorien je Variable - sehr hohe Werte (nahe `count`) deuten auf ID-/Label-Charakter → kein Mehrwert für ML, und bei nominaler Umcodierung drohen sehr viele Dummy-Variablen ### Zero / Low Variance - **Zero Variance**: alle Beobachtungen haben denselben Wert → kein Informationsgehalt, für ML wertlos - **Low Variance**: ein Wert dominiert die anderen stark → meist wenig nützlich, fachlich abzuklären - Konzept stammt aus der Analyse numerischer Variablen, lässt sich aber auch auf kategoriale anwenden ### Visualisierung kategorialer Variablen - Standard: Barchart — `data.education.value_counts().plot.bar()` (Kategorien nach abnehmender Frequenz) - horizontaler Barplot: gut bei vielen Kategorien - Pie Plot: in der Data Analytics eher unbeliebt (Flächen/Winkel schwer vergleichbar) ## Numerische Variablen – Kennzahlen - `data.age.describe()` liefert: | Kennzahl | Bedeutung | |----------|-----------| | `count` | Anzahl (nicht-NA) Werte | | `mean` | Mittelwert (arithmetisches Mittel) | | `std` | Standardabweichung | | `min` | Minimum (→ 0. Quartil) | | `25%` | trennt kleinste 25 % ab → 1. Quartil | | `50%` | Median → 2. Quartil | | `75%` | trennt grösste 25 % ab → 3. Quartil | | `max` | Maximum (→ 4. Quartil) | - `mean` und `std` heissen **parametrische** Kennzahlen (parametrisieren Normalverteilungsmodelle, werden auch zum Skalieren verwendet) - die übrigen (Quartile/Median) sind **nichtparametrische** Kennzahlen (vgl. Boxplot) - `describe()` gibt bei numerischen Variablen **keine** unique-Zahl aus → bei Bedarf `data[num_vars].nunique()` (sinnvoll v.a. bei Integer-Werten, um getarnte Kategorien zu finden) ### Stolperfalle Variablennamen - Zugriff `data..()` klappt nur, wenn der Name den Python-Namenskonventionen entspricht - bei Namen wie `nr.employed` (Punkt!) → traditioneller Zugriff über Spaltenindex nötig: ```python print(data.age.mean()) # ok # print(data.nr.employed.mean()) # error print(data['nr.employed'].mean()) # ok ``` ### Visualisierung numerischer Variablen - **Histogramm**: `data.age.plot.hist()` — Default 10 bins, via `bins=20` überschreibbar; zeigt Verteilungsform - **Densityplot (KDE)**: `data.age.plot.kde()` — Kernel Density Estimator: pro Beobachtung eine Normalverteilung der Fläche 1/n, additiv überlagert; mehr Detail als Histogramm, aber ressourcenhungrig - **Boxplot**: `data.age.plot.box(vert=False)` — zeigt nichtparametrische Kennzahlen + ausreisserverdächtige Werte ### Boxplot / Box-Whisker-Plot - Box begrenzt durch 1. und 3. Quartil; Median innerhalb der Box markiert - Whiskers ("Schnauzhaare") = Werte ausserhalb der Box bis zur Ausreissergrenze - **IQR** (Interquartile Range) = 3. Quartil − 1. Quartil - als **Ausreisser** markiert werden Werte: - unterhalb von `Q1 − 1.5 × IQR` - oberhalb von `Q3 + 1.5 × IQR` - gruppierte Boxplots zum Vergleich: `data.boxplot(column=['age'], by='education', vert=False)` ### Ausreisser prüfen - sortierte Ausgabe hilft einzuschätzen, ob Randwerte echte Extremwerte sind: ```python print(data.age.sort_values().head(10)) # kleinste print(data.age.sort_values(na_position="first").tail(10)) # grösste ``` - Beispiel `age`: kleinster Wert (17) kommt mehrfach vor → plausibel; grösster Wert (116) setzt sich deutlich ab und ist fachlich unglaubwürdig → Ausreisser-Verdacht ### Schiefe Verteilungen - Benennung (rechts-/linksschief) richtet sich nach der Position der **Extremwerte** (der "Schwanz") - rechtsschiefe Verteilungen sind in der Praxis häufig (z.B. Einkommen, hier `age` leicht, `duration` stark) - können oft mittels **Logarithmieren** in weniger schiefe Verteilungen transformiert werden ## Interaktionen zwischen Variablen ### Kategorial × kategorial - **Kreuztabelle** (Mehrweg-Frequenztabelle): `pd.crosstab(data['job'], data['education'])` - farblich formatieren: `ct.style.background_gradient(axis=None)` - **Heatmap** (seaborn): `sns.heatmap(ct, annot=True, fmt='d')` — erleichtert Interpretation - mit Chi-Quadrat liessen sich Zusammenhänge quantifizieren, Vergleiche sind aber schwierig > Toolwechsel: ab hier werden Visualisierungen mit **seaborn** (`sns`) gemacht — High-Level-Interface auf matplotlib, einfacher zu bedienen. matplotlib = mächtiges Low-Level-Interface mit steiler Lernkurve. > ```python > import matplotlib.pyplot as plt > import seaborn as sns > sns.set() > ``` ### Numerisch × numerisch – Korrelationskoeffizient - misst **Stärke und Richtung** des (linearen) Zusammenhangs zweier Variablen - `r` liegt zwischen −1 und +1: - +1 → perfekter positiver Zusammenhang (beide steigen; z.B. Körpergrösse/Gewicht) - 0 → kein linearer Zusammenhang (z.B. Schuhgrösse/Intelligenz) - −1 → perfekter negativer Zusammenhang (eine steigt, andere sinkt; z.B. Geschwindigkeit/Reisezeit) - Anwendung: Feature Selection (Zusammenhang mit Target), EDA (Muster/Beziehungen), Modellinterpretation - Achtung: misst nur **lineare** Zusammenhänge — Korrelation ≠ Kausalität ```python corr = data[['age', 'duration']].corr() ``` - Diagonale immer 1.0 (Identität); Matrix ist symmetrisch, da `cor(x,y) = cor(y,x)` (kommutativ) - **Korrelogramm** (Heatmap) für die ganze Matrix: ```python plt.figure(figsize=(7, 6)) ax = sns.heatmap(corr, annot=True, fmt='.2f', xticklabels=corr.columns, yticklabels=corr.columns, cmap=sns.diverging_palette(10, 220, as_cmap=True), vmin=-1, vmax=1) ``` - **Scatterplot** für zwei Variablen, optional Gruppen einfärben: ```python sns.scatterplot(x='age', y='duration', data=data, hue='y') ``` ### Maskieren starker Zusammenhänge - `.corr()` gibt selbst einen DataFrame → mit `.where()` lassen sich Werte maskieren, z.B. um in einem Korrelogramm nur stark korrelierte Paare zu zeigen: ```python mask = corr.where(abs(corr) >= 0.95) # danach mask in sns.heatmap(...) statt corr verwenden ``` - nützlich, um redundante (hoch korrelierte) Features zu identifizieren → Kandidaten zum Entfernen