1. Same rule, different outcomes
A (made-up) bank uses a model score to approve loans: score ≥ 65 → approved. Sounds fair — one rule for all.
But suppose the score was trained on old data where people from small towns, or from Hindi-medium schools, were rarely given loans. The model learns that pattern and gives them lower scores, even when they repay just as well. The rule is the same; the data carried the bias.
So always check results per group:
- approval rate: what share of each group gets a yes?
- accuracy per group: is the model equally right for everyone?
Try it above: move the threshold and watch the approval rate and accuracy of each group change.
Challenge: Approval rate per group
Write approvalRate(people, group, threshold): look only at people whose group matches, and return the fraction of them with score >= threshold (a number 0–1). If nobody is in the group, return 0.
2. Is it fair? Measure, then fix
In our made-up data, both groups repaid equally often (4 of 6). Yet group A is approved twice as often as group B, and the model is wrong far more often for B. That is a red flag.
What can people do about it?
- check approval rate and accuracy for every group before launch
- fix the data: collect fairer, more representative examples
- drop unfair features (and their stand-ins, like PIN code standing in for community)
- keep a human in the loop for big decisions, and let people appeal
The same holds for exam shortlisting, job filters or face recognition. A model is never neutral just because it is maths.
Challenge: Fairness report
approvalRate is ready. Write:
groupAccuracy(people, group, threshold): in that group, the model says yes when score >= threshold; it is correct when that yes/no equals repaid. Return the fraction correct (0 for an empty group).rateGap(people, threshold): the absolute difference between the approval rates of groups "A" and "B".