Contents

Chapter 1

Fairness Testing Guide

How to audit ML models for bias, with practical code examples and decision frameworks.


Why Fairness Testing Is Non-Negotiable

A model can have 99% accuracy and still be discriminatory. If it learned historical biases from training data (and it almost certainly did), those biases will be replicated โ€” and potentially amplified โ€” in production.

Fairness testing isn't just ethical; it's increasingly a legal requirement. The EU AI Act, the NYC Local Law 144 (for hiring algorithms), and the CFPB's stance on fair lending all require demonstrable bias auditing.


The Three Core Metrics

1. Demographic Parity (Statistical Parity)

Definition: The probability of receiving a positive prediction should be the same regardless of group membership.

Formula (stdlib):

python
# For each group g: positive_rate(g) = count(ลท=1 in g) / count(g)
# Ratio = min(rates) / max(rates)
# Pass when ratio โ‰ฅ 0.80 (the four-fifths rule)

When to use: When you care about *equal representation* in outcomes. Appropriate for hiring, lending, and resource allocation where unequal impact requires justification.

When NOT to use: When base rates legitimately differ between groups. Medical diagnosis, for example โ€” disease prevalence varies by demographics, so forcing equal positive rates would misdiagnose patients.

Code:

python
from src.fairness import demographic_parity_ratio

groups = ["male", "male", "female", "female", "female"]
y_pred = [1, 1, 1, 0, 0]
ratio = demographic_parity_ratio(groups, y_pred)
print(f"DP ratio: {ratio:.2f}")
# male rate: 2/2 = 1.0,  female rate: 1/3 = 0.33
# ratio = 0.33 / 1.0 = 0.33 โ†’ FAILS four-fifths rule

2. Equalized Odds

Definition: The true positive rate (TPR) and false positive rate (FPR) should be equal across groups.

Why both TPR and FPR?

  • Equal TPR means the model is equally good at catching true positives across groups. A cancer screening model that catches 95% of cases in one demographic but only 70% in another is dangerous.
  • Equal FPR means the model is equally likely to generate false alarms across groups. A fraud detection model that falsely flags 20% of transactions from one group but only 2% from another creates a discriminatory customer experience.

Formula (stdlib):

python
# For each group g:
#   TPR(g) = TP(g) / (TP(g) + FN(g))
#   FPR(g) = FP(g) / (FP(g) + TN(g))
# Max TPR gap = max(TPR) - min(TPR)
# Max FPR gap = max(FPR) - min(FPR)
# Pass when both gaps โ‰ค 0.10

Code:

python
from src.fairness import equalized_odds_gap

groups = ["A", "A", "A", "A", "B", "B", "B", "B"]
y_true = [1, 1, 0, 0, 1, 1, 0, 0]
y_pred = [1, 1, 0, 0, 1, 0, 0, 0]  # Group B misses one positive
tpr_gap, fpr_gap = equalized_odds_gap(groups, y_true, y_pred)
print(f"TPR gap: {tpr_gap:.2f}, FPR gap: {fpr_gap:.2f}")
# Group A TPR: 1.0, Group B TPR: 0.5 โ†’ gap = 0.5 โ†’ FAILS

3. Disparate Impact Ratio

Definition: Identical to demographic parity ratio. Named differently because the legal concept ("disparate impact") has specific implications in US employment and lending law.

The four-fifths rule: if the selection rate for any protected group is less than 80% of the rate for the most-selected group, there is evidence of adverse impact.


The Full Audit Workflow

python
from src.fairness import audit_fairness

report = audit_fairness(
    groups=test_df["demographic_group"].tolist(),
    y_true=test_df["label"].tolist(),
    y_pred=predictions.tolist(),
    dp_threshold=0.80,
    eo_tpr_threshold=0.10,
    eo_fpr_threshold=0.10,
)

print(report.summary())
# Output:
# Fairness audit: PASSED
#   [PASS] demographic_parity_ratio: 0.8523 (threshold: 0.80)
#   [PASS] equalized_odds_tpr_gap: 0.0412 (threshold: 0.10)
#   [PASS] equalized_odds_fpr_gap: 0.0187 (threshold: 0.10)
#   [PASS] disparate_impact_ratio: 0.8523 (threshold: 0.80)

Subgroup Deep Dive

When the audit *fails*, you need to understand *which* group is affected and *why*:

python
from src.fairness import compute_subgroup_metrics

metrics = compute_subgroup_metrics(groups, y_true, y_pred)
for group, scores in metrics.items():
    print(f"{group}: acc={scores['accuracy']:.3f}, "
          f"prec={scores['precision']:.3f}, rec={scores['recall']:.3f}, "
          f"f1={scores['f1']:.3f}, n={scores['count']:.0f}")

This table often reveals the root cause:

  • Low recall in one group โ†’ the model under-predicts positives for that group (possibly due to under-representation in training data).
  • High FPR in one group โ†’ the model over-predicts positives for that group (possibly due to label noise or proxy features).

Choosing Protected Attributes

Test on all attributes that could create legal or ethical risk:

DomainTypical protected attributes
HiringGender, race/ethnicity, age, disability status
LendingRace, gender, national origin, marital status
HealthcareRace, gender, age, socioeconomic proxies
InsuranceGender, age, zip code (as race proxy)
Criminal justiceRace, gender, age

Proxy features: Be aware that even if you don't include protected attributes directly, the model can learn to approximate them from correlated features (zip code โ†’ race, name โ†’ gender). Test fairness on the *actual protected attribute*, not just the features in the model.


Intersectionality

Groups can be intersectional. A model might be fair for "women" and "Black applicants" individually but biased against "Black women" specifically. Test intersections:

python
# Create intersectional groups
groups = [f"{row['gender']}_{row['race']}" for _, row in df.iterrows()]
report = audit_fairness(groups, y_true, y_pred)

The trade-off: more intersections = smaller group sizes = noisier estimates. You need at least 50โ€“100 samples per intersectional group for reliable results.


What to Do When Fairness Checks Fail

Decision framework:

1. Is the disparity due to training data imbalance?
   โ†’ Collect more data for underrepresented groups
   โ†’ Apply class-reweighting or oversampling

2. Is the disparity due to proxy features?
   โ†’ Identify and remove or decorrelate proxies
   โ†’ Use adversarial debiasing techniques

3. Is the disparity due to legitimate base-rate differences?
   โ†’ Document the justification
   โ†’ Switch from demographic parity to equalized odds
   โ†’ Consider calibration-based fairness

4. Is the disparity small but persistent?
   โ†’ Apply post-processing threshold adjustment per group
   โ†’ Trade off a small amount of overall accuracy for fairness

5. None of the above work?
   โ†’ The model should not be deployed for this use case

Integration with CI/CD

Add fairness as a required gate in your deployment pipeline:

python
# In your CI test file
def test_fairness_audit():
    """Block deployment if fairness checks fail."""
    report = audit_fairness(
        groups=test_groups,
        y_true=test_labels,
        y_pred=model_predictions,
    )
    assert report.passed, (
        f"Fairness audit failed:\n{report.summary()}\n\n"
        f"Failures: {[f.name for f in report.failures]}\n"
        f"See guides/fairness-testing.md for remediation steps."
    )

Set the gate as non-blocking initially while you calibrate thresholds, then make it blocking once you've established baselines. Failing a fairness gate should be treated with the same severity as a security vulnerability.

Chapter 2
๐Ÿ”’ Available in full product

ML Testing Strategy Guide

Chapter 3
๐Ÿ”’ Available in full product

Fairness Testing Guide

Chapter 4
๐Ÿ”’ Available in full product

ML Testing Strategy Guide

You’ve reached the end of the free preview

Get the full ML Testing Framework and unlock everything.

All Chapters

Get the complete guide with every chapter unlocked, including code samples, diagrams, and best practices.

Full Tool Suite

Access all interactive tools with complete data, all workload profiles, and the full scenario library.

Source Files

Downloadable source code, configuration files, and working examples from every chapter.

Lifetime Updates

Free updates for life. Every new chapter, tool, and improvement included.

Buy Now — $29 →
๐Ÿ“ฆ Free sample included — download another copy or visit the store for the full product.
ML Testing Framework v1.0.0 โ€” Free Preview