Fairness constraints#
fair-seldonian ships builders for the most common fairness definitions, so a
named criterion can go straight into
SeldonianConfig instead of a hand-written
reverse-Polish (postfix) string:
from fair_seldonian import SeldonianConfig, demographic_parity
config = SeldonianConfig(constraint=demographic_parity(epsilon=0.1))
Each builder takes a tolerance epsilon (smaller is stricter). The group-parity
builders also take a groups=(g1, g0) pair, defaulting to ("1", "0"), while
error_rate() takes a single group.
Labels are matched against str(T), so the defaults line up with a 0/1
sensitive column.
What each builder produces#
The table below is generated by calling each builder, so it shows the string the library actually emits today rather than a transcription of it.
Builder |
Postfix at |
Leaves |
Affine forms |
|---|---|---|---|
|
2 |
2 |
|
|
2 |
2 |
|
|
4 |
4 |
|
|
4 |
2 |
|
|
2 |
1 |
Two things the Affine forms column tells you. A constraint inside the affine
fragment can be bounded by Affine-Form Compilation (affine), which is roughly twice as tight
as interval arithmetic — and all five builders qualify, because they are written
over the rate primitives rather than over explicit ratios. The count itself is
the number of slices spent: equalized_odds needs four, which
is why it is the loosest of the five at a fixed tolerance.
The definitions#
Demographic parity: equal predicted-positive rate across groups.
PR(1) PR(0) - abs 0.1 -
affine, 2 form(s)
Dwork, C., Hardt, M., Pitassi, T., Reingold, O., & Zemel, R. (2012). Fairness through awareness. ITCS ‘12. https://arxiv.org/abs/1104.3913
Barocas, S., Hardt, M., & Narayanan, A. (2023). Fairness and Machine Learning: Limitations and Opportunities (independence criterion). MIT Press. https://fairmlbook.org
Equal opportunity: equal true-positive rate (recall) across groups.
TPR(1) TPR(0) - abs 0.1 -
affine, 2 form(s)
Hardt, M., Price, E., & Srebro, N. (2016). Equality of opportunity in supervised learning. NeurIPS 2016. https://arxiv.org/abs/1610.02413
Equalized odds: equal true- and false-positive rates across groups.
TPR(1) TPR(0) - abs FPR(1) FPR(0) - abs + 0.1 -
affine, 4 form(s)
Hardt, M., Price, E., & Srebro, N. (2016). Equality of opportunity in supervised learning. NeurIPS 2016. https://arxiv.org/abs/1610.02413
Error-rate parity: equal misclassification rate across groups.
FP(1) FN(1) + FP(0) FN(0) + - abs 0.1 -
affine, 2 form(s)
Berk, R., Heidari, H., Jabbari, S., Kearns, M., & Roth, A. (2021). Fairness in criminal justice risk assessments: The state of the art. Sociological Methods & Research, 50(1), 3-44 (overall accuracy equality). https://arxiv.org/abs/1703.09207
Barocas, S., Hardt, M., & Narayanan, A. (2023). Fairness and Machine Learning: Limitations and Opportunities. MIT Press. https://fairmlbook.org
Error rate: bound a single group’s misclassification rate.
FP(1) FN(1) + 0.1 -
affine, 1 form(s)
Thomas, P. S., da Silva, B. C., Barto, A. G., Giguère, S., Brun, Y., & Brunskill, E. (2019). Preventing undesirable behavior of intelligent machines. Science, 366(6468), 999-1004 (behavioral constraints such as bounded error). https://doi.org/10.1126/science.aag3311
Choosing a definition#
demographic_parity() asks only
that each group be predicted positive at the same rate. Natural for
allocative decisions, where a positive prediction grants access to
something and equal access is the goal.
It can be satisfied by a model that is deliberately worse for one group.
equal_opportunity() and
equalized_odds() compare rates
among people with the same true label, so they do not penalise a model
for genuinely different base rates between groups.
Use them when the cost of a specific error type must be shared fairly.
error_rate() caps one group’s
misclassification rate — a performance bound rather than a comparison.
error_rate_parity() equalises
total error without constraining which kind of error may differ.
Nothing stops you pairing a parity constraint with an error bound so the model must be both fair and good. Each additional term spends its own slice of , so a combined constraint needs more data.
Warning
These criteria are mutually incompatible in general. Except in degenerate cases — equal base rates across groups, or a perfect classifier — no model can simultaneously satisfy demographic parity and equalized odds, and no model can be both calibrated and equalized-odds fair when base rates differ. Choosing a definition is a decision about which notion of fairness the application needs, not a technical detail. See Barocas, Hardt & Narayanan (2023), fairmlbook.org.
Writing your own#
The builders are conveniences. SeldonianConfig.constraint accepts any postfix
expression over the base variables below, so a criterion that is not shipped can
still be certified.
Primitive |
Conditions on |
Averaged over |
|---|---|---|
|
group and |
recall: predicted 1 among the group’s positives |
|
group and |
predicted 1 among the group’s negatives |
|
group and |
predicted 0 among the group’s negatives |
|
group and |
predicted 0 among the group’s positives |
|
group only |
predicted 1 and labelled 1, as a fraction of the whole group |
|
group only |
predicted 1 and labelled 0, as a fraction of the whole group |
|
group only |
predicted 0 and labelled 0, as a fraction of the whole group |
|
group only |
predicted 0 and labelled 1, as a fraction of the whole group |
|
group only |
predicted 1, whatever the label; equals |
|
group only |
predicted 0, whatever the label; equals |
The distinction between the three kinds matters and is easy to miss. A cell
such as TP(g) is a fraction of the whole group, so every row of the group
contributes and rows with the wrong label contribute zero. A rate such as
TPR(g) is a mean over only that group’s positive rows, so it carries a
different — smaller — sample size, and therefore a wider interval per unit of
. Two base variables are independent exactly when the rows they
average over are disjoint, which is what licenses the affine bound.
Operators are + - * / ^ and abs, all in postfix position:
from fair_seldonian import SeldonianConfig
# Demographic parity, written out by hand.
SeldonianConfig(constraint="PR(1) PR(0) - abs 0.1 -")
# Cap group 1's false-negative rate at 15%.
SeldonianConfig(constraint="FNR(1) 0.15 -")
Every constraint encodes , which is why each example ends
by subtracting its tolerance. SeldonianConfig
runs validate_constraint() on
construction, so a malformed expression raises ValueError immediately rather
than failing deep inside the algorithm.
See also
Concentration Inequalities for how wide each interval is, Algorithm Variants for how the
intervals are combined, and
fair_seldonian.constraints.fairness for the full API.