Linear Regression
The simplest ML algorithm — and the most important one to truly understand. Build a DoorDash delivery time predictor from scratch.
DoorDash needs a number. You need to give them one.
You're a data scientist at DoorDash. Your lead drops a CSV on your desk: 10,000 completed orders, each with the delivery distance and the actual time it took. Your job is to build a model that predicts delivery time from distance.
You open the file and look at the first few rows.
| order_id | distance_km | delivery_time_min |
|---|---|---|
| SW001 | 1.2 | 18 |
| SW002 | 3.8 | 32 |
| SW003 | 2.1 | 24 |
| SW004 | 5.6 | 47 |
| SW005 | 0.8 | 14 |
| SW006 | 4.2 | 38 |
| ... | ... | ... |
You notice something immediately: longer distances mean longer delivery times. SW001 at 1.2 km took 18 minutes. SW004 at 5.6 km took 47 minutes. There is a clear upward trend. If you could capture that relationship as a formula, you could predict delivery time for any new order.
Drawing the best line through messy data
Imagine plotting all 10,000 orders on a graph. Distance on the x-axis. Delivery time on the y-axis. What you see is a cloud of dots drifting upward from left to right — longer distances, longer times, but with enough scatter that no perfect line could touch every point.
Your goal is to draw a line through the middle of that cloud. Once you have the line, predicting is trivial: find your distance on the x-axis, go straight up until you hit the line, read off the delivery time. Done.
The question is: which line is best? There are infinitely many lines you could draw. You need a way to measure how good a line is — and then find the line that scores best by that measure.
The measure is error. For any line, each data point sits some vertical distance above or below it. That distance is the error for that point — how wrong the line's prediction was. A good line keeps these errors small across all 10,000 points.
Linear Regression minimises the sum of squared errors — not the raw errors. Why squared? Two reasons: squaring makes every error positive (so a -5 error and a +5 error do not cancel out), and squaring penalises large errors much more than small ones (a 10-minute error counts 4× as much as a 5-minute error, not 2×). This makes the algorithm more sensitive to outliers, which is usually what you want in practice.
The line is defined by two numbers:
How much delivery time increases for each additional kilometre of distance. A slope of 7.3 means: add 1 km, add 7.3 minutes to the prediction.
The baseline delivery time when distance is zero — roughly the time to accept the order, prepare it, and hand it to a rider before they move. Around 8–9 minutes.
How the algorithm actually finds the best line
Two methods. sklearn uses the first. Deep learning uses the second. Both find the same answer.
LinearRegression() uses OLS by default via a matrix decomposition called SVD. It is exact, fast, and requires no learning rate. For very large datasets or when you want online learning, use SGDRegressor which uses stochastic gradient descent.Build the DoorDash delivery predictor — step by step
Eight steps. Every step has a purpose. Read the explanation before the code — the code will make more sense when you know why you are writing it.
In a real job you would pull this from BigQuery or a Postgres database. Here we simulate it with numpy so you can run it instantly with no setup.
This is non-negotiable. A scatter plot in 30 seconds tells you whether Linear Regression is the right tool, before you write another line of code.
This is the single most important step beginners skip. If you evaluate on the same data you trained on, you are asking a student to grade their own exam using the answer sheet they already memorised. The score is meaningless.
Three lines. That is all sklearn needs. The complexity is hidden inside fit().
A number without context is useless. Always compare your model to the dumbest possible baseline: predict the mean for every order. If your model cannot beat that, it has learned nothing.
Numbers tell you how wrong you are. Charts tell you where and why. The residual plot is the most important diagnostic chart in Linear Regression.
Simple vs Multiple Linear Regression
One input feature. The model is a 2D line. Two parameters: one slope, one intercept. Good for understanding the algorithm. Rarely sufficient for production.
Multiple input features. The model is a hyperplane in n-dimensional space. One coefficient per feature, one intercept. This is what you use in practice.
Linear Regression assumptions — the honest version
Every statistics textbook lists Linear Regression assumptions in a way designed to make you feel like you need a PhD to check them. You do not. Here is each assumption in plain English, how to check it in 5 minutes, and what happens if it is violated.
What it means: The relationship between your features and your target is approximately a straight line. If the true relationship is a curve, forcing a straight line through it produces systematic errors.
How to check: Plot each feature vs the target. If you see a clear curve, run a residual plot — if residuals curve instead of scatter randomly, linearity is violated.
Predicting app revenue vs user count — early users grow revenue linearly but later users contribute less (saturation). LR underestimates at high user counts.
Distance vs delivery time — adding 1 km consistently adds ~7 minutes regardless of starting distance. The relationship is genuinely linear.
What it means: Because errors are squared, a single extreme point can drag the line significantly toward itself. LR is not robust to outliers.
How to check: Box plots of each feature. Check for values more than 3 standard deviations from the mean. Plot residuals — outliers appear as isolated points far from zero.
A 90-minute delivery (driver had an accident) treated as normal training data. The line tilts toward that point, making predictions slightly worse for all other orders.
After removing the 0.3% of orders with delivery_time > 90 minutes, the line fits the remaining 99.7% much more cleanly.
What it means: Your features should not be highly correlated with each other. If distance_km and distance_miles are both in your model, the algorithm cannot separate their individual contributions.
How to check: Compute a correlation matrix: df.corr(). Features correlated above 0.85 with each other are a problem. Use VIF (Variance Inflation Factor) for a precise check.
Including both distance_km and an estimated_travel_time_sec feature — they measure the same underlying thing. Coefficients become unstable and uninterpretable.
distance_km, restaurant_prep_time, and weather_severity are genuinely independent. Each measures something different. Coefficients are stable and interpretable.
What it means: Errors for one prediction should not predict errors for another. If your model is always wrong at 7pm, those errors are correlated with time — and your model has missed a systematic pattern.
How to check: Plot residuals against time or any variable not in your model. A pattern means you are missing a feature. Random scatter means errors are independent.
Errors are consistently positive (under-predicting) on Friday evenings. time_of_week is not in the model. Residuals correlate with hour_of_day.
After adding is_peak_hour, the Friday evening systematic error disappears. Residuals scatter randomly across all hours.
Every error, explained and fixed
These are the errors you will encounter in your first few weeks. Every one of them is fixable in under five minutes once you know what caused it.
Day one. You've just joined DoorDash's data team.
Your manager shares a Notion doc: "Current ETA accuracy is ±12 minutes. We need ±5 minutes within Q2. You have access to 6 months of BigQuery order data. Go."
Here is what the actual week looks like — not the sanitised tutorial version.
Five things people get wrong about Linear Regression
Two more assumptions get silently violated far more often than the ones on this page's assumptions table: homoscedasticity (residual variance stays roughly constant across the range of predictions) and normality of the residuals. Homoscedasticity is checked with the same residual-versus-predicted plot from Step 7 above — look specifically for a fan or cone shape, spread widening as predictions grow, which shows up constantly in real-world data where absolute error naturally scales with the size of the thing being predicted, like larger orders having proportionally larger errors. Normality of residuals is checked with a Q-Q plot comparing residual quantiles to a theoretical normal distribution, and it mainly affects whether confidence intervals and p-values on the coefficients can be trusted, not the accuracy of the point predictions themselves. Both drift in gradually and are easy to miss unless you deliberately plot for them.
R squared only measures how much of this particular sample's variance in the target the fitted line accounts for — it says nothing about whether the model's assumptions hold, whether it will generalise to new orders, or why the relationship exists. The Multiple Linear Regression example earlier on this page reaches an R squared around 0.92 by adding traffic_score and restaurant_prep; that number looks identical whether traffic genuinely slows deliveries or whether traffic simply happens to be measured at the same times as some other unmodelled cause of delay. A high R squared earned on training data specifically can also just mean the model memorised patterns that will not hold on the held-out test set, which is exactly why this page insists on splitting the data before ever looking at R squared at all.
Multicollinearity mainly damages your ability to interpret individual coefficients, not the accuracy of the combined prediction. If distance_km and an estimated_travel_time feature are highly correlated, the model can split credit between them almost arbitrarily — one fit might give most of the weight to distance and a little to travel time, another equally valid fit might reverse that split — while the combined contribution to any given prediction stays nearly the same either way. That instability is a real problem if the goal is explaining which feature drives delivery time, but if the only goal is minimising prediction error on data that resembles the training distribution, a collinear model can predict just as well as one with a redundant feature removed. The real danger shows up later: if the correlation between those features shifts in production away from what training data showed, a model that leaned on that correlation can degrade sharply.
Ordinary Least Squares fits parameters by minimising squared error on whatever training data it is given, and handing it one more column — even a genuinely useless one, like a random number generator's output — gives the optimiser strictly more freedom to reduce that training error further. R squared on the training set is mathematically guaranteed to only increase or stay flat as features are added; it can never go down. That is exactly why adjusted R squared exists: it subtracts a penalty that grows with the number of predictors relative to the sample size, so a feature that is not pulling its weight can push adjusted R squared down even while plain R squared ticks up. Comparing models with different feature counts using plain R squared instead of the adjusted version, or instead of test set performance, is one of the most common ways beginners convince themselves a bigger model is a better one.
A fitted coefficient captures the association between a feature and the target after accounting for whatever other features happen to be in the model, on this particular dataset — it is a correlational quantity, not a causal one, no matter how large or stable it looks. If a restaurant_popularity feature came out with a large positive coefficient on delivery time, the honest read is only that popular restaurants and long delivery times moved together in this data; a very plausible alternative story is that popular restaurants get overwhelmed with orders and understaff their kitchen during exactly those busy hours — a confounder, not an effect of popularity itself. Distance is the rare feature on this page where correlation and causation likely do line up, since physically travelling further mechanically takes more time, but that confidence comes from reasoning about how delivery works in the real world, not from anything the regression itself proved. Establishing causation in general needs a controlled experiment or a dedicated causal-inference method, never a coefficient by itself.
Linear Regression — 5 questions interviewers actually ask
I would check each one with a specific diagnostic rather than assuming from a scatter plot. Linearity and homoscedasticity both show up in a residuals-versus-predicted-values plot: a random cloud centred on zero with roughly constant spread supports both, a curved pattern means the true relationship is not linear, and a fan or cone shape means variance is not constant. Normality of residuals is checked with a Q-Q plot comparing residual quantiles to a theoretical normal distribution, and it mainly affects whether confidence intervals and p-values on the coefficients can be trusted, not the point predictions themselves. Independence of errors is checked by plotting residuals against time, order, or any variable outside the model — a pattern there means a systematic effect is missing from the model. Multicollinearity is checked with a correlation matrix or, more precisely, Variance Inflation Factor. I would emphasise that violated assumptions in ordinary least squares mostly threaten how much you can trust the model's inference — its confidence intervals, its p-values, how interpretable individual coefficients are — while the point predictions themselves can often still be reasonably useful even under a moderate violation.
R squared alone does not answer that, and I would ask a few things before trusting it. First, is 0.95 measured on a held-out test set or on the training data — a strong score on training data alone says almost nothing about generalisation, and the gap between training and test R squared is often more informative than either number alone. Second, how does it compare to a naive baseline, such as always predicting the mean — a workflow like the DoorDash example on this page always computes that baseline MAE first specifically so a strong-looking metric can be judged against a floor. Third, a high R squared does not validate the model's assumptions or imply the relationships found are causal; it is entirely possible to reach a high R squared with a leaked feature that is only available because the outcome already happened, or with a relationship that will not hold once the underlying data distribution shifts. I would want to see the baseline comparison, the train-versus-test gap, and a residual plot before calling 0.95 good rather than lucky or leaky.
R squared can only increase or stay the same as you add features to an Ordinary Least Squares model, because the optimiser is minimising training error and an extra column — even a useless one — gives it strictly more freedom to reduce that error further; there is no mechanism by which adding a feature can make training R squared go down. Adjusted R squared fixes this by including a penalty term that grows with the number of predictors relative to the number of observations, so a feature that does not meaningfully reduce error can cause adjusted R squared to fall even while plain R squared rises slightly. In practice I use adjusted R squared, or better, held-out test performance, whenever I am comparing two models with different numbers of features, since plain R squared is always biased toward the larger model regardless of whether the extra features are genuinely useful.
The core issue is that the model can no longer cleanly separate how much each of the two correlated features individually contributes, because many different splits of coefficient weight between them produce almost the same combined prediction — that makes individual coefficients unstable and their standard errors unreliable, so a statement like "feature A matters twice as much as feature B" cannot be trusted when the two are highly collinear. Whether it matters in production depends on the goal: if the job is explaining which factor drives the outcome, this is a real problem, and I would drop one of the redundant features, combine them, or use a regularised model like Ridge regression, which handles correlated features more gracefully by shrinking coefficients instead of letting them swing to extreme, unstable values. If the job is purely predictive accuracy on data that resembles the training distribution, collinearity by itself often does not hurt performance much, since the model still captures the combined effect correctly — the real danger is that if the correlation between those features breaks down in future data in a way it never did during training, a model that leaned on that correlation can degrade sharply.
A coefficient from a fitted regression is an association conditional on whatever other features happen to be in the model, on this particular dataset — by itself it is not proof of causation, no matter how large, stable, or statistically significant it looks. The honest process is to ask whether a plausible confounder could produce the same pattern without a causal link, and in general that is a real risk: a feature like restaurant popularity correlating with delivery time could easily be driven by understaffed kitchens during that restaurant's busiest hours rather than popularity itself causing delay. Distance is actually one of the more defensible cases for a near-causal read, since travelling a longer physical distance mechanically takes more time regardless of any other variable, and that mechanical story exists independently of the regression. But that confidence comes from domain reasoning about how delivery physically works, not from the coefficient itself — to establish causation rigorously in the general case, you would want a randomised experiment or a dedicated causal inference method, not just a large coefficient in an observational dataset.
🎯 Key Takeaways
- ✓Linear Regression finds the line that minimises the sum of squared errors (OLS). "Least squares" is the name of that objective.
- ✓The trained model is just two numbers: model.coef_ (one slope per feature) and model.intercept_. Prediction = dot product of weights and features + intercept.
- ✓Always split 80/20 before touching the model. Never evaluate on training data. Never make decisions based on test set performance — use validation or cross-validation.
- ✓Report three metrics: MAE (interpretable, same units as target), RMSE (penalises large errors), R² (fraction of variance explained). Always compare to a naive baseline.
- ✓Four assumptions to check: linearity (scatter plot), no extreme outliers (box plot), no multicollinearity (correlation matrix), independence of errors (residual plot vs time).
- ✓sklearn interface is always the same: instantiate → fit(X_train, y_train) → predict(X_test). Every algorithm in this section follows this pattern.
Discussion
0Have a better approach? Found something outdated? Share it — your knowledge helps everyone learning here.