Programming for data science
Python for Data Scientists: Train a Reproducible Demand Model
A pandas and scikit-learn workflow for validation-led model selection, leakage-safe preprocessing, untouched holdout evaluation and reviewable artefacts.
2026 edition · Reproduced 15 August 2026
On this page
Reproduction contract
What this guide lets you reproduce
The workflow, data snapshot and checks below are versioned together. Use the stated commands and compare your results with the tested outputs in the repository.
Before you begin
- Basic descriptive statistics, regression metrics and command-line execution
- Understanding of predictors, targets and post-outcome leakage
- Completion of the SQL feature-table guide or an equivalent validated features.csv artefact
You will be able to
- Construct a pandas and scikit-learn pipeline whose preprocessing is estimated from the permitted development rows
- Select a regularisation value on a chronological validation period without opening the test period
- Refit the selected pipeline on train plus validation and compare it with a simple development-only baseline
- Save a fitted pipeline, machine-readable metadata, metrics and row-level holdout predictions
- Interpret predictive error without converting an historical holdout result into a causal or deployment claim
Tested environment
Pinned dataset
UCI Bike Sharing — hour.csv, LF-normalised repository copy
Historical 2011–2012 Washington, DC rentals; no station capacity, bicycle availability, rebalancing, price or reliable basis for present-day or causal claims. The UCI page reports ten more instances than the archived hourly file contains.
Runnable companion
Review and disclosure
AI assistance helped draft the guide and example code. Clean-environment reproduction was verified in GitHub Actions run
A model should not decide how its own final examination is marked. This workflow uses a chronological design: an early period for fitting, a later validation period for choosing the ridge penalty, and an untouched final quarter for one assessment after the choice is fixed. Preprocessing lives inside the fitted pipeline, so category encoding and scaling follow the same boundary.
What this guide assumes
This is not a tour of Python syntax or a catalogue of libraries. It assumes that you can read a function, work in a virtual environment and interpret mean absolute error (MAE) and root mean squared error (RMSE). The SQL guide must first produce artifacts/features.csv from the pinned source.
The analytical target is the hourly total target_rentals. The allowed predictors describe calendar position, working-day status, weather categories, normalised environmental measures and elapsed time. Neither component of the total enters the feature matrix.
The 2011–2012 observations are used as a stable teaching benchmark. They are not current travel evidence and cannot establish the causal effect of a predictor.
Install the executable lock
From the repository root:
python3 -m venv examples/programming-for-data-science/.venv
examples/programming-for-data-science/.venv/bin/python -m pip install \
-r examples/programming-for-data-science/requirements.txt
The lock pins the direct analytical stack and its runtime dependencies. The central versions are:
| Component | Version | Role |
|---|---|---|
| Python | 3.14.7 | Publication runtime |
| NumPy | 2.5.2 | Arrays and target transformation |
| pandas | 3.0.5 | Feature input and prediction export |
| scikit-learn | 1.9.0 | Composite pipeline, validation and ridge model |
| SciPy | 1.18.0 | Sparse linear algebra |
| joblib | 1.5.3 | Fitted-pipeline serialisation |
| DuckDB | 1.5.5 | Upstream feature-table generation |
Exact pins make the result diagnosable. They do not mean dependencies should never be upgraded. Upgrade them in a separate change, rerun the chain, compare the artefacts and record what changed.
Load only the reviewed feature contract
The source is parsed with observed_at as a timestamp. The script checks the three partition counts and verifies that the last row of each earlier period precedes the first row of the next.
frame = pd.read_csv(FEATURE_PATH, parse_dates=["observed_at"])
counts = frame["split"].value_counts().to_dict()
assert counts == {"train": 13003, "validation": 2208, "test": 2168}
Predictors are declared through an allow-list. A second assertion rejects the target and its known components if they appear in that list. This defence is intentionally duplicated after the SQL handover: upstream quality checks reduce risk, but the model owner remains responsible for the matrix actually fitted.
The script creates one composite category, working_hour, from working-day status and hour. It represents the practical observation that the shape of an hourly rental profile differs between working and non-working days. The interaction is constructed without reading the target.
Put preprocessing inside the estimator
Categorical fields use one-hot encoding with unknown-category handling. Numeric fields are standardised. A ColumnTransformer joins both branches, and Pipeline ensures that each candidate estimates its transformations from the rows supplied to fit.
preprocessor = ColumnTransformer(
transformers=[
(
"categorical",
OneHotEncoder(handle_unknown="ignore", sparse_output=True),
CATEGORICAL_FEATURES,
),
("numeric", StandardScaler(), NUMERIC_FEATURES),
],
remainder="drop",
sparse_threshold=1.0,
)
regressor = Pipeline(
steps=[
("preprocess", preprocessor),
("ridge", Ridge(alpha=alpha, solver="lsqr", tol=1e-8, max_iter=10000)),
]
)
This arrangement prevents a common leakage path: fitting the scaler or category vocabulary on the full dataset before splitting. It also serialises the transformation and model together, which reduces discrepancies between training and later prediction.
OneHotEncoder(handle_unknown="ignore") keeps a previously unseen category from crashing prediction. It does not establish that the new category is safe or meaningful. Production monitoring should still flag unexpected levels and decide how to handle them.
Model a skewed non-negative outcome carefully
Hourly rentals are non-negative and right-skewed. The example wraps the pipeline in TransformedTargetRegressor, fits log1p(target_rentals) and converts predictions back with expm1. Negative back-transformed values are clipped to zero.
TransformedTargetRegressor(
regressor=regressor,
func=np.log1p,
inverse_func=np.expm1,
check_inverse=True,
)
This is a pragmatic baseline rather than a complete count model. The log transform changes the loss geometry, ridge remains linear in its transformed features, and clipping can affect error near zero. A Poisson, negative-binomial, gradient-boosting or other model may fit the problem better, but it should enter through the same validation and holdout contract.
Select on validation, then refit
The script compares alpha ∈ {0.1, 1, 10, 100}. Each candidate fits only the 13,003 training rows. Validation MAE selects the penalty:
| Alpha | Expected validation MAE |
|---|---|
| 0.1 | 63.697 |
| 1 | 63.504 |
| 10 | 61.941 |
| 100 | 63.147 |
The chosen alpha = 10 is then refitted on the 15,211 combined train and validation rows. Only after that refit does the script generate predictions for the 2,168 test rows.
This is a small manual grid, suitable for showing the boundary. A larger search still needs a time-respecting design, such as rolling-origin validation. Random cross-validation would mix later and earlier observations and answer a different question.
Compare against a baseline worth beating
The baseline predicts the mean rental count for each weekday-and-hour group. For validation it is estimated from training rows. For the final test it is re-estimated from train plus validation, matching the candidate’s available development period.
A baseline is part of the scientific argument. Reporting only the candidate’s error cannot show whether its preprocessing and model add useful predictive information beyond a stable calendar pattern.
Expected test checkpoints from the pinned workflow are:
| Metric | Calendar baseline | Selected ridge pipeline |
|---|---|---|
| MAE | 79.049 | 49.894 |
| RMSE | 119.952 | 78.457 |
| Mean signed error | — | −0.927 |
Lower values are better for MAE and RMSE. The candidate reduces MAE by about 29.16 rentals per recorded hour relative to the baseline within this historical test period. Its small negative mean signed error indicates slight average underprediction, although positive and negative errors can cancel in that summary.
These figures describe one 2012 holdout under this feature contract. They do not establish causal effects, current performance or transferability to another city. The next guide tests whether the paired absolute-error difference remains favourable when whole test days are resampled.
Run the model and preserve the evidence
examples/programming-for-data-science/.venv/bin/python \
examples/programming-for-data-science/python/train.py
Expected checkpoint:
model_ok selected_alpha=10 validation_mae=61.941 test_rows=2168 baseline_mae=79.049 model_mae=49.894 model_rmse=78.457
The stage writes:
python_model.joblib, containing the fitted transformation and ridge estimator;python_model_metadata.json, recording versions, selected alpha, feature groups, row counts and transformed feature count;python_metrics.json, retaining every validation candidate and the final validation/test metrics; andpredictions.csv, containing row-level actual, baseline and model values for the validation and test periods.
The display rounds metrics to three decimals. The machine-readable artifact retains full precision, and the golden test permits an absolute difference of 1e-5 rentals so equivalent builds do not fail on negligible floating-point differences in the linear-algebra stack.
Do not treat a joblib file as a permanent, language-neutral model format. It can execute Python objects and should only be loaded from a trusted source in a compatible environment. A deployment decision may require a safer exchange format, feature service or direct reimplementation with parity tests.
Diagnose before adding complexity
Before trying another algorithm, inspect the existing errors by hour, weekday, month, weather category and target magnitude. Check whether rare severe-weather rows dominate RMSE, whether peaks are systematically missed and whether residual structure changes across the quarter. Compare validation and test performance; a large change can indicate seasonality, drift or selection sensitivity.
For production work, add:
- rolling temporal backtests rather than one validation window;
- prediction intervals or quantile forecasts where operational decisions need them;
- capacity, availability and rebalancing variables with prediction-time definitions;
- drift and unknown-category monitoring;
- model-card ownership, approval and rollback procedures; and
- a newer external dataset for transportability assessment.
Do not repeatedly tune against the current test quarter. Once its results influence a choice, reserve a new final holdout.
When Python is the right layer
Python works well here because pandas provides a clear artefact boundary and scikit-learn composes preprocessing, selection and modelling into one fitted object. SQL remains better for the governed upstream table, while R provides a useful independent statistical audit. Keeping those responsibilities explicit reduces the chance that one notebook silently becomes ingestion job, model registry, report and source of truth.
Continue to R for Data Scientists to recalculate the holdout metrics and estimate a day-block bootstrap interval.
Primary references
- Python Software Foundation (2026), Python 3.14 documentation.
- pandas development team (2026), pandas user guide.
- scikit-learn developers (2026), Pipelines and composite estimators and Model evaluation.
- Fanaee-T, H. (2013), Bike Sharing, UCI Machine Learning Repository, CC BY 4.0.
AI assistance helped draft the guide and code. Exact dependency checks, golden metrics, persisted metadata and the independent R audit make the work reviewable. Clean-environment reproduction was verified on 15 August 2026, and human editorial review followed; the historical-data, causal and deployment limitations remain in force.