EduFlow

Section 1 · What Machine Learning Is

Baselines: The Model to Beat

17 min3 min readPython exercise

Lesson at a glance

Build the two baselines every model must beat, a majority-class dummy and a one-line business rule, and judge a model by its lift over them instead of its raw score.

  1. 1The dumbest possible model
  2. 2The rule a person would write
  3. 3Baselines for regression
  4. 4Lift, not score

No video here — just the real thing: a written lesson, runnable examples, a quiz and practice.

Your depth-3 tree predicts churn with 73% accuracy on unseen customers. Before you show that number to anyone, ask the question every experienced data scientist asks first: compared to what? Without a comparison, 73% is neither good nor bad. It’s a number.

The dumbest possible model

The first baseline ignores the features entirely. For classification, it predicts the most common class for everyone. For regression, it predicts the average. scikit-learn ships both as real estimators, so they fit into the same code as any model.

Python
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.dummy import DummyClassifier

churn = pd.read_csv("churn.csv")
features = ["tenure_days", "orders", "total_spent", "avg_order_value",
            "days_since_last_order", "used_coupon", "support_tickets", "mobile_share"]
X_train, X_test, y_train, y_test = train_test_split(
    churn[features], churn["churned"], test_size=0.25, random_state=42,
    stratify=churn["churned"])

dummy = DummyClassifier(strategy="most_frequent").fit(X_train, y_train)
print("dummy predicts:", dummy.predict(X_test)[:5])
print("dummy accuracy:", dummy.score(X_test, y_test))

Predicting “churned” for every customer is right 55% of the time, because 55% of customers churned. That’s the floor. Your tree’s 73% is 18 points above it, which is real progress, but you’re not done asking.

The rule a person would write

The second baseline is the one that hurts. Before machine learning, someone at Cartwheel would have written a rule: “if the customer hasn’t ordered in N days, they’re churning”. You choose N the way you’d train a model: on the training data only.

Python
import pandas as pd
from sklearn.model_selection import train_test_split

churn = pd.read_csv("churn.csv")
features = ["tenure_days", "orders", "total_spent", "avg_order_value",
            "days_since_last_order", "used_coupon", "support_tickets", "mobile_share"]
X_train, X_test, y_train, y_test = train_test_split(
    churn[features], churn["churned"], test_size=0.25, random_state=42,
    stratify=churn["churned"])

def rule_accuracy(days, X, y):
    predicted = (X["days_since_last_order"] > days).astype(int)
    return (predicted == y).mean()

candidates = range(30, 301, 10)
best_days = max(candidates, key=lambda d: rule_accuracy(d, X_train, y_train))
print("best cut-off on training data:", best_days, "days")
print("rule accuracy on test data:", rule_accuracy(best_days, X_test, y_test))

The rule gets 72% on the test set. The tree gets 73%. On 100 test customers that’s a difference of one person.

Bar chart of test accuracy: majority-class dummy 55%, 80-day rule 72%, depth-3 tree 73%. Dummy 55% 80-day rule 72% Depth-3 tree 73% bar length = accuracy (0% at the axis, 100% at 610)
Accuracy on the same 100 test customers

That isn’t a failure. It’s the most useful thing you’ve learned so far. It tells you that most of the signal for churn lives in one column, recency, and that an eight-feature tree has so far added almost nothing on top of it. It also gives you a target: from here on, a model has to beat 72%, not 55%, to be worth deploying, explaining and monitoring.

Baselines for regression

The regression counterpart is DummyRegressor, which predicts the training mean for every row. Its score (R²) on test data is close to 0, because R² is defined as improvement over predicting the mean. You’ll use it in section 2.

Python
from sklearn.datasets import load_diabetes
from sklearn.dummy import DummyRegressor
from sklearn.model_selection import train_test_split

X, y = load_diabetes(return_X_y=True, as_frame=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=42)
dummy = DummyRegressor(strategy="mean").fit(X_train, y_train)
print("predicts", round(dummy.predict(X_test)[0], 1), "for everyone")
print("R² on test:", round(dummy.score(X_test, y_test), 3))

Lift, not score

From now on, report a model as lift over its baselines: “73% accuracy, against 55% for the majority class and 72% for the 80-day rule”. That one sentence tells a reader more than any single number, and it protects you from fooling yourself.

Keep the baselines in your code, next to the models, and rerun them whenever the data or the split changes. A baseline computed once on an old split and quoted forever is worse than none, because it looks like evidence. In practice the comparison table at the end of a notebook should have the dummy at the top, the best rule below it, and every model after that, all scored on exactly the same test rows.

Be honest about noise, too. With 100 test customers, one customer is one percentage point, so differences of two or three points can appear or vanish with a different shuffle. Section 4 replaces the single split with cross-validation, which tells you how much a score wobbles. Until then, treat small gaps as ties.

Accuracy is also not the only way to compare. A model that ranks customers by churn risk can hand marketing the 30 most likely churners, which a yes/no rule can’t do. Section 3 measures exactly that. First, section 2 opens up the simplest model of all, a straight line, to see what “learning” means in numbers.

Key takeaways

  • A score means nothing until you compare it with a baseline built on the same split.
  • DummyClassifier predicts the most common class and DummyRegressor predicts the mean; they set the floor.
  • A simple rule tuned on the training data is the baseline that keeps you honest about whether a model is worth its cost.
  • When one class dominates, accuracy rewards ignoring the rare class; a 95% score can mean the model found nothing.

Further reading

Check your understanding

4 questions

Question 1 of 4

A fraud model scores 97% accuracy. 3% of transactions are fraud. What should you check first?

Question 2 of 4

Your rule baseline uses a cut-off of 80 days. How should that 80 have been chosen?

Question 3 of 4

DummyRegressor() on the diabetes data scores an R² of about 0 on the test set. Why?

Question 4 of 4

A tree beats the majority-class dummy by 18 points but a one-line rule by 1 point. What is the most defensible next step?

Practice

Python exercise

Beat the baselines, or don't

Put a k-nearest-neighbours model next to its two baselines on the same split.

  1. dummy_acc: test accuracy of a DummyClassifier that predicts the most frequent class.
  2. best_days: the cut-off from range(30, 301, 10) that gives the best accuracy on the training data for the rule “churned if days_since_last_order > cut-off”. Then rule_acc: that rule’s accuracy on the test data.
  3. knn_acc: test accuracy of KNeighborsClassifier() with default settings.

Data files: churn.csv

import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.dummy import DummyClassifier
from sklearn.neighbors import KNeighborsClassifier

churn = pd.read_csv("churn.csv")
features = ["tenure_days", "orders", "total_spent", "avg_order_value",
            "days_since_last_order", "used_coupon", "support_tickets", "mobile_share"]
X_train, X_test, y_train, y_test = train_test_split(
    churn[features], churn["churned"], test_size=0.25, random_state=42,
    stratify=churn["churned"])

# 1. Majority-class baseline: test accuracy
dummy_acc = None

# 2. Rule baseline: churned if days_since_last_order > best_days.
#    Pick best_days from range(30, 301, 10) using the TRAINING data only.
best_days = None
rule_acc = None   # the rule's accuracy on the test data

# 3. A k-nearest-neighbours model (default settings): test accuracy
knn_acc = None
Loading the editor…

Ctrl/⌘ + Enter to run · Runs in a sandbox in your browser. Nothing leaves your device.

(no output)

Checks

  • dummy_acc is the majority-class test accuracy
  • best_days is the best cut-off on the training data
  • rule_acc is that rule’s test accuracy
  • knn_acc is the k-NN model’s test accuracy