Search the courses, subjects, tracks, games and features, or jump straight to a page.
Learn it your way
A short class with Sarah and her students. The other ways are under More.
Sign up free to listen. Transcripts are free to read.
Our dataset has ten students. Hours_Studied runs from 1 to 10, and Passed is 0 for the first five and 1 for the last five. What do you notice?
Everyone who studied 5 hours or less failed, and everyone with 6 or more passed.
Exactly. So where should a model switch from predicting Fail to predicting Pass?
Somewhere between 5 and 6 hours.
That switching point is the decision boundary. And because the answer is Pass or Fail, what kind of problem is this?
Binary classification, so we use logistic regression.
Suppose we just fit a straight line through the 0s and 1s. At 10 hours it predicts 1.18. Is that a sensible probability?
No, a probability can't be more than 1. And at 0 hours it would be negative.
Right. Logistic regression still computes a straight-line score, w times hours plus b, but then puts it through the sigmoid.
Which squeezes any number to between 0 and 1, so we can read it as the chance of passing.
Yes. Then we use a threshold: 0.5 or more means Pass, less means Fail.
So the output is a probability first and a class second.
We hold back the 2-hour and 9-hour students, train on the other eight, and get w of about 2.1 and b of about minus 11.4. Where does the probability reach 0.5?
At minus b divided by w, which is about 5.4 hours.
Good. Both held-out students are predicted correctly. Does that mean the model is perfect?
Not really. It's only two students, and the data has no tricky cases.
Exactly. Now a new student studied 4.5 hours. The model gives a probability of about 0.12. What do we predict?
Fail, because 0.12 is below the 0.5 threshold, and quite clearly.
Predict Pass (1) or Fail (0) from Hours_Studied, a binary classification problem.
Ten students; 1 to 5 hours all failed and 6 to 10 hours all passed, so the boundary lies between 5 and 6 hours.
A straight line gives values below 0 and above 1, while the sigmoid keeps every output between 0 and 1.
Inspect, split, train, predict, evaluate and visualise, which are the steps of lessons 7.5 to 7.9.
The probability reaches 0.5 at about 5.4 hours, both test students are correct, and 4.5 hours gives about 0.12, so Fail.
Two test students and perfectly tidy data make a weak test, and hours studied is only one factor.
Fitting a straight line to the 0 and 1 labels shows why we need logistic regression: its outputs escape the 0 to 1 range.
hours = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
passed = [0, 0, 0, 0, 0, 1, 1, 1, 1, 1]
n = len(hours)
mean_x = sum(hours) / n
mean_y = sum(passed) / n
slope = sum((x - mean_x) * (y - mean_y) for x, y in zip(hours, passed)) / sum((x - mean_x) ** 2 for x in hours)
intercept = mean_y - slope * mean_x
for h in [0, 1, 5.5, 10, 12]:
print(h, "hours -> straight-line output", round(slope * h + intercept, 3))With the trained weights, the probability of passing climbs along an S-shape: close to 0 for few hours, close to 1 for many, and changing fastest near the boundary.
import math
def sigmoid(z):
return 1 / (1 + math.exp(-z))
# Weights learned by the training code in the summary (rounded)
w, b = 2.108, -11.442
for h in [1, 2, 3, 4, 4.5, 5, 6, 7, 8, 9, 10]:
p = sigmoid(w * h + b)
print(str(h).rjust(4), "hours | P(pass) =", format(p, ".3f"), "|", "*" * int(p * 20))Counting the four confusion-matrix cells over all ten students gives 5 true positives, 5 true negatives and no errors, which is expected on such tidy data.
import math
def sigmoid(z):
return 1 / (1 + math.exp(-z))
w, b = 2.108, -11.442
hours = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
passed = [0, 0, 0, 0, 0, 1, 1, 1, 1, 1]
tp = tn = fp = fn = 0
for h, actual in zip(hours, passed):
predicted = 1 if sigmoid(w * h + b) >= 0.5 else 0
if predicted == 1 and actual == 1:
tp += 1
elif predicted == 0 and actual == 0:
tn += 1
elif predicted == 1 and actual == 0:
fp += 1
else:
fn += 1
print("TP:", tp, "TN:", tn, "FP:", fp, "FN:", fn)
print("Accuracy on all 10 students:", (tp + tn) / len(hours))Five hours or fewer, a fail is in store; the passes belong to the six-hours-or-more.
Ms Rao lined up ten students by hours studied. The first five had failed and the last five had passed, so she placed a ribbon between the fifth and sixth student. Logistic regression is her ribbon drawn as a smooth S-shaped slope instead of a hard line.
Score, Squash, Split: compute w × hours + b, squash it with the sigmoid, then split at 0.5.
P.A.S.S. - Probability from the sigmoid, Above 0.5 is pass, Split the data to test, Stay humble about tiny datasets.
Term
What are the feature and the label in this example?
Click to flip
Term
Where is the decision boundary in the data, and why?
Click to flip
Term
Why is a straight line a poor model for pass or fail?
Click to flip
Term
What does the trained model predict for a student who studied 4.5 hours?
Click to flip
Term
Which students does the chapter's 80/20 split hold back for testing?
Click to flip
A classification task with exactly two possible classes, here Pass (1) and Fail (0).
A classification algorithm that turns a straight-line score into a probability with the sigmoid function.
The S-shaped function 1 / (1 + e^(−z)) that maps any number into the range 0 to 1.
The probability cut-off, usually 0.5, that turns a predicted probability into a class.
The input value where the predicted probability equals the threshold; here, between 5 and 6 hours.
The examples the model learns from; here, 8 of the 10 students.
Examples held back to check the model on unseen data; here, the 2-hour and 9-hour students.
A training method that repeatedly nudges the weights in the direction that reduces the prediction errors.
This is the worked example the rest of the chapter builds on: predicting whether a student passes (1) or fails (0) from the hours they studied. Ten students switch cleanly from Fail to Pass between 5 and 6 hours, and logistic regression turns that pattern into a probability of passing.
The example has one feature, Hours_Studied (1 to 10), and one binary label, Passed: the five students who studied 1 to 5 hours failed and the five who studied 6 to 10 hours passed. Because the answer is a category, a straight line is the wrong tool; logistic regression passes a straight-line score through the sigmoid function to get a probability between 0 and 1, then applies a 0.5 threshold. This lesson walks the whole example end to end in plain Python: inspect the data, hold out a test set, train the model, check it on unseen students and predict for a student who studied 4.5 hours. It then explains what the result does and does not mean, ready for the step-by-step lessons that follow.
This is the example that the rest of the chapter, from visualising the data to drawing the logistic curve, keeps coming back to. A teacher wants to know: given how many hours a student studied, will they pass the exam? The answer is one of two categories, Pass (1) or Fail (0), so this is binary classification, the job logistic regression was built for.
The data. In the chapter's code (Step 2) the dataset is a small table with two columns: Hours_Studied, the feature, and Passed, the label.
A quick look in plain Python:
# The chapter's dataset: hours studied and whether the student passed (1) or not (0)
hours_studied = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
passed = [0, 0, 0, 0, 0, 1, 1, 1, 1, 1]
for h, p in zip(hours_studied, passed):
print(str(h).rjust(2), "hours |", "*" * h, "| passed" if p == 1 else "| failed")
fails = [h for h, p in zip(hours_studied, passed) if p == 0]
passes = [h for h, p in zip(hours_studied, passed) if p == 1]
print("Pass rate:", sum(passed) / len(passed))
print("Most hours among those who failed:", max(fails))
print("Fewest hours among those who passed:", min(passes))Two facts stand out. The classes are balanced (a pass rate of 0.5), and they are perfectly separated: everyone who studied 5 hours or less failed, and everyone who studied 6 hours or more passed. So the decision boundary, where predictions should switch from Fail to Pass, lies somewhere between 5 and 6 hours.
Why not a straight line? If you fit ordinary linear regression to the 0s and 1s, the line predicts −0.33 at 0 hours and 1.18 at 10 hours. Neither can be a probability. Logistic regression keeps the straight-line score z = w × hours + b but passes it through the sigmoid function, σ(z) = 1 / (1 + e^(−z)), which squeezes any number into the range 0 to 1. The result is read as the probability of passing, and a threshold of 0.5 turns it into a class.
The plan. The next lessons take these steps one at a time with pandas, scikit-learn and matplotlib:
train_test_split(X, y, test_size=0.2, random_state=42) holds back 2 of the 10 students: the ones who studied 9 hours and 2 hours.The whole example in plain Python. Here is the same pipeline without libraries. Training starts with w = 0 and b = 0, where every probability is 0.5. In each of 5,000 rounds the code compares each training student's predicted probability with their true 0 or 1, and nudges w and b a little in the direction that shrinks the errors. This nudging is called gradient descent.
import math
def sigmoid(z):
return 1 / (1 + math.exp(-z))
# Hold out the 2-hour and 9-hour students as a small test set
train_x = [1, 3, 4, 5, 6, 7, 8, 10]
train_y = [0, 0, 0, 0, 1, 1, 1, 1]
test_x = [9, 2]
test_y = [1, 0]
# Train: start at w = 0 and b = 0, then nudge them 5000 times
w, b = 0.0, 0.0
learning_rate = 0.1
for step in range(5000):
grad_w = 0.0
grad_b = 0.0
for x, y in zip(train_x, train_y):
error = sigmoid(w * x + b) - y
grad_w += error * x
grad_b += error
w -= learning_rate * grad_w / len(train_x)
b -= learning_rate * grad_b / len(train_x)
print("Learned w =", round(w, 3), "and b =", round(b, 3))
print("Probability is 0.5 at", round(-b / w, 2), "hours")
# Evaluate on the two held-out students
predictions = [1 if sigmoid(w * x + b) >= 0.5 else 0 for x in test_x]
print("Test predictions:", predictions, "| actual:", test_y)
correct = sum(1 for p, y in zip(predictions, test_y) if p == y)
print("Test accuracy:", correct / len(test_y))
# Predict for a new student
p = sigmoid(w * 4.5 + b)
print("4.5 hours -> probability of passing", round(p, 3), "->", "Pass" if p >= 0.5 else "Fail")The results. Training gives w ≈ 2.108 and b ≈ −11.442, so the probability crosses 0.5 at about 5.43 hours, between the last fail and the first pass. Both held-out students are classified correctly (the 9-hour student as Pass, the 2-hour student as Fail), a test accuracy of 1.0. The new student who studied 4.5 hours gets a probability of passing of about 0.124, so the prediction is Fail.
What it means.
LogisticRegression adds a small penalty by default that stops the weights growing, so its exact probabilities differ from ours. Its 0.5 point still lies between 5 and 6 hours, and it makes the same Pass and Fail predictions here.