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 epsilon=0.1

Leaves

Affine forms

demographic_parity()

PR(1) PR(0) - abs 0.1 -

2

2

equal_opportunity()

TPR(1) TPR(0) - abs 0.1 -

2

2

equalized_odds()

TPR(1) TPR(0) - abs FPR(1) FPR(0) - abs + 0.1 -

4

4

error_rate_parity()

FP(1) FN(1) + FP(0) FN(0) + - abs 0.1 -

4

2

error_rate()

FP(1) FN(1) + 0.1 -

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 δ\delta slices spent: equalized_odds needs four, which is why it is the loosest of the five at a fixed tolerance.

The definitions#

demographic_parity

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 opportunity: equal true-positive rate (recall) across groups.

TPR(1) TPR(0) - abs 0.1 -

affine, 2 form(s)

equalized_odds

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)

error_rate_parity

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

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#

Ignore the label

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.

Condition on the label

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.

Bound the errors

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.

Combine them

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 δ\delta, 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

TPR(g)

group and Y = 1

recall: predicted 1 among the group’s positives

FPR(g)

group and Y = 0

predicted 1 among the group’s negatives

TNR(g)

group and Y = 0

predicted 0 among the group’s negatives

FNR(g)

group and Y = 1

predicted 0 among the group’s positives

TP(g)

group only

predicted 1 and labelled 1, as a fraction of the whole group

FP(g)

group only

predicted 1 and labelled 0, as a fraction of the whole group

TN(g)

group only

predicted 0 and labelled 0, as a fraction of the whole group

FN(g)

group only

predicted 0 and labelled 1, as a fraction of the whole group

PR(g)

group only

predicted 1, whatever the label; equals TP + FP

NR(g)

group only

predicted 0, whatever the label; equals TN + FN

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 δ\delta. 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 g(θ)0g(\theta) \leq 0, 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.