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.
🎯 Learning Objectives
- State the simple linear regression model and explain what each term contributes
- Derive the least-squares estimates of slope and intercept — and compute both by hand on a small dataset
- Name the four LINE assumptions, explain why each exists, check each with the right plot or statistic, and list the standard remedies
- Compute and interpret R² from the SST = SSE + SSR decomposition, and resist the classic R² traps
- Implement, visualise and interrogate the model in scikit-learn on Salary_Data.csv
- Report a fitted regression in the one-paragraph format professionals use — coefficients, fit, validity, caveats
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?
| Quantity | Value | Reading |
|---|---|---|
| Rows | 30 | Small — every split choice matters (Chapter 3) |
| YearsExperience | 1.1 – 10.5 | The single numeric feature x |
| Salary | 37,731 – 122,391 | Continuous target y — regression, not classification |
| Pearson r | 0.978 | Very strong positive linear association (Chapter 2) |
2. The Model & the Least-Squares Line
The SLR model says: the mean of y at any x lies on a straight line.
- ŷ ("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:
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:
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:
- Take the partial derivative of Q with respect to b₀ and set it to zero
- Take the partial derivative with respect to b₁ and set it to zero
- Solve the two equations — the "normal equations" — for the two unknowns
The solution — the least-squares estimates — is remarkably compact:
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):
| xi | yi | x−x̄ | y−ȳ | (x−x̄)(y−ȳ) | (x−x̄)² | (y−ȳ)² |
|---|---|---|---|---|---|---|
| 1 | 4 | −2 | −3 | 6 | 4 | 9 |
| 2 | 5 | −1 | −2 | 2 | 1 | 4 |
| 3 | 7 | 0 | 0 | 0 | 0 | 0 |
| 4 | 8 | 1 | 1 | 1 | 1 | 1 |
| 5 | 11 | 2 | 4 | 8 | 4 | 16 |
| x̄ = 3 | ȳ = 7 | 0 | 0 | Σ = 17 | Σ = 10 | Σ = 30 |
Step 1 — slope:
Step 2 — intercept (line must pass through (x̄, ȳ) = (3, 7)):
Step 3 — predictions and residuals:
| x | y | ŷ = 1.9 + 1.7x | e = y − ŷ | e² |
|---|---|---|---|---|
| 1 | 4 | 3.6 | +0.4 | 0.16 |
| 2 | 5 | 5.3 | −0.3 | 0.09 |
| 3 | 7 | 7.0 | 0.0 | 0.00 |
| 4 | 8 | 8.7 | −0.7 | 0.49 |
| 5 | 11 | 10.4 | +0.6 | 0.36 |
| SSE = Σe² | 1.10 | |||
Step 4 — R² (derived properly in Section 5):
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.
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:
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:
SSE = Σ(yi − ŷi)² (unexplained — what the line still misses)
SSR = Σ(ŷi − ȳ)² (explained — what the line removes from SST)
The coefficient of determination is the explained share:
5.1 What counts as a good R²?
| Domain | Typical "good" R² | Why |
|---|---|---|
| Physical / lab processes | 0.99+ | Governed by precise laws, little noise |
| Engineering / process control | 0.8 – 0.99 | Known dynamics, contained noise |
| Economics, salary, demand data | 0.5 – 0.9 | Human behaviour adds irreducible noise |
| Psychology, social surveys | 0.1 – 0.5 | Enormous 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
- Trap 1 — high R², invalid model. Anscombe's quartet (Chapter 2): a curved dataset can carry the same R² as a clean linear one. R² measures fit, not correctness. The residual plot is the arbiter.
- Trap 2 — in-sample R² only rises. Add any feature — even random noise — and training R² can only increase or stay flat, because OLS can always ignore it at worst... and usually finds some accidental correlation. Which is why we evaluate on the test set, and why Chapter 5 introduces adjusted R², which penalises predictor count.
- Trap 3 — R² says nothing about scale. R² = 0.96 on salaries could still mean ±₹20,000 typical misses. Chapter 10's metrics (MAE, RMSE) speak in target units; R² speaks in variance shares. Report both.
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:
- Slope: "Each additional year of experience is associated with about ₹9,346 higher salary, on average, across the observed range." — this is the sentence the business asked for.
- Intercept: "A employee with zero years of experience would earn ₹26,816." — grammatically fine, substantively suspect: no one in the data has less than 1.1 years, so b₀ is an extrapolation to a region the model never saw. The line must pass through (x̄, ȳ); the intercept is where that geometric fact happens to meet x = 0. Interpret it only when x = 0 is meaningful and observed.
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
8. Strengths, Limitations & the Road Ahead
| Strengths | Limitations |
|---|---|
| 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
- Plot salary vs experience first (Anscombe's law). Does a line look defensible?
- Split 1/3 test with
random_state=0; fitLinearRegression. - Report b₀, b₁ in rupees, R² on train and test, and RMSE on test.
- Predict for 6.5 years — and for 12 years. Which answer deserves a caveat, and why?
- Run the residual-vs-fitted and Q–Q plots. Verdict on L, I, N, E?
- 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
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."
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̄, ȳ).
LINE: Linearity, Independence, Normality, Equal variance — they apply to the residuals, checked chiefly with the residual-vs-fitted plot.
R² splits the variance ledger (SST = SSE + SSR): the explained share. High R² never certifies validity — patterns hide under good numbers.
Report coefficients in business units, fit on held-out data, and mark out-of-range predictions as extrapolation — the four-sentence template.
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: