CSBootcampHSG

Lektion 09

Daten aufbereiten und visualisieren

DataFrames mit `pd.merge` zusammenführen (inner/left/right/outer), gruppierte Aggregationen mit `.agg()`, CSV-Dateien sauber laden und aus dem Ergebnis ein lesbares Plot bauen — Linien, Balken, Subplots und Seaborn-Box/Regplots.

Dauer:
60 Min. geschätzt
Voraussetzungen:
Pandas-Grundlagen (Lektion 08)
Zwei Tabellen werden über eine gemeinsame Spalte zusammengeführt; rechts entsteht aus dem Ergebnis ein Balken- und ein Liniendiagramm — die typische Pipeline aus Daten zusammenführen, gruppieren, plotten.

Theorie & Konzepte

Phase 1 / 4

In Lektion 08 hast du gelernt, einen einzelnen DataFrame zu filtern, gruppieren und sortieren. Diese Lektion geht zwei Schritte weiter: mehrere DataFrames zusammenführen (mit pd.merge) und das Ergebnis plotten (mit matplotlib und seaborn).

Damit hast du die komplette Data-Science-Pipeline der Vorlesung beisammen — von der CSV-Datei bis zum lesbaren Diagramm. Die Stolperfallen, die das Quiz abprüft, sind alle in den sechs Sektionen unten markiert.

Zwei DataFrames zusammenführen — `pd.merge`

Wenn du Bestellungen (ORDERS) mit Personen (PERSONS) verknüpfen willst, brauchst du pd.merge. Das ist der Pandas-Begriff für einen SQL-JOIN. Die wichtigsten Parameter:

pd.merge(
    ORDERS,                  # 1. linker DataFrame
    PERSONS,                 # 2. rechter DataFrame
    on='purchase-id',        # gemeinsame Spalte (in beiden vorhanden)
    how='inner',             # 'inner' | 'left' | 'right' | 'outer'
    suffixes=('_x', '_y'),   # bei Spaltenkollisionen
)

Das Ergebnis ist ein neuer DataFrame, in dem die beiden Eingaben zeilenweise verknüpft sind, wann immer on='purchase-id' denselben Wert hat. Was mit Zeilen passiert, die keinen Partner finden, hängt am how-Parameter:

  • how='inner' — nur Zeilen mit Match in beiden Tabellen. Default. Keine NaN.
  • how='left' — alle Zeilen aus der linken Tabelle, mit oder ohne Match. Felder ohne Match werden zu NaN.
  • how='right' — alle Zeilen aus der rechten Tabelle, mit oder ohne Match.
  • how='outer' — alle Zeilen aus beiden Tabellen.

Probier die vier `how`-Varianten aus

LR

LOANS (links)

keybook
ADune
B1984
CSolaris

MEMBERS (rechts)

keyname
AAnna
BBeat
DDora

Resultat (2 Zeilen)

keybookname
ADuneAnna
B1984Beat

match  left-only   right-only

pd.merge(LOANS, MEMBERS, on='key', how='inner')
Klick auf die Tabs oben — links siehst du, welche Zeilen aus LOANS und MEMERS jeweils im Resultat landen, und welche zu NaN werden.

Drei Arten, dasselbe Ziel zu erreichen

`pd.merge`, `df.merge` und `df.join` machen leicht unterschiedliche Dinge.

Klar und vorhersehbar

# Top-level Funktion — am ausdrücklichsten:
pd.merge(orders, persons, on='purchase-id', how='left')

# Methode auf dem linken DataFrame — gleiches Ergebnis:
orders.merge(persons, on='purchase-id', how='left')

Beide Varianten verwenden Spalten, du kontrollierst how und on explizit.

Falle: `df.join`

# join nutzt standardmäßig den INDEX, nicht eine Spalte:
orders.join(persons, how='left')

Falls purchase-id keine Index-Spalte ist, joint Pandas auf den Default-Integer-Index — und du merkst es erst am NaN-Müll im Ergebnis. Im Zweifel merge, nicht join.

Gruppieren + mehrere Aggregationen mit `.agg()`

Aus Lektion 08 weißt du: df.groupby("schluessel")["wert"].mean() gibt dir den Mittelwert pro Gruppe. Wenn du mehrere Aggregationen oder verschiedene pro Spalte willst, kommt .agg(...) ins Spiel:

IRIS.groupby("species").agg({
    "sepal_length": "mean",                # eine Aggregation
    "petal_length": ["min", "max"],        # mehrere → Multi-Index in den Spalten
    "petal_width":  "median",
    "sepal_width":  "std",
})

Das Ergebnis ist ein DataFrame mit drei Zeilen (eine pro Spezies) und fünf Spalten — eine pro Aggregation. Bei ["min", "max"] entsteht ein Multi-Index auf den Spalten: ("petal_length", "min") und ("petal_length", "max").

Die folgenden vier Schreibweisen sind funktional äquivalent — alle liefern dieselbe Series mit dem Minimum pro Land:

df.groupby("country")["customer_id"].min()
df.groupby("country")["customer_id"].agg(min)         # builtin function
df.groupby("country")["customer_id"].agg("min")       # named string
df.sort_values("customer_id").groupby("country")["customer_id"].first()

Die letzte Variante ist subtiler: weil sortiert ist, ist der erste Wert pro Gruppe automatisch der kleinste.

Aggregations-Builder — `.agg()` zusammenklicken

sepal_length

petal_length

petal_width

Resultat (3 Zeilen × 4 Spalten)— Multi-Index in den Spalten

speciessepal_lengthpetal_lengthpetal_width
meanminmaxmedian
setosa4.901.301.400.20
versicolor6.774.504.901.50
virginica6.405.106.002.10
IRIS.groupby('species').agg({
    'sepal_length': 'mean',
    'petal_length': ['min', 'max'],
    'petal_width': 'median'
})

Mehrere Aggregationen pro Spalte (z. B. ['min', 'max']) erzeugen einen Multi-Index in den Spalten — du adressierst dann mit Tupeln wie result[('petal_length', 'min')].

Klick die Aggregationen pro Spalte an. Sobald eine Spalte mehrere Aggregationen hat, entsteht ein Multi-Index in den Spalten — siehst du in der Tabelle und im generierten Code.

CSV-Dateien sauber laden — `pd.read_csv`

Das Lade-Pattern aus dem HSG-Notebook sieht meist so aus:

import pandas as pd

DF = pd.read_csv('orders.csv', index_col=0)

Die wichtigsten Parameter, die du im Kopf haben solltest:

ParameterWozu
sep / delimiterTrennzeichen. Default ,. Bei TSV: sep="\t". Bei CSV mit ;: sep=";".
headerWelche Zeile ist die Kopfzeile? Default 0. header=None wenn keine.
namesEigene Spaltennamen, falls header=None.
index_colWelche Spalte als Index nutzen. Oft 0, manchmal ein Spaltenname.
na_valuesWelche Strings als NaN interpretieren (z. B. "-", "NULL").
dtypeTypen pro Spalte erzwingen.

Wichtig: pd.read_csv liest jede Textdatei, egal ob .csv, .txt oder ohne Endung — solange das Trennzeichen passt. Die Dateiendung interessiert Pandas nicht.

`read_csv`-Sandkasten — der Separator macht's

sep =

data/people.txt (roh)

name;country;age
Anna;CH;28
Beat;DE;34
Cara;USA;41
Dora;CH;27

pd.read_csv('data/people.txt', sep=',')

name;country;age
Anna;CH;28
Beat;DE;34
Cara;USA;41
Dora;CH;27

⚑ Alles in einer Spalte. Pandas wirft keinen Fehler — du merkst es erst, wenn df['country'] einen KeyError wirft.

Die Datei heißt .txt, aber Pandas ist das egal — sie wird mit jedem Separator gelesen, den du angibst. Das echte Problem ist immer das Trennzeichen, nie die Endung.

Auf der linken Seite die rohe Textdatei (mit `;` getrennt). Toggle den `sep`-Parameter — du siehst sofort, ob Pandas die Spalten korrekt erkennt oder ob alles in einer einzigen Spalte landet.

Matplotlib — die zwei Bausteine `figure` und `axes`

matplotlib.pyplot (importiert als plt) ist die Standardbibliothek zum Plotten. Zwei Konzepte musst du auseinanderhalten:

  • Figure — das Gesamtbild (das Fenster, die Datei). Eine Figure kann mehrere Subplots enthalten.
  • Axes — ein einzelnes Diagramm mit eigenen Achsen, Titel, Labels. Nicht zu verwechseln mit axis (eine einzelne X- oder Y-Achse).

Das Standard-Idiom in den HSG-Aufgaben:

import matplotlib.pyplot as plt

fig, ax = plt.subplots(figsize=(8, 6))   # eine Figure, ein Axes
ax.plot(x, y, color='green', label='Apple')
ax.legend()
plt.show()

Du kannst ax.plot, ax.bar, ax.scatter, ax.boxplot direkt auf das Achsen-Objekt aufrufen. Niemals plt.plot und ax.plot mischen — die eine ist die alte funktionale API, die andere die objektorientierte.

Mehrere Diagramme mit `plt.subplots(rows, cols)`

Wenn du mehrere Diagramme nebeneinander willst, kriegst du sie alle aus einem Aufruf:

fig, axes = plt.subplots(nrows=3, ncols=1, figsize=(8, 6),
                          sharex=True, sharey=True)
  • nrows — Zeilen im Subplot-Gitter.
  • ncols — Spalten im Subplot-Gitter.
  • figsize — Gesamtgröße der Figure (Breite, Höhe in Inches).
  • sharex / sharey — Achsen synchronisieren (alle Subplots zeigen denselben Bereich).

Die Anzahl der erzeugten Achsen ist nrows × ncols — das ist die Frage im Quiz: Wie viele axes werden von folgendem Ausdruck erzeugt?

fig, axes = plt.subplots(ncols=3, nrows=2)
# axes ist ein 2x3 Numpy-Array → 6 Axes

fig, axes = plt.subplots(ncols=3)
# default nrows=1 → 3 Axes (1D Array)

fig, ax = plt.subplots()
# default 1x1 → 1 Axes (kein Array, ein einzelnes Objekt)

Subplots-Builder — `nrows` und `ncols` im Kopf rechnen

nrows
2
ncols
3
2 × 3 = 6 Axes
[0,0][0,1][0,2][1,0][1,1][1,2]
fig, axes = plt.subplots(2, 3)

axes ist ein 2D-Array, Form (2, 3)

Stell die Anzahl Zeilen und Spalten ein. Du siehst sofort, wie viele Axes entstehen, welche Form das `axes`-Objekt hat und wie der Aufruf konkret aussieht.

Balkendiagramme — Position und Breite zusammen denken

Ein einzelner Balken pro X-Wert ist einfach:

ax.bar(['Mo', 'Di', 'Mi'], [10, 5, 8], color='green')

Wenn du mehrere Balken pro Tag gruppieren willst (z. B. Apple, Banana, Cherry pro Wochentag), musst du die X-Positionen leicht versetzen. Die Bauanleitung:

import numpy as np

labels = fruits.columns
xticks = np.arange(len(labels))   # [0, 1, 2, 3, 4, 5, 6]
width  = 0.3

ax.bar(xticks - width, fruits.loc['Apple'],  width=width, color='green')
ax.bar(xticks,         fruits.loc['Banana'], width=width, color='blue')
ax.bar(xticks + width, fruits.loc['Cherry'], width=width, color='red')

ax.set_xticks(xticks)
ax.set_xticklabels(labels)

Wichtig: drei Balken in eine Lücke der Breite 1 zu quetschen, geht nur, wenn 3 × width ≤ 1. Bei width=0.3 füllst du 0.9 der Lücke — passt knapp. Bei width=0.5 würdest du 1.5 brauchen → Balken überlappen.

Bar-Width-Slider — wann überlappen die Balken?

3 × width = 0.90 → passt in Lücke 1
Tag 0Tag 1Tag 2Tag 3AppleBananaCherry
ax.bar(xticks - 0.30, fruits.loc['Apple'],  width=0.30)
ax.bar(xticks,         fruits.loc['Banana'], width=0.30)
ax.bar(xticks + 0.30, fruits.loc['Cherry'], width=0.30)
Schieb den Regler. Sobald `3 × width > 1` ist, schlagen die Balken in den nächsten Tag — das Badge wechselt von grün auf rot. Diese Faustregel beantwortet die Quiz-Frage.

Seaborn — schöne Plots in einer Zeile

Seaborn (importiert als sns) ist eine Schicht über Matplotlib für statistische Plots. Gibt dir Boxplots, Regressionsplots und Heatmaps in einer Zeile.

import seaborn as sns

sns.boxplot(data=IRIS.loc['setosa'])           # eine Box pro Feature
sns.regplot(x=IRIS['petal_length'],
            y=IRIS['petal_width'],
            order=3)                            # kubische Regression
sns.heatmap(IRIS.corr(), annot=True)            # Korrelationsmatrix

Seaborn-Funktionen geben ein Axes-Objekt zurück und akzeptieren ax=..., sodass du sie in dein Subplot-Gitter integrieren kannst:

fig, axes = plt.subplots(ncols=3, figsize=(16, 6), sharex=True, sharey=True)
for i, flower in enumerate(sorted(IRIS.index.unique())):
    sns.boxplot(data=IRIS.loc[flower], ax=axes[i])
    axes[i].set_title(flower.title())

Das ist genau das Muster aus Aufgabe 7.1.

Assignment-Walkthrough

Phase 2 / 4

Hier gehen wir typspezifisch durch die 7 Aufgabenformen von Assignment 09: was gefragt ist, wie man es angeht, und wo man sich typischerweise verrennt. Die konkreten Namen und Daten siehst du im HSG-Notebook — die Muster hier passen auf jede Variante.

Assignment-Aufgabe 1

Inner-Merge — nur Zeilen mit Match auf beiden Seiten

Was ist gefragt

Die erste Aufgabe gibt dir zwei DataFrames mit einer gemeinsamen Schlüsselspalte (z. B. purchase-id in ORDERS und PERSONS). Du sollst sie so verbinden, dass nur Zeilen mit einem Treffer in beiden Tabellen erhalten bleiben — das Resultat hat keine NaN-Werte.

Am Ende ist genau eine bestimmte Spaltenliste vorgegeben (z. B. products, purchase-id, name, birthdate). Die Reihenfolge ist meist die natürliche, die pd.merge sowieso liefert.

Strategie

Eine Zeile reicht — du musst dich nur für den richtigen how-Wert entscheiden:

  1. Beide Tabellen haben eine gemeinsame Spalte → on='spaltenname'.
  2. Du willst nur Zeilen mit Match → how='inner' (das ist auch der Default, lässt sich also auch weglassen).
  3. Spaltenreihenfolge stimmt automatisch — Pandas behält die linke Tabelle zuerst, hängt die rechte (ohne Schlüsselspalte) hinten dran.

Lösungsskelett

import pandas as pd

def meine_funktion():
    return pd.merge(
        ORDERS,
        PERSONS,
        on='purchase-id',
        how='inner',
    )

Typische Stolperstellen

  • DF.copy() falsch verstandenpd.merge gibt einen neuen DataFrame zurück; die Eingaben werden nicht mutiert. Du brauchst hier kein copy().
  • how weggelassen funktioniert (inner ist Default), aber die Tests prüfen oft len(result) — wenn du mit how='outer' arbeitest, kommen NaN-Zeilen rein und die Anzahl stimmt nicht.
  • Falsche Spalte für on — beide DataFrames müssen exakt diesen Spaltennamen haben. Bei Schreibfehler wirft Pandas einen KeyError. Nimm dir 5 Sekunden, um in beiden DataFrames df.columns zu prüfen.

Assignment-Aufgabe 2

Left-Merge — alle Zeilen aus der linken Tabelle behalten

Was ist gefragt

Aufgabe 2 ist quasi Aufgabe 1 mit einem anderen how-Wert. Du sollst alle Bestellungen behalten — auch die, deren purchase-id keinen Eintrag in PERSONS hat. In solchen Zeilen werden name und birthdate zu NaN.

Strategie

Identische Struktur wie Aufgabe 1 — nur how ändern:

  1. pd.merge(linker, rechter, on='spalte', how='left')how='left' bedeutet: alle Zeilen aus dem linken DataFrame bleiben erhalten.
  2. Felder, die im rechten DataFrame keinen Match finden, sind im Ergebnis NaN.
  3. Die Test-Zelle prüft typischerweise len(result) == len(linker_df) und result.isna().any().any() == True.

Lösungsskelett

def meine_funktion():
    return pd.merge(
        ORDERS,
        PERSONS,
        on='purchase-id',
        how='left',
    )

Typische Stolperstellen

  • how='right' — würde nur Zeilen aus PERSONS behalten und Bestellungen ohne Person verlieren. Falsche Richtung.
  • how='outer' — würde zusätzlich Personen ohne Bestellungen hinzunehmen — mehr Zeilen, als die Aufgabe verlangt.
  • how=left (ohne Anführungszeichen) — NameError, weil Python left als Variable interpretiert.

Assignment-Aufgabe 3

Right-Merge — alle Zeilen aus der rechten Tabelle

Was ist gefragt

Aufgabe 3 spiegelt Aufgabe 2: jetzt sollen alle Personen im Ergebnis bleiben, auch die, die keine Bestellung aufgegeben haben. Bei diesen Personen werden products und purchase-id-Felder zu NaN.

Strategie

Drittes Mal dasselbe Schema — diesmal mit how='right':

pd.merge(ORDERS, PERSONS, on='purchase-id', how='right')

Man könnte das auch durch ein Vertauschen der Argumente erreichen: pd.merge(PERSONS, ORDERS, on='purchase-id', how='left') — funktional identisch. Die HSG-Aufgabe gibt aber meist eine Spaltenreihenfolge vor, also folge dem Beispiel der Aufgabe.

Lösungsskelett

def meine_funktion():
    return pd.merge(
        ORDERS,
        PERSONS,
        on='purchase-id',
        how='right',
    )

Typische Stolperstellen

  • Spaltenreihenfolge — bei how='right' kommen die Spalten aus ORDERS zuerst, dann die aus PERSONS (ohne Schlüssel). Falls die Aufgabe eine andere Reihenfolge will, explizit auswählen: result[['col_a', 'col_b', 'col_c', 'col_d']].
  • Anzahl Zeilen ≠ len(PERSONS) — wenn eine Person mehrere Bestellungen hat, vervielfacht sich ihre Zeile im Right-Merge. Das ist normal und vom Test meistens erwartet.

Assignment-Aufgabe 4

Pro Zeile eine Anzahl aus einer Dict-Spalte berechnen

Was ist gefragt

Aufgabe 4 (oft als optional markiert) gibt dir eine Spalte, in der pro Zeile ein Python-Dict steht — z. B. {'apple': 2, 'book': 1, 'chair': 1}. Du sollst eine neue Spalte anhängen, die die Summe der Werte (also die Gesamtanzahl Items) pro Zeile enthält.

Wichtig: das Original darf nicht verändert werden, das Ergebnis ist ein neuer DataFrame.

Strategie

Drei Schritte:

  1. work = df.copy() — Original schützen.
  2. Auf der Dict-Spalte mit .apply(...) über alle Zeilen laufen. Pro Zeile bekommt die Lambda-Funktion das Dict als Argument; sum(d.values()) ist die Anzahl Items.
  3. Das Resultat als neue Spalte zuweisen: work['product_count'] = work['products'].apply(...).

Tipp: wenn die Dict-Spalte als String gespeichert ist (z. B. nach einem CSV-Import), brauchst du noch json.loads(...) davor — das ist der HSG-Hinweis auf JSON.

Lösungsskelett

def meine_funktion(df):
    work = df.copy()
    work['product_count'] = work['products'].apply(lambda d: sum(d.values()))
    return work

Typische Stolperstellen

  • In-place mutieren — wenn du df['product_count'] = ... schreibst, mutierst du den globalen ORDERS. Andere Testzellen scheitern dann scheinbar grundlos. Erst copy(), dann zuweisen.
  • .map() statt .apply() — bei einer Spalte mit Dicts funktioniert beides; idiomatisch ist .apply() für komplexere Operationen pro Zeile.
  • String statt Dict in der Spalte — wenn die Spalte ein CSV-Import ist, sind die Dicts oft nur Strings. Dann: work['products'].apply(json.loads).apply(lambda d: sum(d.values())) (zwei applys, oder einer kombiniert mit Lambda).

Assignment-Aufgabe 5

Pro Gruppe aggregieren — eine oder mehrere Statistiken

Was ist gefragt

Aufgabe 5 ist die klassische Spezies-Statistik auf dem IRIS-Datensatz. Zwei Varianten:

  • 5.1: Eine Aggregation (Mittelwert) über mehrere Spalten — Ergebnis: ein DataFrame mit species als Index und einer Zeile pro Spezies.
  • 5.2: Mehrere Aggregationen pro Spalte (z. B. min und max für petal_length) — Ergebnis hat einen Multi-Index in den Spalten.

Strategie

5.1 — eine Zeile reicht:

IRIS.groupby('species').mean()

5.2agg(...) mit einem Dict, wenn pro Spalte unterschiedliche Aggregationen gewünscht sind:

IRIS.groupby('species').agg({
    'sepal_length': 'mean',
    'petal_length': ['min', 'max'],   # zwei Aggregationen → Multi-Index
    'petal_width':  'median',
    'sepal_width':  'std',
})

Wichtig: das Ergebnis hat species als Index (nicht als Spalte) — falls der Test species als Spalte verlangt, hängst du .reset_index() an.

Lösungsskelett

def stats_5_1():
    return IRIS.groupby('species').mean()

def stats_5_2():
    return IRIS.groupby('species').agg({
        'sepal_length': 'mean',
        'petal_length': ['min', 'max'],
        'petal_width':  'median',
        'sepal_width':  'std',
    })

Typische Stolperstellen

  • .mean() auf nicht-numerischen Spalten — bevor pandas 2.0 mussten manchmal nicht-numerische Spalten ausgewählt werden. Mit aktuellen Pandas-Versionen ignoriert .mean() Object-Spalten standardmäßig. Falls eine Warnung kommt: nur numerische Spalten selektieren oder numeric_only=True.
  • Multi-Index in den Spalten ignoriert — das Test-Cell prüft oft list(result.columns) und erwartet Tupel wie ('petal_length', 'min'). Falls dein Resultat flache Strings hat, hast du wahrscheinlich agg(['min', 'max']) ohne Dict geschrieben — dann verschwindet der Multi-Index.
  • reset_index() zu früh aufgerufen — kostet dich den Index, den der Test aber als Index erwartet.

Assignment-Aufgabe 6

Plots erstellen — Linien, Balken, Subplots

Was ist gefragt

Aufgaben 6.x bauen drei verschiedene Plots auf einem kleinen fruits-DataFrame (Wochenkonsum von Apple, Banana, Cherry):

  • 6.1: Liniendiagramm — drei farbige Linien (grün/blau/rot), eine pro Frucht.
  • 6.2: Gruppiertes Balkendiagramm — drei Balken pro Wochentag, leicht versetzt.
  • 6.3: Drei vertikal gestapelte Subplots, einer pro Frucht, mit gemeinsamen Achsen.

Diese Aufgaben haben keine Test-Zelle — du vergleichst dein Resultat visuell mit dem vorgegebenen Bild.

Strategie

6.1 — eine Linie pro Frucht:

fig, ax = plt.subplots(figsize=(8, 6))
ax.plot(fruits.columns, fruits.loc['Apple'],  color='green', label='Apple')
ax.plot(fruits.columns, fruits.loc['Banana'], color='blue',  label='Banana')
ax.plot(fruits.columns, fruits.loc['Cherry'], color='red',   label='Cherry')
ax.legend()
plt.show()

6.2 — drei Balken pro Wochentag, versetzt um width:

import numpy as np
xticks = np.arange(len(fruits.columns))
w = 0.3
ax.bar(xticks - w, fruits.loc['Apple'],  width=w, color='green')
ax.bar(xticks,     fruits.loc['Banana'], width=w, color='blue')
ax.bar(xticks + w, fruits.loc['Cherry'], width=w, color='red')
ax.set_xticks(xticks)
ax.set_xticklabels(fruits.columns)

6.3 — drei Subplots, gestapelt, geteilte Achsen:

fig, (apple_ax, banana_ax, cherry_ax) = plt.subplots(
    nrows=3, ncols=1, figsize=(8, 6), sharex=True, sharey=True
)
apple_ax.bar(fruits.columns,  fruits.loc['Apple'],  color='green')
banana_ax.bar(fruits.columns, fruits.loc['Banana'], color='blue')
cherry_ax.bar(fruits.columns, fruits.loc['Cherry'], color='red')

Lösungsskelett

# 6.2 — gruppierte Balken
import numpy as np
fig, ax = plt.subplots(figsize=(8, 6))
xticks = np.arange(len(fruits.columns))
w = 0.3
ax.bar(xticks - w, fruits.loc['Apple'],  width=w, color='green',  label='Apple')
ax.bar(xticks,     fruits.loc['Banana'], width=w, color='blue',   label='Banana')
ax.bar(xticks + w, fruits.loc['Cherry'], width=w, color='red',    label='Cherry')
ax.set_xticks(xticks)
ax.set_xticklabels(fruits.columns)
ax.legend()
plt.show()

Typische Stolperstellen

  • width zu groß — bei drei Balken pro Tag muss 3 × width ≤ 1 gelten. Bei width=0.5 überlappen sich Balken benachbarter Tage. 0.3 ist sicher.
  • set_xticks vor set_xticklabels — diese Reihenfolge gilt; sonst ignoriert matplotlib die Labels.
  • plt.plot und ax.plot mischen — bleib bei ax.xxx, sobald du eine Figure mit plt.subplots() erzeugst. Sonst landen einige Elemente auf der falschen Achse.

Assignment-Aufgabe 7

Seaborn-Plots — Boxplot und Regplot

Was ist gefragt

Aufgaben 7.x verwenden den IRIS-Datensatz wieder, jetzt mit Seaborn (Importiert als sns):

  • 7.1: Pro Spezies einen Boxplot der vier Features (sepal/petal length/width). Drei Subplots nebeneinander, geteilte Achsen.
  • 7.2: Für jede Kombination aus zwei Features einen Regressionsplot (kubisch, order=3). Insgesamt sechs Plots untereinander gestapelt.

Strategie

7.1 — eine Schleife über die drei Spezies, einen Boxplot pro Subplot:

fig, axes = plt.subplots(ncols=3, figsize=(16, 6), sharex=True, sharey=True)
for i, flower in enumerate(sorted(IRIS.index.unique())):
    sns.boxplot(data=IRIS.loc[flower], ax=axes[i])
    axes[i].set_title(flower.title())
plt.show()

7.2itertools.combinations über die Spalten, dann pro Paar ein regplot:

from itertools import combinations
features = list(combinations(IRIS.columns, 2))   # 6 Paare bei 4 Spalten
fig, axes = plt.subplots(nrows=len(features), figsize=(12, 25))
for i, (feat_a, feat_b) in enumerate(features):
    sns.regplot(x=IRIS[feat_a], y=IRIS[feat_b], order=3, ax=axes[i])
plt.subplots_adjust(top=1)
plt.show()

Lösungsskelett

from itertools import combinations

# 7.2 — Regplots für alle Feature-Kombinationen
features = list(combinations(IRIS.columns, 2))

fig, axes = plt.subplots(nrows=len(features), figsize=(12, 25))
for i, (feat_a, feat_b) in enumerate(features):
    sns.regplot(x=IRIS[feat_a], y=IRIS[feat_b], order=3, ax=axes[i])

plt.subplots_adjust(top=1)
plt.show()

Typische Stolperstellen

  • itertools.combinations vs itertools.permutations — bei vier Spalten liefern combinations(cols, 2) sechs Paare (ungeordnet); permutations wären zwölf. Das Bild zeigt sechs Plots — also combinations.
  • Achsen verstecken — wenn die X-Achsen-Beschriftung der oberen Subplots vom Plot darunter verdeckt wird, hilft plt.subplots_adjust(top=1) oder figsize deutlich höher setzen.
  • ax=axes[i] vergessen — ohne ax-Argument zeichnet Seaborn auf eine neue Figure, nicht in dein Subplot-Gitter.

Aufgaben

Phase 3 / 4
Aufgabe

Inner-Merge — nur Zeilen mit Match

2 Punkte

Schreibe eine Funktion merge_books(loans, members), die zwei DataFrames über die Spalte member_id so zusammenführt, dass nur Zeilen mit einem Treffer in beiden Tabellen im Ergebnis landen.

  • loans hat die Spalten book, member_id, return_due.
  • members hat die Spalten member_id, name, tier.
  • Das Ergebnis muss exakt diese fünf Spalten haben (in dieser Reihenfolge): book, member_id, return_due, name, tier.
  • Es dürfen keine NaN-Werte im Ergebnis sein.

Du brauchst genau eine Zeile Pandas-Code im Funktionsrumpf — und musst den passenden how-Parameter wählen.

Hinweise

    Aufgabe

    Left-Merge — Treuer zur linken Tabelle

    2 Punkte

    Schreibe eine Funktion all_loans(loans, members), die alle Zeilen aus loans behält, auch wenn der zugehörige member_id nicht in members existiert. Dann sollen name und tier in dieser Zeile NaN sein.

    Spalten und Reihenfolge wie in Task 1: book, member_id, return_due, name, tier.

    Wieder genau eine Zeile Code.

    Hinweise

      Aufgabe

      Multi-Aggregat mit `.agg()`

      3 Punkte

      Schreibe eine Funktion stats_by_tier(df), die einen DataFrame zurückgibt, in dem pro tier (Bronze, Silber, Gold) folgende Statistiken pro Spalte berechnet werden:

      • borrowed: Mittelwert (mean) und Maximum (max)
      • overdue: Summe (sum)

      Der Eingabe-DataFrame df hat die Spalten tier, borrowed, overdue. Das Ergebnis hat tier als Index und drei Spalten — als Multi-Index auf der Spalten-Achse:

                       borrowed       overdue
                       mean    max    sum
      tier
      bronze           ...     ...    ...
      gold             ...     ...    ...
      silver           ...     ...    ...
      

      Sortiere den Index aufsteigend. Verändere den Eingabe-DataFrame nicht.

      Hinweise

        Aufgabe

        Spalte mit Anzahl pro Zeile hinzufügen

        3 Punkte

        Schreibe eine Funktion with_book_count(df), die einen neuen DataFrame zurückgibt, basierend auf df. Der DataFrame hat eine Spalte books, in der jeweils ein Python-Dict gespeichert ist (z. B. {"Dune": 1, "1984": 2}). Du sollst eine neue Spalte book_count anhängen, die die Summe der Werte im Dict pro Zeile enthält.

        • Das Original df darf nicht verändert werden.
        • Das Ergebnis enthält alle ursprünglichen Spalten von df plus book_count als letzte Spalte.
        • book_count ist ein int (oder Pandas-numerischer Typ).

        Hinweise

          Übungsquiz

          Phase 4 / 4

          Teste dein Verständnis in 15 Minuten mit 6 Frage(n), direkt an den HSG-Quizstolperfallen ausgerichtet.

          Quiz starten