Code

Jupyter notebooks

Two tracks. The starter notebooks provide the scaffolding, data loading and plotting, with the estimators left as raise NotImplementedError. The solution notebooks are complete and already executed. Use them to check yourself, not to start.

Running them

Everything runs on NumPy, pandas, Matplotlib and scikit-learn. In Colab, upload uber_surge_mumbai.csv and new_city_features.csv alongside the notebook; locally, keep them in a data/ folder next to it. If a CSV is missing the notebook regenerates it from the same seed.

Starter notebooks

Task 1Task 2

01 · Least squares and Ridge

Hand computation of $X^\top X$ on five trips, the determinant, the explicit $2\times2$ inverse; then Ridge in closed form, the coefficient path, and 5-fold CV. Includes the instability table you must fill in.

≈ 30 cells · NumPy only for the estimators

Task 3

02 · Lasso by coordinate descent

Soft-thresholding from scratch, cyclic coordinate descent, verification against sklearn.Lasso to three decimals, the elimination-order path, and the 120-feature / 60-trip new-city selection problem.

≈ 34 cells · includes the $p \gg n$ dataset

Task 4

03 · Elastic Net and the grouping effect

The derived is_bad_weather feature, the three-way comparison on the correlated group, the ρ sweep, and the bootstrap selection-stability experiment that turns "Lasso picks one at random" into a measurement.

≈ 28 cells · 200 bootstrap refits, runs in seconds

Task 5Bonus

04 · The surge engine and the posterior

The price() function with its policy layer, the BKC simulation, the supply sweep with the cap and threshold marked, and then the full Gaussian posterior with credible intervals and predictive variance.

≈ 30 cells · ends with a deployable function

Task 6ℓ₁ℓ₂

05 · Logistic regression

A Bernoulli likelihood in place of a Gaussian one, with the same priors. Penalized logistic regression by proximal gradient descent, verified against sklearn; coefficient paths; ROC and a threshold chosen from a stated cost ratio; and a measurement of what dichotomising the target cost.

≈ 20 cells · includes the liblinear intercept trap

Everything at once

One archive with all ten notebooks, both datasets, the ground-truth file and the generator script.

Download all (.zip) uber_surge_mumbai.csv new_city_features.csv make_data.py

Conventions used in the code

ConventionWhy
Objective is $\frac{1}{2n}\lVert y-Xw\rVert^2 + \alpha\rho\lVert w\rVert_1 + \frac{\alpha(1-\rho)}{2}\lVert w\rVert_2^2$ Identical to scikit-learn's ElasticNet, so your solver and the library agree to machine precision. The $1/2n$ matters.
Ridge closed form uses $\alpha_{\text{sklearn}} = n\alpha$ sklearn.Ridge omits the $1/n$. This factor is the most common reason a path plot looks shifted.
Standardize on the training split only, then apply to test Computing the mean and sd over all 200 trips leaks test information into training. A small effect here, a bad habit elsewhere.
Time-based split, never shuffled Rows are in time order. A random split lets the model see the future.
np.linalg.solve(A, b) rather than inv(A) @ b Faster and numerically better conditioned. The explicit inverse appears exactly once, in Task 1, because there you are asked to look at it.
Logistic objective is mean log-loss plus the same penalty, intercept unpenalized Matches LogisticRegression at $C = 1/(n\alpha)$ — but only with lbfgs or saga. liblinear penalizes the intercept, which this objective does not.
Random seed fixed at 42 everywhere Bootstrap numbers should match the solution notebook exactly; if not, something else differs.
On using the solutions

They are published deliberately: checking an answer against a correct one is how people learn to debug numerical code. But a coordinate descent loop you have read is not one you can write. Attempt each cell first, and if you consult the solution, say so in your submission.

The same computations, without leaving the browser, are in the algorithm walkthroughs and the lab.