Renee RamjetBack to portfolio
Data Science / Machine Learning / REST API

Municipal Election Outcome Intelligence

Turning historical election records into an estimate of a candidate’s chances of being elected.

I built a classification workflow that prepares election data, compares models and serves predictions through FastAPI. The project explores what historical information can tell us before the votes are counted.

70.1%Evaluation accuracy
0.635F1-score for elected candidates
0.733ROC-AUC
The question

Why estimate an election outcome?

Campaign teams may want an early indication of which candidates need closer attention. A lower estimate could prompt further investigation and a discussion about additional support. Teams would combine that signal with current polling, campaign knowledge and local conditions.

This project tests a possible source of that information. It does not establish that using the model improves campaign decisions, identify which intervention would work, or determine whether an election is fair.

The evidence

Canadian municipal election data

The Canadian Municipal Elections Database on Borealis contains 123,416 candidate records and 19 variables covering 1867–2022. After excluding missing outcomes and filtering to contested races, 91,768 records remained.

The outcome is simple: elected or not elected. Inputs include incumbency, election year, competition for seats and candidate and location information. Final vote totals and acclamation information were excluded to prevent the model from seeing the answer.

Candidates already holding office were elected in 70.5% of records with known incumbency, compared with 29.1% for non-incumbents.

That is a historical association, not proof of cause. About 40% of candidate records in contested races were elected, making a majority-class benchmark essential.

The method

Learning from earlier elections

  1. Prepare the data. Standardize missing values, check duplicates, filter contested races and exclude outcome-revealing fields.
  2. Train and compare. Use 83,857 candidate records before 2022 to fit Logistic Regression and Random Forest, alongside a majority-class benchmark.
  3. Tune without splitting races. Use three-fold cross-validation grouped by race to tune a 120-tree Random Forest.
  4. Evaluate on later elections. Compare predictions with the known outcomes of 7,911 candidates from 2022.

Preprocessing and the trained model are saved together, so the API applies the same transformations to new requests.

The results

A small gain from tuning

F1 balances identifying actual winners with avoiding incorrect winner predictions. ROC-AUC measures how well scores distinguish elected from non-elected candidates across thresholds.

Recorded 2022 evaluation results
ModelAccuracyF1ROC-AUC
Majority-class baseline59.0%0.0000.500
Logistic Regression70.5%0.5630.722
Random Forest70.1%0.6330.738
Tuned Random Forest70.1%0.6350.733

I selected the tuned forest for its highest F1. The improvement was only 0.002, and other models led on accuracy or ROC-AUC. The 2022 results also informed model selection, so an independent final evaluation remains necessary.

Real candidates, real outcomes

A closer look at one council race

To make the results easier to interpret, I compared model estimates with actual outcomes for all 21 candidates competing for eight seats in the 2022 Abbotsford council election. This shows how campaign teams could compare candidate prospects and why a higher estimated chance does not guarantee a win.

Abbotsford council, 2022 · 21 candidates for 8 seatsElectedNot elected
  1. Patricia Ross63.1%
  2. Kelly Chahal61.4%
  3. Sandy Blue61.4%
  4. Dave Loewen60.6%
  5. Dave Sidhu60.6%
  6. Les Barkman60.6%
  7. Alex Mitchell48.9%
  8. Bharathi Sandhu48.9%
  9. Patricia Driessen48.9%
  10. Gurnoor Sidhu48.6%
  11. Mark Warkentin41.5%
  12. Amritpal Mund40.9%
  13. Dao Tran40.9%
  14. David Evans40.9%
  15. David Mclauren40.9%
  16. Jas Anand40.9%
  17. Reann Gasper40.9%
  18. Simon Gibson40.9%
  19. Steve Pimm40.9%
  20. Tim Felger40.9%
  21. Tom Norton40.9%
0%20%40%60%80%
Retrospective estimates from the final notebook, ranked from highest to lowest. Gold bars mark candidates who were actually elected. These are historical results, not forecasts for a current election.

Patricia Ross had an estimated chance of 63.1% and was elected. Sandy Blue had a similar estimate of 61.4% but was not elected. Patricia Driessen and Alex Mitchell both received an estimate of 48.9%, yet only Driessen was elected.

For a campaign manager, these estimates offer a starting point for reviewing candidate prospects alongside current local evidence. They do not explain why an individual won or show which campaign intervention would improve their chances.

The prediction service

From candidate information to an API response

The FastAPI service accepts one candidate’s information, validates it and loads the saved preprocessing and model pipeline. It returns an elected/not elected label and an estimated probability without retraining.

POST/predict200 OK
Predicted class
1
Outcome
Elected
Probability of election
0.5232
Recorded notebook test output, reformatted for readability. The request is illustrative. A 0.5232 score is a model estimate, not a verified result for a real candidate.

Notebook TestClient checks returned HTTP 200 for both the health and prediction endpoints. This verifies the application in process. A Dockerfile and dependency list are prepared, but a WSL service failure prevented container testing. The service is not publicly hosted on this portfolio.

Responsible use

What needs further testing

The data excludes polling, campaign spending and current local issues. Coverage varies across years and places, and historical advantages can carry into predictions. Gender is included as an input, with group fairness checks still outstanding.

Before operational use, I would evaluate on independent data, examine group performance, assess probability calibration and verify the Docker container. A prediction provides a starting point for investigation, with human judgment and current evidence still essential.

Explore the implementation

Notebook and API code

The project repository contains the original notebook with recorded outputs, the API implementation, Dockerfile, dependencies, example request and reproducibility instructions. The trained model can be generated by running the notebook with the source dataset.