Easier to read with cream paper, a tint, more contrast or larger text? Choose your colours and text, once, for every page.

Free lesson

How to measure fairness in a model

After this lesson: I can measure how often a model selects each group and how often it finds the qualified people in each.

60 min lesson3 commandsJupyterLab data science lab: pandas, Matplotlib and scikit-learn in the browser

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

WordWhat it means
Selection ratethe share of a group the model selects
True positive ratethe share of qualified people in a group the model selects
False positive ratethe share of unqualified people in a group the model selects
Paritythe same rate for both groups
Trade-offa 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

GroupQualified and selectedQualified, not selectedNot qualified, selectedNot qualified, not selected
A40101040
B164476

The cells students fill in are left blank in this preview.

Print the selection rate of each group

Command
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

Command
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

Code: python
# 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

AimWhat it asks forWhat it costs
Equal selection ratesSelect about 50 from group BAt least 30 unqualified people selected
Equal true positive ratesFind 0.8 of the qualified in bothGroup B is selected 0.2 of the time, A 0.5
Equal false positive ratesSelect unqualified people at the same rateGroup 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

Command
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.

About this lab and its source: JupyterLab data science lab: pandas, Matplotlib and scikit-learn in the browser

Teaching a class? Set this module for your students with a teacher guide, marked assignments and a lab for every student.