How can fairness be measured?
Fairness in a model is measured by comparing how it treats two groups. There is more than one way to compare, and the ways can disagree about the same model.
A company scores job applicants and invites the top scorers to interview. Is the score fair to group A and group B? In this lesson you work out two measures and see why one number never settles it.
By the end of this lesson
Note
I can measure how often a model selects each group and how often it finds the qualified people in each.
Key words
| Word | What it means |
|---|---|
| Selection rate | the share of a group the model selects |
| True positive rate | the share of qualified people in a group the model selects |
| False positive rate | the share of unqualified people in a group the model selects |
| Parity | the same rate for both groups |
| Trade-off | a gain in one thing that costs another |
The cells students fill in are left blank in this preview.
Compare rates, not raw counts
To compare groups, count outcomes inside each group, then divide by the size of that group. Raw counts mislead: a group of 200 has more selected people than a group of 50 even when both are treated alike.
The invented data here have 100 people in group A and 100 in group B. Each person is either qualified or not, and either selected or not. The Run below prints the share of each group that was selected.
Worked example: who was selected
| Group | Qualified and selected | Qualified, not selected | Not qualified, selected | Not qualified, not selected |
|---|---|---|---|---|
| A | 40 | 10 | 10 | 40 |
| B | 16 | 4 | 4 | 76 |
The cells students fill in are left blank in this preview.
Print the selection rate of each group
docker exec jupyter python3 -c 'r=[("A",1,1)]*40+[("A",1,0)]*10+[("A",0,1)]*10+[("A",0,0)]*40+[("B",1,1)]*16+[("B",1,0)]*4+[("B",0,1)]*4+[("B",0,0)]*76;print([sum(x[2] for x in r if x[0]==g)/sum(1 for x in r if x[0]==g) for g in "AB"])'Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Two measures ask two different questions
Equal selection rates ask: is each group chosen equally often? Equal true positive rates ask: of the people who were qualified, was each group found equally often?
In this data, 40 of 50 qualified people in group A are selected, and 16 of 20 in group B. Both rates are 0.8, so the second question is answered yes. The first is answered no, as 0.5 is not 0.2.
Print the true positive rate of each group
docker exec jupyter python3 -c 'r=[("A",1,1)]*40+[("A",1,0)]*10+[("A",0,1)]*10+[("A",0,0)]*40+[("B",1,1)]*16+[("B",1,0)]*4+[("B",0,1)]*4+[("B",0,0)]*76;print([sum(x[2] for x in r if x[0]==g and x[1]==1)/sum(1 for x in r if x[0]==g and x[1]==1) for g in "AB"])'Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Work out one true positive rate
# Each row: group, qualified (1 or 0), selected (1 or 0).
rows = [["A", 1, 1]] * 40 + [["A", 1, 0]] * 10 + [["A", 0, 1]] * 10 + [["A", 0, 0]] * 40
qualified = 0
found = 0
for row in rows:
if row[1] == 1:
qualified = qualified + 1
found = found + row[2]
print("Group A true positive rate:", found / qualified)
# Change the 40 and the 10 so fewer qualified people are found, then run again.
The measures disagree when the groups differ
Here 50 of 100 people in group A are qualified, but only 20 of 100 in group B. The groups differ in how many qualified people they hold, so the two measures pull apart.
To make the selection rates equal, group B would need 50 people chosen, but only 20 are qualified. At least 30 unqualified people would be selected. That lowers how often a selection is right.
Worked example: what each choice costs
| Aim | What it asks for | What it costs |
|---|---|---|
| Equal selection rates | Select about 50 from group B | At least 30 unqualified people selected |
| Equal true positive rates | Find 0.8 of the qualified in both | Group B is selected 0.2 of the time, A 0.5 |
| Equal false positive rates | Select unqualified people at the same rate | Group A 0.2 of its unqualified, B 0.05 |
The cells students fill in are left blank in this preview.
Print the false positive rate of each group
docker exec jupyter python3 -c 'r=[("A",1,1)]*40+[("A",1,0)]*10+[("A",0,1)]*10+[("A",0,0)]*40+[("B",1,1)]*16+[("B",1,0)]*4+[("B",0,1)]*4+[("B",0,0)]*76;print([sum(x[2] for x in r if x[0]==g and x[1]==0)/sum(1 for x in r if x[0]==g and x[1]==0) for g in "AB"])'Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
No single measure settles fairness
Each measure protects something different. Equal selection rates protect equal access. Equal true positive rates protect the qualified from being missed. Equal false positive rates protect the unqualified from a wrong selection.
When base rates differ you cannot have all of them. A team picks the measure that fits the harm, asks the people affected, and writes down the choice and its cost.
A common mistake
Warning
Many people think a model is fair if it never sees the group. Other inputs can stand in for it, such as an area or a school. Leaving the group out does not equalise the rates, so measure them on the results.
Going further
Edit the numbers in the true positive rate block so group A finds 0.8 and a made-up group B finds 0.5. Write one sentence on who is missed, and one on what could cause it.
Then say which measure you would report to the people affected, and what you would tell them it costs.