Part II — Regression

Chapter 4 · Simple Linear Regression

The first real model of the course — and the one every later chapter leans on. One numeric feature, one continuous target, one straight line: how least squares finds that line, what the four LINE assumptions demand and how to check each one, what R² really measures, and how to read and report the fitted model like a professional — with every number computed by hand before Python touches the data.

📖 Chapter 4 of 33 ⏱ ~100 min read Mostly Core Stable 📝 Quiz · 30 MCQs

🎯 Learning Objectives

1. Regression: Predicting Numbers

Regression is supervised learning with a continuous target: predicting a salary, a price, a temperature, a demand level. The complement — predicting a category — is classification, and it takes over in Chapter 11. Simple linear regression (SLR) is regression in its purest form: exactly one numeric feature, and the assumption that the relationship with the target is a straight line.

That austerity is deliberate. Because SLR has only two parameters, every idea in this chapter — estimation, assumptions, fit, diagnostics — can be computed and seen. Multiple regression (Chapter 5) adds features, polynomial regression (Chapter 6) bends the line, but the vocabulary and the validity questions are all invented here.

1.1 The running problem: salary from experience

Salary_Data.csv holds 30 employees: years of experience (1.1 to 10.5) and annual salary (₹37,731 to ₹122,391). The business question: given a candidate's years of experience, what salary should we expect?

QuantityValueReading
Rows30Small — every split choice matters (Chapter 3)
YearsExperience1.1 – 10.5The single numeric feature x
Salary37,731 – 122,391Continuous target y — regression, not classification
Pearson r0.978Very strong positive linear association (Chapter 2)
💡
Pro Tip: r = 0.978 makes this the perfect teaching dataset — but never let a strong correlation substitute for the scatter plot. Chapter 2's Anscombe warning applies double before fitting a line. (Plot first; we will.)

2. The Model & the Least-Squares Line

The SLR model says: the mean of y at any x lies on a straight line.

ŷ  =  b0  +  b1x
  • ŷ ("y-hat") — the predicted value at a given x
  • b0 — the intercept: predicted y when x = 0
  • b1 — the slope: average change in y per one-unit increase in x

Real points do not sit on the line. The vertical gap between an observed value and its prediction is the residual:

ei  =  yi − ŷi   — observed minus predicted
x (years) y ŷ = b₀ + b₁x
Figure 4.1 — Five observations and the fitted line. The line is positioned so that the squared vertical gaps are as small as possible in total.

2.1 What "best fit" means — ordinary least squares

Infinitely many lines pass near these points. Ordinary least squares (OLS) picks the one minimising the sum of squared residuals:

minimize  Q(b0, b1)  =  Σ (yi − b0 − b1xi)²

Why squared? Squaring makes every residual positive (so misses in either direction count against the fit), penalises large misses disproportionately — one error of 4 costs more than two errors of 2 — and, crucially, produces a smooth, differentiable objective whose minimum can be found exactly, with calculus taught in any first course:

  1. Take the partial derivative of Q with respect to b₀ and set it to zero
  2. Take the partial derivative with respect to b₁ and set it to zero
  3. Solve the two equations — the "normal equations" — for the two unknowns

The solution — the least-squares estimates — is remarkably compact:

b1  =  Σ(xi − x̄)(yi − ȳ)  /  Σ(xi − x̄)²      b0  =  ȳ − b1x̄
Look closely at b₁'s numerator — it is Chapter 2's covariance sum, unscaled. And the denominator is the feature's spread. The slope is literally "how x and y co-vary, per unit of x's own variance." The second formula guarantees the line passes through the point (x̄, ȳ) — the centre of the data cloud.
e₁ e₂ e₃ e₄ fitted line residual = y − ŷ (signed: above line positive, below negative)
Figure 4.2 — Residual anatomy: each residual is the signed vertical gap between the observed point and the line's prediction at that x.
📝
Note: OLS minimises squared error vertically. It answers "best line for predicting y from x" — not the geometric "closest line to the cloud", and not a line usable in reverse to predict x from y. Direction matters in regression, always.

3. Worked by Hand: Five Employees

Before Python runs anything, derive the line yourself once. Five employees, years of experience x and salary y (in ₹ lakh per annum — small numbers by design):

xiyix−x̄y−ȳ(x−x̄)(y−ȳ)(x−x̄)²(y−ȳ)²
14−2−3649
25−1−2214
3700000
4811111
511248416
x̄ = 3ȳ = 700Σ = 17Σ = 10Σ = 30

Step 1 — slope:

b1  =  17 / 10  =  1.7 ₹ lakh per extra year

Step 2 — intercept (line must pass through (x̄, ȳ) = (3, 7)):

b0  =  7 − 1.7 × 3  =  1.9 ₹ lakh

Step 3 — predictions and residuals:

xyŷ = 1.9 + 1.7xe = y − ŷe²
143.6+0.40.16
255.3−0.30.09
377.00.00.00
488.7−0.70.49
51110.4+0.60.36
SSE = Σe²1.10

Step 4 — R² (derived properly in Section 5):

R²  =  1 − SSE/SST  =  1 − 1.10/30  ≈  0.963

You have just fitted a regression with pencil and paper: every additional year of experience is associated with +₹1.7 lakh of salary on average; the model explains 96.3% of the salary variation across these five employees. Nothing that follows — not sklearn, not a million rows — changes the idea, only the arithmetic.

💡
Pro Tip: Sanity-check any fitted slope with the rise-over-run glance: from x=1 (ŷ=3.6) to x=5 (ŷ=10.4), the line rises 6.8 over a run of 4 → 6.8/4 = 1.7. If your software ever disagrees with your glance, trust neither until you find the bug.

4. The Assumptions — LINE

OLS always returns a line — on any data, no questions asked. The assumptions decide whether that line means anything. The classic mnemonic LINE organises the four conditions that make OLS estimates trustworthy, predictions valid, and inference (p-values, Chapter 5) honest:

Assumption What it demands How to check If violated Standard remedy
L
Linearity
The mean of y follows a straight line in x Scatter plot of y vs x; residuals-vs-fitted must show no curve Systematically wrong predictions; slope misleading Transform x or y (log); polynomial terms (Ch 6)
I
Independence of errors
Residuals carry no information about each other Residuals vs observation order; Durbin–Watson ≈ 2 Understated uncertainty; overconfident conclusions Time-series methods; add missed structure (trend, seasonality)
N
Normality of residuals
Errors follow a normal distribution Histogram of residuals; Q–Q plot hugs the diagonal Confidence intervals and p-values unreliable (predictions still unbiased) Transform y (log); more data (CLT); robust methods
E
Equal variance
(homoscedasticity)
Residual spread is constant across all fitted values Residuals-vs-fitted: a flat band, no funnel Unstable slope estimate; wrong standard errors Log-transform y; weighted least squares

The workhorse check is a single picture: residuals on the y-axis against fitted values on the x-axis. A healthy model shows a formless horizontal band around zero. Every systematic shape is the residuals trying to tell you which assumption is bleeding:

Healthy: flat band Funnel: unequal variance Curve: non-linearity Q–Q hugs diagonal: normal Left three panels: residuals (vertical) vs fitted values (horizontal). Right panel: Q–Q plot of residuals. A healthy model shows a formless horizontal band — pattern of any kind is a message.
Figure 4.3 — Reading the residual plot: four patterns, four diagnoses.
⚠
Warning: The assumptions concern the residuals, not the raw data. A wildly non-normal y is fine if the residuals around the line are normal; a curved y–x relationship is the linearity assumption's business, judged from the scatter and the residual plot. Beginners check the wrong object — always diagnose what remains after fitting.
🌎
Real World: Salary data is the classic heteroscedasticity case: pay spreads widen with seniority — junior salaries cluster tightly, senior salaries fan out. When you run the diagnostics in Section 7 on Salary_Data.csv, watch for exactly that funnel, and note that the standard remedy (log-salary) is what compensation analysts routinely use.

5. R² — the Variance Ledger

How good is the line? The elegant answer decomposes the target's total variation into explained and unexplained parts. Define the total sum of squares and its two children:

SST  =  Σ(yi − ȳ)²   (total variation — what a mean-only model would miss)
SSE  =  Σ(yi − ŷi)²   (unexplained — what the line still misses)
SSR  =  Σ(ŷi − ȳ)²   (explained — what the line removes from SST)
For OLS these exactly balance: SST = SSE + SSR — every unit of variation is either explained or it is not.

The coefficient of determination is the explained share:

R²  =  1 − SSE/SST  =  SSR/SST
R² = 0.963 (the hand example): the line erases 96.3% of the variation a flat mean would have missed. In SLR there is a bonus identity: R² = r² — square Chapter 2's correlation and you have R² (0.978² ≈ 0.957 on the full dataset). One line, two vocabularies.

5.1 What counts as a good R²?

DomainTypical "good" R²Why
Physical / lab processes0.99+Governed by precise laws, little noise
Engineering / process control0.8 – 0.99Known dynamics, contained noise
Economics, salary, demand data0.5 – 0.9Human behaviour adds irreducible noise
Psychology, social surveys0.1 – 0.5Enormous individual variation

The honest answer is domain-relative: a 0.4 can be a publishing event in one field and a rounding error in another. Judge R² against what is explainable in principle, not against 1.0.

5.2 The three R² traps

📋 Stable content — Reviewed: August 2026

6. Implementation in Python

Now the full walkthrough, faithful to the course source and modernised for current sklearn. Only the pipeline elements Chapter 3 taught — split, fit, predict — appear, because a one-feature OLS needs neither imputation, encoding, nor scaling (more on that below).

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

# 1. Load; separate feature and target
dataset = pd.read_csv('Salary_Data.csv')
X = dataset.iloc[:, :-1].values      # YearsExperience, shape (30, 1)
y = dataset.iloc[:, -1].values       # Salary, shape (30,)

# 2. Split: 1/3 held out for testing (source convention for this dataset)
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=1/3, random_state=0)      # 20 train / 10 test

# 3. Fit OLS
from sklearn.linear_model import LinearRegression
regressor = LinearRegression()
regressor.fit(X_train, y_train)

print('Intercept b0 :', round(regressor.intercept_, 2))
print('Slope     b1 :', round(regressor.coef_[0], 2))
print('Train R2     :', round(regressor.score(X_train, y_train), 4))

# 4. Predict on the unseen test rows
y_pred = regressor.predict(X_test)

from sklearn.metrics import r2_score, mean_squared_error
print('Test  R2     :', round(r2_score(y_test, y_pred), 4))
print('Test  RMSE   :', round(np.sqrt(mean_squared_error(y_test, y_pred)), 2))

Listing 4.1 — The complete SLR workflow (source: simple_linear_regression.py).

Running this prints (verify it — Chapter 3's ritual):

Intercept b0 : 26816.19
Slope     b1 : 9345.94
Train R2     : 0.9382
Test  R2     : 0.9749
Test  RMSE   : 4585.42

Output 4.1 — The fitted model on the 20 training rows, graded on 10 unseen rows.

6.1 Why no scaling in this chapter?

Chapter 3's table said trees are scale-free but distance/gradient methods need scaling. OLS sits in a third category: sklearn's LinearRegression solves the normal equations exactly (no gradient descent, no distances), so scaling changes only the units in which coefficients are expressed — not the fit, not the predictions. Scaling features before OLS is a legitimate convention when coefficients must be comparable (Chapter 5); here it would only bury the salary-per-year reading we want.

6.2 Visualising the fit

# Training set: points + fitted line
plt.scatter(X_train, y_train, color='red', label='Train (actual)')
plt.plot(X_train, regressor.predict(X_train), color='blue', label='Fitted line')
plt.title('Salary vs Experience (Training set)')
plt.xlabel('Years of Experience'); plt.ylabel('Salary')
plt.legend(); plt.show()

# Test set: the same line, judged on unseen points
plt.scatter(X_test, y_test, color='red', label='Test (actual)')
plt.plot(X_train, regressor.predict(X_train), color='blue', label='Fitted line')
plt.title('Salary vs Experience (Test set)')
plt.xlabel('Years of Experience'); plt.ylabel('Salary')
plt.legend(); plt.show()

Listing 4.2 — The two source plots: the line learned on train, judged on test.

6.3 Predicting a new observation

# One candidate, 6.5 years of experience
single = regressor.predict([[6.5]])
print(f'Predicted salary: ₹{single[0]:,.0f}')     # ₹87,565

Listing 4.3 — New predictions take a 2-D input: [[6.5]], one row, one feature.

7. Reading & Reporting the Model

7.1 Translating the coefficients

With b₀ = 26,816 and b₁ = 9,346:

⚠
Warning — extrapolation: Asked about a 12-year veteran, the model cheerfully returns ₹138,968 — but the data ends at 10.5 years. Beyond the observed range the linear shape is an assumption, not a finding; senior salaries typically flatten, and the model cannot know that. Predict within the data's span; beyond it, label the answer as extrapolation and widen its error bars.

7.2 The diagnostic pass — assumptions on real data

residuals = y_train - regressor.predict(X_train)

# L & E: residuals vs fitted — want a flat, formless band
plt.scatter(regressor.predict(X_train), residuals)
plt.axhline(0, color='grey', linestyle='--')
plt.xlabel('Fitted salary'); plt.ylabel('Residual')
plt.title('Residuals vs Fitted'); plt.show()

# N: Q-Q plot — want points hugging the diagonal
from scipy import stats
stats.probplot(residuals, plot=plt)
plt.title('Q-Q plot of residuals'); plt.show()

Listing 4.4 — The two-plot diagnostic pass from Section 4, on the training residuals.

On this dataset the band stays reasonably flat with mild widening at the right (senior salaries spread more — Section 4's Real World box, materialised), and the Q–Q points track the diagonal with light tail wobble. Verdict: assumption concerns are minor here, log-salary would be the professional refinement, and the honest report says so.

7.3 The one-paragraph report

💡
Template: "We modelled salary as a linear function of years of experience on 20 employees (b₀ = ₹26,816; b₁ = ₹9,346/year; 95% of variation explained on held-out data, R² = 0.975; typical miss ≈ ₹4,585, RMSE). Residual diagnostics support linearity with mild heteroscedasticity at senior levels; predictions are valid for 1–10.5 years of experience — beyond that they are extrapolation." — coefficients, fit, validity, caveats, in four sentences. Reuse this skeleton for every regression you ever report.

8. Strengths, Limitations & the Road Ahead

StrengthsLimitations
Transparent: slope and intercept mean something in business language One feature only — real problems need more (Chapter 5)
Exact solution, no tuning knobs, fast on any size Straight lines only — curvature needs transforms or polynomials (Chapter 6)
Statistical foundation: assumptions, inference, confidence intervals Sensitive to outliers — a single extreme point moves the line
A natural baseline every fancier model must beat Extrapolation beyond the observed x-range is assumption, not evidence

Keep SLR as your permanent first model — the two-minute baseline that contextualises everything later. The next three chapters grow it: Chapter 5 adds features (and the multicollinearity, dummy-trap and feature-selection questions that come with them), Chapter 6 bends the line and formally introduces the bias-variance tradeoff, and Chapters 7–9 swap the line for more flexible learners — each judged against this chapter's transparent yardstick.

🛠 Hands-On: Fit, Diagnose, ReportBeginner

A hiring manager asks: "What should we offer a candidate with 6.5 years of experience?" Fit SLR on Salary_Data.csv, run the full diagnostic pass, and answer in one professional paragraph — numbers, validity and caveats included.

Steps

  1. Plot salary vs experience first (Anscombe's law). Does a line look defensible?
  2. Split 1/3 test with random_state=0; fit LinearRegression.
  3. Report b₀, b₁ in rupees, R² on train and test, and RMSE on test.
  4. Predict for 6.5 years — and for 12 years. Which answer deserves a caveat, and why?
  5. Run the residual-vs-fitted and Q–Q plots. Verdict on L, I, N, E?
  6. Write the one-paragraph report using Section 7.3's template.
View Solution / Walkthrough
import numpy as np, pandas as pd
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.metrics import r2_score, mean_squared_error
from scipy import stats

ds = pd.read_csv('Salary_Data.csv')
X, y = ds.iloc[:, :-1].values, ds.iloc[:, -1].values

plt.scatter(X, y); plt.title('Salary vs Experience'); plt.show()   # Step 1

X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=1/3, random_state=0)
r = LinearRegression().fit(X_tr, y_tr)
y_pred = r.predict(X_te)

print('b0 = %.0f   b1 = %.0f' % (r.intercept_, r.coef_[0]))
print('R2 train %.4f | R2 test %.4f | RMSE %.0f' %
      (r.score(X_tr, y_tr), r2_score(y_te, y_pred),
       np.sqrt(mean_squared_error(y_te, y_pred))))
print(f"6.5 yrs: ₹{r.predict([[6.5]])[0]:,.0f}   12 yrs: ₹{r.predict([[12.0]])[0]:,.0f}")

resid = y_tr - r.predict(X_tr)
plt.scatter(r.predict(X_tr), resid); plt.axhline(0, ls='--', c='grey'); plt.show()
stats.probplot(resid, plot=plt); plt.show()

Findings to weave into your paragraph:

  • Step 1: the scatter climbs steadily with no curve or gap — a straight line is defensible; r = 0.978 agrees.
  • Step 3: b₀ ≈ ₹26,816, b₁ ≈ ₹9,346/year; R²_train ≈ 0.938, R²_test ≈ 0.975 (test exceeding train on 10 rows = small-sample luck, not magic), RMSE ≈ ₹4,585.
  • Step 4: 6.5 years → ≈ ₹87,565 (interpolation, trustworthy with a ±₹4.6k typical-miss caveat); 12 years → ≈ ₹138,968 (extrapolation beyond 10.5 — flag it).
  • Step 5: flat-ish residual band with mild right-side widening (E: borderline), Q–Q close to the diagonal (N: satisfied), no curve (L: satisfied), rows are independent employees (I: fine).
  • Step 6: your paragraph should mirror Section 7.3's template — coefficients, fit, validity, caveats. If it reads like that, you have completed this chapter's promise.

🎯 Key Takeaways

1

SLR predicts a continuous target from one feature with ŷ = b₀ + b₁x — the slope is "how x and y co-vary, per unit of x's variance."

2

OLS finds the line that minimises squared residuals; the solution is exact: b₁ = Σ(x−x̄)(y−ȳ)/Σ(x−x̄)², b₀ = ȳ − b₁x̄, and the line passes through (x̄, ȳ).

3

LINE: Linearity, Independence, Normality, Equal variance — they apply to the residuals, checked chiefly with the residual-vs-fitted plot.

4

R² splits the variance ledger (SST = SSE + SSR): the explained share. High R² never certifies validity — patterns hide under good numbers.

5

Report coefficients in business units, fit on held-out data, and mark out-of-range predictions as extrapolation — the four-sentence template.

6

SLR is your permanent baseline: transparent, exact, assumption-checkable. Every fancier model in Part II must earn its place against this line.

Test Your Understanding

30 questions across the whole chapter — model, estimation, hand computation, assumptions, R², implementation and interpretation. Choose one answer per question, then check your answers.

Q1.Simple linear regression is designed for:

Q2.In ŷ = b₀ + b₁x, the slope b₁ represents:

Q3.The residual of an observation is:

Q4.Ordinary least squares chooses b₀ and b₁ to minimise:

Q5.Squaring residuals (rather than taking absolute values) is preferred because squaring:

Q6.The numerator of the OLS slope formula is:

Q7.The OLS intercept is computed as:

Q8.(Hand example) With Σ(x−x̄)(y−ȳ) = 17 and Σ(x−x̄)² = 10, the slope is:

Q9.(Hand example) With x̄ = 3, ȳ = 7 and b₁ = 1.7, the intercept b₀ is:

Q10.In the LINE mnemonic, the "E" stands for:

Q11.The first check of the Linearity assumption is:

Q12.A funnel shape in the residuals-vs-fitted plot signals:

Q13.A Q–Q plot is used to check:

Q14.A Durbin–Watson statistic near 2.0 suggests:

Q15.Violation of the Independence assumption is most typical when data are:

Q16.R² is computed from the sums of squares as:

Q17.An R² of 0.96 means:

Q18.(Hand example) With SST = 30 and SSE = 1.10, R² equals:

Q19.A very high R² guarantees that:

Q20.Adding another predictor to a regression model can never decrease the training R² because:

Q21.This chapter's SLR code performs no feature scaling because:

Q22.With b₁ ≈ 9,346 on salary data, the correct business sentence is:

Q23.The intercept b₀ ≈ 26,816 should be interpreted cautiously because:

Q24.Predicting salary at 12 years when the data spans 1.1–10.5 years is:

Q25.After fitting, regressor.coef_ returns:

Q26.regressor.predict([[6.5]]) passes a list-of-lists because:

Q27.Before fitting any regression, the professional first move is to:

Q28.The residuals-vs-fitted plot shows a clear U-shaped curve. The standard next move is:

Q29.The variance decomposition behind R² states:

Q30.The natural extension of this chapter — many features, same least-squares core — arrives in: