Classification with Logistic Regression
From Numbers to Categories
Classification predicts a label: spam/ham, churn/stay, fraud/clean. Despite its name, logistic regression is a classifier — it’s linear regression pushed through a squashing function (the sigmoid) so the output lands between 0 and 1 and can be read as a probability:
z = w · x + b # any real number
p = 1 / (1 + e^(-z)) # squashed into (0, 1)If p = 0.93, the model says “93% confident this is spam”.
A Working Spam Classifier
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(
emails, labels, test_size=0.2, random_state=42, stratify=labels
)
vec = TfidfVectorizer(stop_words="english")
X_train_t = vec.fit_transform(X_train) # learn vocabulary on TRAIN only
X_test_t = vec.transform(X_test) # apply it to test
clf = LogisticRegression(max_iter=1000)
clf.fit(X_train_t, y_train)
clf.score(X_test_t, y_test) # accuracyTwo details that separate working code from broken code:
stratify=labelskeeps the class ratio identical in both splits.fit_transformon train, plaintransformon test — fitting the vectoriser on test data is data leakage and inflates your score dishonestly.
Probabilities and Thresholds
clf.predict(X_test_t) # hard labels, threshold 0.5
clf.predict_proba(X_test_t) # probabilities per classThe 0.5 threshold is a default, not a law. Blocking emails when p(spam) > 0.5 is aggressive; a bank flagging fraud might act at 0.2 because missing fraud costs more than a false alarm. The threshold encodes a business decision, and choosing it belongs to you, not the library.
Multi-Class Works Out of the Box
clf = LogisticRegression(max_iter=1000)
clf.fit(X_train, y_train) # y can have 10 classes — no code changescikit-learn handles multi-class via softmax automatically. The mental model stays identical: linear score per class, squashed into probabilities that sum to 1.