Klassifikation
Kurz gesagt: Eine Klassifikation sagt eine Klasse voraus, zum Beispiel „kauft wieder“ oder nicht; die logistische Regression liefert eine Wahrscheinlichkeit, und der Schwellenwert tauscht Präzision gegen Trefferquote, je nachdem, welche Fehler mehr kosten.
Problem
„Kauft der Kunde wieder?“
Das Marketing will einen Rabattgutschein an die Kunden schicken, die sonst aufhören würden zu kaufen. Ein Gutschein für alle kostet viel, und die meisten Kunden kommen ohnehin wieder. Die Frage ist: Wem?
Gehen wir in der Zeit zurück. Stell dich auf den 1. Juli 2026: Das ist der Stichtag. Von jedem Kunden kennst du die Bestellungen bis zu diesem Datum. Wie viele, wie viele Tage her die letzte war, wie groß der durchschnittliche Warenkorb ist. Ob er wieder bestellt, erfährst du von Juli bis Ende Oktober:
Vor dem 1. Juli haben 1.279 Kunden etwas gekauft. Danach haben 72,6 % von ihnen wieder bestellt. Die Spalte lost ist 1 für die übrigen: Sie haben 4 Monate lang nichts bestellt. Für diese Leute ist der Gutschein.
Genauer, für Neugierige
Hier zählen wir jede Bestellung, auch die stornierten: Die Frage ist, ob der Kunde kommt, nicht wie viel Umsatz er bringt. basket ist der durchschnittliche Wert seines Warenkorbs. ~people.index.isin(...) ist die Maske „der Kunde fehlt nach dem Stichtag“: ~ kehrt True und False um.
Probier es aus
Wie viel ist geschenkt?
Lege in lost_share ab, welcher Anteil der Kunden verloren ist, und in baseline_acc, wie oft die Regel „alle kommen wieder“ ohne jedes Modell richtig liegt.
Lösung
import pandas as pd
orders = pd.read_csv('year_orders.csv', parse_dates=['ordered_at'])
items = pd.read_csv('year_items.csv')
items['value'] = items['quantity'] * items['unit_price']
orders['basket'] = orders['id'].map(items.groupby('order_id')['value'].sum())
cutoff = pd.Timestamp('2026-07-01')
before = orders[orders['ordered_at'] < cutoff]
after = orders[orders['ordered_at'] >= cutoff]
people = before.groupby('customer_id').agg(
orders=('id', 'size'), last=('ordered_at', 'max'), basket=('basket', 'mean'))
people['days_since'] = (cutoff - people['last']).dt.days
people['lost'] = (~people.index.isin(after['customer_id'])).astype(int)
lost_share = people['lost'].mean()
baseline_acc = (people['lost'] == 0).mean()
print(round(lost_share, 3), round(baseline_acc, 3))people['lost'].mean() ergibt 0,274: 27,4 % sind verloren. Die Regel „alle kommen wieder“ liegt genau bei ihnen falsch und bei den übrigen richtig: 1 − 0,274 = 0,726. Das ist die Baseline aus der Lektion über den Ablauf. Jedes Modell für diese Frage muss über 72,6 % liegen, sonst brauchst du es nicht.
Die Idee
Klassifikation: Die Antwort ist eine Klasse
Bei der Regression ist die Vorhersage eine Zahl: der Umsatz eines Tages. Hier ist die Antwort „ja“ oder „nein“: Ist der Kunde verloren? Eine solche Aufgabe heißt Klassifikation, und die möglichen Antworten sind Klassen. Der Weg ist derselbe: Merkmale X, Label y, Aufteilung, fit, predict.
Das erste Modell ist die logistische Regression (LogisticRegression). Der Name führt in die Irre: Sie klassifiziert. Wie die lineare Regression addiert sie die Merkmale mit Gewichten, drückt die Summe dann aber zu einer Wahrscheinlichkeit zwischen 0 und 1 zusammen. Dagegen stellen wir DummyClassifier, der immer die häufigste Klasse sagt:
Die Regel „alle kommen wieder“ liegt bei 72,5 % der Testkunden richtig. Das Modell liegt bei 72,2 % richtig: weniger als die Regel. Die Genauigkeit (der Anteil richtiger Antworten) täuscht hier. Wenn etwa jeder vierte Kunde verloren ist, stimmt „alle bleiben“ fast immer.
Genauer, für Neugierige
stratify=y teilt so auf, dass der Anteil der verlorenen Kunden im Training und im Test gleich ist: 27,5 %. Ohne das kann der Test bei ungleichen Klassen zufällig mehr oder weniger verlorene Kunden bekommen. Hier ist es fair, die Kunden zufällig aufzuteilen: Die Merkmale jedes Kunden stammen aus der Zeit vor dem Stichtag und das Label aus der Zeit danach. max_iter=1000 gibt dem Modell mehr Schritte, um die Gewichte zu finden: Die Merkmale haben unterschiedliche Größenordnungen (Bestellungen bis 77, Tage bis 240).
Die Idee
Die Konfusionsmatrix
Die Genauigkeit ist eine Zahl für alle Fehler. Aber die Fehler sind nicht gleich. Ein verpasster verlorener Kunde ist entgangenes Einkommen. Ein Gutschein für einen Kunden, der wiedergekommen wäre, ist ein verschenkter Rabatt. Die Tabelle, die sie getrennt zählt, heißt Konfusionsmatrix:
Die Zeilen sind die Wahrheit, die Spalten das, was das Modell sagt. Von 88 verlorenen Kunden zeigt das Modell auf 1. Die anderen 87 verpasst es. Deshalb ist es so genau wie die Regel: Auch es sagt fast immer „bleibt“.
Die Idee
Präzision und Trefferquote: Wer bekommt einen Gutschein
Zwei Zahlen beschreiben die Gutscheine besser als die Genauigkeit:
- Präzision: von den Kunden mit Gutschein, wie viele wirklich gegangen wären. Niedrig heißt viele verschenkte Rabatte;
- Trefferquote: von allen, die gegangen wären, wie viele einen Gutschein bekommen. Niedrig heißt viele verpasste Kunden.
Das Modell gibt 3 Gutscheine. Einer geht an einen verlorenen Kunden: Die Präzision ist 0,333. Von den 88 verlorenen erwischt es 1: Die Trefferquote ist 0,011. Ein solches Modell hilft dem Marketing nicht, egal wie genau es ist.
Die Idee
Die Wahrscheinlichkeit und der Schwellenwert
predict ist nicht alles, was das Modell weiß. predict_proba gibt für jede Klasse eine Wahrscheinlichkeit. Die Spalten folgen der Reihenfolge von model.classes_: die erste für 0 (bleibt), die zweite für 1 (verloren):
Die höchste Abwanderungswahrscheinlichkeit im Test ist 0,56. predict gibt nur dann einen Gutschein, wenn die Wahrscheinlichkeit über 0,5 liegt, und das sind 3 Kunden. Aber 0,5 ist kein Gesetz. Die Zahl, ab der du „ja“ sagst, heißt Schwellenwert, und du wählst sie. Bei einem Schwellenwert von 0,3 gibt es 160 Gutscheine:
Von 160 Gutscheinen gehen 63 an verlorene Kunden: Die Präzision ist 39,4 %. Von den 88 verlorenen erwischst du 63: Die Trefferquote ist 71,6 %. Die Genauigkeit sinkt, aber jetzt arbeitet das Modell. Zum Vergleich: 160 Gutscheine an zufällige Kunden hätten eine Präzision gleich dem Anteil der verlorenen, 27,5 %. Das Modell ordnet die Kunden besser als der Zufall, wenn auch weit von perfekt.
Genauer, für Neugierige
Ein anderes Modell zeigt ein ähnliches Bild. Ein Entscheidungsbaum mit Tiefe 3 ist ebenfalls genau so genau wie die Regel, und bei einem Schwellenwert von 0,3 hat er eine Präzision von 38,8 % und eine Trefferquote von 64,8 %:
Mit diesen drei Merkmalen ist nicht viel mehr drin. Willst du ein besseres Modell, such Merkmale, die mehr über den Kunden sagen: welche Produkte er kauft, wie regelmäßig er bestellt.
Probier es aus
Zähl selbst
Zähle beim Schwellenwert 0,4 fp (ein Gutschein für einen Kunden, der bleibt) und fn (ein verlorener Kunde ohne Gutschein), und berechne dann Präzision und Trefferquote aus tp, fp und fn, ohne sklearn.
Lösung
import pandas as pd
orders = pd.read_csv('year_orders.csv', parse_dates=['ordered_at'])
items = pd.read_csv('year_items.csv')
items['value'] = items['quantity'] * items['unit_price']
orders['basket'] = orders['id'].map(items.groupby('order_id')['value'].sum())
cutoff = pd.Timestamp('2026-07-01')
before = orders[orders['ordered_at'] < cutoff]
after = orders[orders['ordered_at'] >= cutoff]
people = before.groupby('customer_id').agg(
orders=('id', 'size'), last=('ordered_at', 'max'), basket=('basket', 'mean'))
people['days_since'] = (cutoff - people['last']).dt.days
people['lost'] = (~people.index.isin(after['customer_id'])).astype(int)
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
X = people[['orders', 'days_since', 'basket']]
y = people['lost']
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.25, random_state=42, stratify=y)
model = LogisticRegression(max_iter=1000).fit(X_train, y_train)
proba = model.predict_proba(X_test)[:, 1]
pred = (proba >= 0.4).astype(int)
truth = y_test.to_numpy()
tp = ((pred == 1) & (truth == 1)).sum()
fp = ((pred == 1) & (truth == 0)).sum()
fn = ((pred == 0) & (truth == 1)).sum()
precision = tp / (tp + fp)
recall = tp / (tp + fn)
print(tp, fp, fn, round(precision, 3), round(recall, 3))Beim Schwellenwert 0,4 ist tp = 37, fp = 44 und fn = 51. Präzision = 37 / (37 + 44) = 0,457: Du teilst durch alle mit Gutschein. Trefferquote = 37 / (37 + 51) = 0,42: Du teilst durch alle verlorenen Kunden. Vertauschst du sie, beantwortest du die andere Frage.
Die Idee
Der Schwellenwert tauscht das eine gegen das andere
Jeder Schwellenwert gibt andere Gutscheine. Hier sind alle Schwellenwerte von 0,1 bis 0,5:
Bei einem Schwellenwert von 0,1 gibt es 262 Gutscheine, und du erwischst 96,6 % der verlorenen Kunden, aber die Präzision ist 32,4 %: ein wenig über den 27,5 % zufälliger Gutscheine. Bei 0,4 gibt es 81 Gutscheine, und die Präzision ist 45,7 %, aber du verpasst mehr als die Hälfte der verlorenen Kunden. Bei 0,5 sind es nur 3 Gutscheine. Kein Schwellenwert ist für sich allein der beste. Die Wahl hängt von den Kosten ab: was ein Gutschein kostet und was ein verlorener Kunde kostet.
Übung
Die Gutscheine des Marketings
Das Marketing hat 80 Gutscheine für die Kunden im Test. Gib sie den 80 mit der höchsten Abwanderungswahrscheinlichkeit: proba.argsort() gibt die Positionen von der kleinsten zur größten Wahrscheinlichkeit. Lege in hits ab, wie viele von ihnen wirklich verloren sind, in precision ihren Anteil unter den 80 und in recall, welcher Anteil aller verlorenen Kunden im Test einen Gutschein bekommt.
Lösung
import pandas as pd
orders = pd.read_csv('year_orders.csv', parse_dates=['ordered_at'])
items = pd.read_csv('year_items.csv')
items['value'] = items['quantity'] * items['unit_price']
orders['basket'] = orders['id'].map(items.groupby('order_id')['value'].sum())
cutoff = pd.Timestamp('2026-07-01')
before = orders[orders['ordered_at'] < cutoff]
after = orders[orders['ordered_at'] >= cutoff]
people = before.groupby('customer_id').agg(
orders=('id', 'size'), last=('ordered_at', 'max'), basket=('basket', 'mean'))
people['days_since'] = (cutoff - people['last']).dt.days
people['lost'] = (~people.index.isin(after['customer_id'])).astype(int)
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
X = people[['orders', 'days_since', 'basket']]
y = people['lost']
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.25, random_state=42, stratify=y)
model = LogisticRegression(max_iter=1000).fit(X_train, y_train)
proba = model.predict_proba(X_test)[:, 1]
top = proba.argsort()[::-1][:80]
hits = y_test.to_numpy()[top].sum()
precision = hits / 80
recall = hits / y_test.sum()
print(hits, round(precision, 3), round(recall, 3))proba.argsort()[::-1][:80] gibt die Positionen der 80 mit der höchsten Wahrscheinlichkeit. Von ihnen sind 36 verloren: Präzision 0,45, Trefferquote 36 / 88 = 0,409. 80 zufällige Gutscheine hätten etwa 22 verlorene Kunden gefunden. Mit argsort() ohne Umkehrung gibst du die Gutscheine den sichersten Kunden, und mit der Spalte [:, 0] sortierst du nach der Wahrscheinlichkeit, dass der Kunde bleibt.
Das Marketing sagt: „Wir wollen mindestens 80 % der verlorenen Kunden erreichen.“ Finde in best den höchsten Schwellenwert von 0,05 bis 0,5, bei dem die Trefferquote mindestens 0,8 beträgt, und in best_precision die Präzision dort.
Lösung
import pandas as pd
orders = pd.read_csv('year_orders.csv', parse_dates=['ordered_at'])
items = pd.read_csv('year_items.csv')
items['value'] = items['quantity'] * items['unit_price']
orders['basket'] = orders['id'].map(items.groupby('order_id')['value'].sum())
cutoff = pd.Timestamp('2026-07-01')
before = orders[orders['ordered_at'] < cutoff]
after = orders[orders['ordered_at'] >= cutoff]
people = before.groupby('customer_id').agg(
orders=('id', 'size'), last=('ordered_at', 'max'), basket=('basket', 'mean'))
people['days_since'] = (cutoff - people['last']).dt.days
people['lost'] = (~people.index.isin(after['customer_id'])).astype(int)
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
X = people[['orders', 'days_since', 'basket']]
y = people['lost']
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.25, random_state=42, stratify=y)
model = LogisticRegression(max_iter=1000).fit(X_train, y_train)
proba = model.predict_proba(X_test)[:, 1]
import numpy as np
from sklearn.metrics import precision_score, recall_score
best = None
for t in np.arange(0.05, 0.51, 0.05).round(2):
pred = (proba >= t).astype(int)
if recall_score(y_test, pred) >= 0.8:
best = t
pred = (proba >= best).astype(int)
best_precision = precision_score(y_test, pred)
print(best, round(best_precision, 3))Die Trefferquote beträgt bis zu einem Schwellenwert von 0,25 mindestens 0,8. Es gibt 193 Gutscheine, die Trefferquote ist 86,4 % und die Präzision 39,4 %. Ein niedrigerer Schwellenwert erreicht ebenfalls 80 %, aber mit mehr unnötigen Gutscheinen. Deshalb suchst du den höchsten Schwellenwert, der das Ziel noch erfüllt.
Test
Teste dich
Verwandte Lektionen
Abschnitt „Verwandte Lektionen“- Baut auf: Datumswerte und Zeitreihen · Das erste Diagramm · Modelle: aus Beispielen lernen · Lineare Regression
- Geht weiter in: Datenleck · Kreuzvalidierung
- Aufgaben zum Thema: Modelle mit scikit-learn