Predictive Modelling
RiskIQ: pricing auto insurance risk instead of guessing at it
A claim-likelihood model and a deployable underwriting function — where the simplest, most interpretable model beat every tree ensemble we tried.
- Context
- MMA 867 — Queen’s University
- Role
- Solution architecture — EDA, model interpretation, deployable underwriting tool
- Timeline
- 2026
- 0.887
- ROC-AUC, best model (logistic regression)
- 10,000
- policyholder records, 18 variables
- 4
- models benchmarked head-to-head
Problem
Blanket, demographic-class premiums overcharge safe drivers and undercharge risky ones — adverse selection that bled Canadian auto insurers roughly $1.2B in Alberta alone in a recent fiscal year.
Approach
Benchmarked logistic regression against decision tree, random forest and gradient boosting on 10,000 records; picked the winner on ROC-AUC, not just accuracy; wrapped it in a scoring function built for a real intake form.
Result
Logistic regression wins outright — 0.887 ROC-AUC, ahead of every tree ensemble — and ships as a `score_customer()` function that turns a raw customer record into a probability and a risk tier.
The business problem, with a number attached
Canadian auto insurers have been pricing risk with a blunt instrument. Broad demographic rating classes assume risk shifts slowly by group — age band, postal code, vehicle class — and charge everyone in a class the same premium. That assumption broke: claim severity and auto-theft payouts both climbed sharply in recent years, and insurers using blanket pricing were absorbing losses averaging 18% more in claims and legal overhead than they collected in premiums, a gap that ran to roughly $1.2B in Alberta alone.
Blanket pricing also self-selects against the insurer. Overcharge a safe driver relative to their actual risk and they leave for a competitor pricing them correctly. Undercharge a risky one and they stay. The book quietly gets worse every renewal cycle. A per-policy risk score, applied at quote time, is the direct countermeasure — and the point of this project was to build one, prove it works, and make it usable by an actual intake process rather than just by a notebook.
The data
10,000 policyholder records, 18 variables spanning demographics (age, gender, education,
income bracket, marital status), driving history (experience, past accidents, speeding
violations, DUIs), and the vehicle itself (year, type, ownership status, annual mileage).
The target — outcome — is whether the policy generated a claim: 31.3% did, 68.7%
didn’t. Moderate imbalance, not the extreme rarity you’d see in fraud detection, but
enough that raw accuracy is a bad optimization target — a model that predicts “no claim”
for everyone still scores nearly 69%.
Roughly 10% of records were missing credit_score and annual_mileage. Rather than drop
those rows or silently impute and move on, we added a credit_score_missing flag before
median-imputing — missingness on a credit file is itself a risk signal (thin or no credit
history correlates with risk in ways a silently-imputed median would erase), and the flag
let the model use that signal instead of losing it.
Four models, one honest comparison
We benchmarked logistic regression against a decision tree, a random forest and gradient boosting on an identical train/test split, and evaluated on precision, recall, F1 and ROC-AUC — never on accuracy alone, for the reason above.
That result mattered beyond the leaderboard. Interpretability isn’t a nice-to-have in insurance pricing — it’s close to a legal requirement. A regulator or an ombudsman asking why a specific customer was priced the way they were needs an answer better than “the gradient boosting model said so.” Logistic regression winning meant we didn’t have to trade performance for explainability — we got both from the same model.
What actually drives claim risk
With logistic regression as the production model, its standardised coefficients double as the interpretability layer the regulatory case needs. Sign and magnitude both matter here: a negative coefficient means that factor lowers claim odds.
Threshold selection is a business decision, not a statistical one
The costs of the two error types are wildly asymmetric: underpricing a genuinely risky policy costs a claim payout; overpricing a safe one costs a customer to a competitor who prices them correctly. The operating threshold has to come from those relative costs, not from an F1-optimal default — a point the strategic report built the deployment recommendation around, not just the model itself.
The deployable piece
A notebook that produces a good ROC-AUC and stops is a homework assignment. The thing that
makes this a solution rather than an exercise is score_customer() — a function that takes
a raw record in the shape an intake form would actually produce (strings like "high school"
or "before 2015", not pre-encoded features), and returns a claim probability and a risk
tier:
def score_customer(record: dict, model=logit, scaler=scaler, ...):
"""
Take a raw customer record (as it would come off the intake form),
return claim probability and a risk tier.
"""
r = dict(record)
# ordinal encoding for education / income / driving_experience
# credit_score: flag-then-impute if missing, using the training median
# one-hot encoding for vehicle_year and vehicle_type
# build the feature vector in trained column order, scale, predict
...
return {"claim_probability": prob, "risk_tier": tier}
It handles the two things a real intake form guarantees will happen: missing
credit_score, and categorical fields arriving as human-readable strings rather than
model-ready codes. Both are handled the same way the training pipeline handled them, so a
production score can never silently drift from how the model was actually fit. Wrapped
behind a Google Form intake, it’s deployable as-is.
What I owned
Team Danforth split the pipeline into phases — data cleaning, feature engineering, hypothesis testing, modelling — across five people. My piece was the EDA on all 18 variables at the start, and the underwriting scoring tool at the end: taking a fitted model out of a notebook and making it something an intake process could actually call.
Limitations
- Synthetic-adjacent dataset. The 10,000-record dataset is realistic but not a live book of business — real deployment needs validation against actual claims experience before any premium decision leans on it.
- Static snapshot, not a monitored model. Risk profiles drift — driving habits, vehicle age, local claim severity all move over time. A production version needs scheduled retraining and drift monitoring, not a one-time fit.
- Fairness review is unfinished.
gendershows up as a meaningful coefficient, and any model that prices on demographic-adjacent features needs an explicit fairness and regulatory-compliance pass before it touches a real quote.