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.
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.
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.
Learning from earlier elections
- Prepare the data. Standardize missing values, check duplicates, filter contested races and exclude outcome-revealing fields.
- Train and compare. Use 83,857 candidate records before 2022 to fit Logistic Regression and Random Forest, alongside a majority-class benchmark.
- Tune without splitting races. Use three-fold cross-validation grouped by race to tune a 120-tree Random Forest.
- 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.
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.
| Model | Accuracy | F1 | ROC-AUC |
|---|---|---|---|
| Majority-class baseline | 59.0% | 0.000 | 0.500 |
| Logistic Regression | 70.5% | 0.563 | 0.722 |
| Random Forest | 70.1% | 0.633 | 0.738 |
| Tuned Random Forest | 70.1% | 0.635 | 0.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.
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.
- Patricia Ross63.1%
- Kelly Chahal61.4%
- Sandy Blue61.4%
- Dave Loewen60.6%
- Dave Sidhu60.6%
- Les Barkman60.6%
- Alex Mitchell48.9%
- Bharathi Sandhu48.9%
- Patricia Driessen48.9%
- Gurnoor Sidhu48.6%
- Mark Warkentin41.5%
- Amritpal Mund40.9%
- Dao Tran40.9%
- David Evans40.9%
- David Mclauren40.9%
- Jas Anand40.9%
- Reann Gasper40.9%
- Simon Gibson40.9%
- Steve Pimm40.9%
- Tim Felger40.9%
- Tom Norton40.9%
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.
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.
- Predicted class
- 1
- Outcome
- Elected
- Probability of election
- 0.5232
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.
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.
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.