Sunday, 06 September 2026
Advertisement Advertise Your advert could be here Reach thousands of learners and ICT professionals across Rwanda. Contact us
Advertisement Opportunity Jobs, scholarships & hackathons Fresh openings from Rwandan job boards are pulled in every hour. See openings

Regression: predicting a number

Machine learning with Python · lesson 3 of 12

In this lesson: Fit and interpret a regression model, and diagnose it from its residuals.

Start with linear regression. It is old, it is simple, it is often good enough, and — decisively — you can explain it to a head teacher or a loan officer.

Fit one

import pandas as pd
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_absolute_error, r2_score

features = ["attendance", "previous_mark", "hours_studied"]
X = df[features]
y = df["final_mark"]

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42)

model = LinearRegression().fit(X_train, y_train)
pred = model.predict(X_test)

print("MAE:", round(mean_absolute_error(y_test, pred), 2))
print("R2 :", round(r2_score(y_test, pred), 3))

What it learned

coefs = pd.Series(model.coef_, index=features).sort_values()
print(coefs)
print("intercept:", round(model.intercept_, 2))

# attendance        0.42
# hours_studied     1.85
# previous_mark     0.61
# intercept        12.30

Read it as: holding the other features constant, one extra hour studied is associated with 1.85 more marks. Three cautions, all important:

  • "Associated with", not "causes". Students who study more may differ in ten other ways.
  • The size depends on the unit. A coefficient on attendance measured 0–1 is a hundred times the same relationship measured 0–100. Standardise before comparing coefficient magnitudes.
  • Correlated features scramble the attribution. If attendance and hours studied move together, the model can put the credit on either, and the split between them is unstable.

Residuals: where the model is wrong

import matplotlib.pyplot as plt

residuals = y_test - pred

fig, ax = plt.subplots(1, 2, figsize=(11, 4))
ax[0].scatter(pred, residuals, alpha=0.4)
ax[0].axhline(0, color="red", linestyle="--")
ax[0].set_xlabel("Predicted"); ax[0].set_ylabel("Actual - Predicted")
ax[0].set_title("Residuals vs predicted")

ax[1].hist(residuals, bins=30)
ax[1].set_title("Distribution of residuals")
fig.tight_layout()

What the left panel tells you:

  • A shapeless cloud around zero — good. The model captured the structure.
  • A curve — the true relationship is not straight. Add a squared term, or use a tree-based model.
  • A widening fan — error grows with the prediction. Common with money and counts; try predicting log(y).
  • A tilted line — the model is systematically over- or under-predicting across the range. Something is missing.
Always plot residuals against the predicted value. One number like R² tells you how much error there is; the residual plot tells you where and why, which is the part you can act on.

When straight lines are not enough

# interactions and curvature, still a linear model underneath
from sklearn.preprocessing import PolynomialFeatures
from sklearn.pipeline import make_pipeline

model = make_pipeline(
    PolynomialFeatures(degree=2, include_bias=False),
    LinearRegression(),
).fit(X_train, y_train)

Degree 2 on ten features produces around sixty-five columns, and degree 3 explodes. Polynomial features overfit quickly — check the test score, not the train score.

Regularisation: linear models that resist overfitting

from sklearn.linear_model import Ridge, Lasso, ElasticNet
from sklearn.preprocessing import StandardScaler

ridge = make_pipeline(StandardScaler(), Ridge(alpha=1.0)).fit(X_train, y_train)
lasso = make_pipeline(StandardScaler(), Lasso(alpha=0.1)).fit(X_train, y_train)
  • Ridge shrinks all coefficients toward zero. Good when features are correlated.
  • Lasso pushes some coefficients to exactly zero, so it selects features for you.
  • ElasticNet is a mix of the two.

alpha controls the strength: higher means simpler. Both require scaling — a penalty applied to unscaled coefficients punishes whichever feature happens to have small units.

Try it yourself

Fit a linear regression on your data, print MAE, RMSE and R², compare all three against the DummyRegressor baseline, and plot the residuals. Then look at your five worst predictions with df.iloc[np.argsort(-np.abs(residuals))[:5]] — those rows usually reveal either a missing feature or a data-quality problem.

Create a free account to save progress

All lessons in this track

  1. 1
  2. 2
  3. 3
  4. 4
    Regression metrics: MAE, RMSE and R² ~25 min account needed
  5. 5
    Classification: predicting a category ~30 min account needed
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
    Trees, forests and gradient boosting ~30 min account needed
  11. 11
  12. 12
Advertisement Yanjye Learn a new digital skill this week ICT, programming and professional courses with graded weekly assignments. Start free