357 lines
14 KiB
Markdown
357 lines
14 KiB
Markdown
# 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.<variable>.<funktion>()` 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
|