Skip to content

Commit 1f58fd2

Browse files
docs: correct COMPAS reduction figure repo-wide (71% -> 82%) + Benefits Denial proxy count
#545: the COMPAS "Reduction" figure was the raw percentage-point gap (86.77 - 15.69 = 71.08 -> "71%"), not the relative reduction every other audit reports: (86.77 - 15.69) / 86.77 = 81.9% -> "82%" The other six audits' rows all check out against the relative formula (97.3%, 73.6%, ~60%, ~72%, 53%). "71%" was COMPAS's number not just in the two summary tables but in nine explainers and CHANGELOG.md, so fixing only the tables would have left the repo internally inconsistent. Updated every "71% reduction" / "71% of the gap" COMPAS reference in source (COMPAS/README.md, root README.md x3, CHANGELOG.md, PULL_REQUEST_TEMPLATE.md example, and explainers ai-objectivity-myth, confounding-variable, data-leakage, equalized-odds, how-ai-detects-patterns, ml-bias, proxy-variables, reinforcement-learning) and regenerated the explainer pages / package mirror / llms-full.txt. The underlying gap numbers (86.77%, 15.69%) are unchanged. #546: Benefits Denial's audit.yaml lists five proxy_features (marital.status, occupation, relationship, hours.per.week, fnlwgt) and fair.py removes all five with documented reasoning, but both READMEs listed only four. Added fnlwgt to Benefits Denial/README.md and the root README (summary row, the "four proxy variables" -> "five" counts, and the fair.py code-block comment list). Closes #545 Closes #546
1 parent 9cd70fc commit 1f58fd2

30 files changed

Lines changed: 62 additions & 61 deletions

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131

3232
**After fairness gap:** <!-- e.g. 15.69% -->
3333

34-
**Reduction:** <!-- e.g. 71% -->
34+
**Reduction:** <!-- e.g. 82% -->
3535

3636
**Protected attribute(s):** <!-- e.g. Race -->
3737

Benefits Denial/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ python3 "Benefits Denial/fair.py" # mitigated (protected attribute + proxies
1919
## What the audit controls
2020

2121
- Protected attribute(s): Sex, Race, Origin, Age
22-
- Proxy feature(s) removed in `fair.py`: Relationship, Marital Status, Hours, Occupation
22+
- Proxy feature(s) removed in `fair.py`: Relationship, Marital Status, Hours, Occupation, fnlwgt (census sampling weight)
2323
- Fairness metric: Demographic Parity (difference in positive-prediction rate between groups)
2424

2525
## Expected result (published, paper-aligned)

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -717,7 +717,7 @@ First release since **v1.1.0** (9 Jun 2026). The headline is the **Open Dataset
717717
### Added
718718
- Explainer: Reinforcement Learning - `reinforcement-learning.md` created by evanjain-dot (PR #48, commit a785ea95), added to `index.html`, `README.md`, and `CONTRIBUTING.md` (commit e3928af7)
719719
- Full explainer covering the three-part RL loop (state → action → reward → policy), reward function design as a political act, reward hacking, and the credit assignment problem
720-
- Real-world proof using COMPAS as an RL-adjacent system: biased policy produces 86.77% Black/White fairness gap; removing race + `CustodyStatus` proxy reduces gap to 15.69% (71% reduction)
720+
- Real-world proof using COMPAS as an RL-adjacent system: biased policy produces 86.77% Black/White fairness gap; removing race + `CustodyStatus` proxy reduces gap to 15.69% (82% reduction)
721721
- Results table: biased policy vs. race-only removal vs. race + proxy removal
722722
- Second case: YouTube recommendation engine using watch time as reward signal - documents asymmetric demographic consequences and outrage optimisation
723723
- `fairness_gap()` detection code with chi-squared proxy check for state representation audit

COMPAS/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,6 @@ python3 "COMPAS/fair.py" # mitigated (protected attribute + proxies dropped)
2626

2727
| Group | Gap, biased (`unfair.py`) | Gap, mitigated (`fair.py`) | Reduction |
2828
|-------|--------------------------:|---------------------------:|----------:|
29-
| Race | 86.77% | 15.69% | 71% |
29+
| Race | 86.77% | 15.69% | 82% |
3030

3131
These match the "Results at a Glance" table in the [main README](../README.md#results-at-a-glance) and the frozen snapshot in `paper/results-frozen/`. The scripts are deterministic at `random_state=42`, so a correct local run reproduces them exactly. If your numbers differ, check the seed, the split, and your package versions before opening an issue - and never edit the frozen numbers to match a local run (see [CLAUDE.md](../CLAUDE.md)).

README.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,12 @@ Each audit ships as both a pair of Python scripts (`unfair.py` / `fair.py`) for
6868

6969
| # | Domain | Protected Attribute | Proxies Removed | Gap Before → After | Reduction |
7070
|:-:|--------|--------------------|-----------------|--------------------|:---------:|
71-
| 01 | [Criminal Justice](#01--compas--criminal-justice-bias) | Race | Custody Status | 86.77% → 15.69% | **71%** |
71+
| 01 | [Criminal Justice](#01--compas--criminal-justice-bias) | Race | Custody Status | 86.77% → 15.69% | **82%** |
7272
| 02 | [Hiring](#02--ai-fair-recruitment--hiring-bias) | Gender | Age | 4.51% → 0.12% | **97.3%** |
7373
| 03 | [Lending](#03--german-credit-lending--lending-bias) | Age | Employment Tenure | 7.16% → 1.89% | **73.6%** |
7474
| 04 | [Healthcare](#04--insurance-denial--healthcare-bias) | Age, Gender | BMI, Smoker, Diabetic | Age: 7.93% → 3.18% | **60%** |
7575
|| | | | Gender: 5.44% → 1.54% | **72%** |
76-
| 05 | [Welfare](#05--benefits-denial--welfare-eligibility-bias) | Sex, Race, Origin, Age | Relationship, Marital Status, Hours, Occupation | Sex: 18.00% → 8.52% | **53%** |
76+
| 05 | [Welfare](#05--benefits-denial--welfare-eligibility-bias) | Sex, Race, Origin, Age | Relationship, Marital Status, Hours, Occupation, fnlwgt | Sex: 18.00% → 8.52% | **53%** |
7777
|| | | | Race: 12.75% → 6.90% | **46%** |
7878
|| | | | Origin: 4.40% → 0.52% | **88%** |
7979
| 06 | [Healthcare Readmission](#06--healthcare-readmission--clinical-bias) | Race, Gender, Age | Payer Code, Discharge Disposition, Medical Specialty, Prior Inpatient | Gender: 0.02% → 0.04% | **+100% ↑** |
@@ -371,7 +371,7 @@ X = pd.get_dummies(df[[
371371
| White Defendants | 69.02% |
372372
| **New Fairness Gap** | **15.69%** |
373373

374-
**Result: 71% reduction in the fairness gap.**
374+
**Result: 82% reduction in the fairness gap.**
375375

376376
> **Key insight:** Removing race alone isn't enough. Proxy variables like custody status carry the same racial signal because of historical over-policing of Black communities. Both the protected attribute *and* its proxies must be removed.
377377
@@ -546,7 +546,7 @@ Automated welfare and benefits systems use income-prediction models to screen ap
546546

547547
#### The Problem - `unfair.py`
548548

549-
Trained with sex, race, age, and national origin directly, plus four proxy variables that reconstruct those attributes even after the protected columns are removed.
549+
Trained with sex, race, age, and national origin directly, plus five proxy variables that reconstruct those attributes even after the protected columns are removed.
550550

551551
| Group | Ineligibility Flag Rate |
552552
|-------|:-----------------------:|
@@ -568,7 +568,7 @@ Trained with sex, race, age, and national origin directly, plus four proxy varia
568568

569569
#### The Fix - `fair.py`
570570

571-
Dropped all four protected attributes and all four proxy variables. Retained only the features a means-tested programme can legitimately consult under equality law.
571+
Dropped all four protected attributes and all five proxy variables. Retained only the features a means-tested programme can legitimately consult under equality law.
572572

573573
```python
574574
# THE FIX: Policy-defined economic signals only
@@ -586,6 +586,7 @@ features = [
586586
# marital.status removed ✓ (proxy: encodes sex via spousal status)
587587
# hours.per.week removed ✓ (proxy: encodes sex via caregiving gap)
588588
# occupation removed ✓ (proxy: encodes race via occupational segregation)
589+
# fnlwgt removed ✓ (proxy: census sampling weight, no causal link)
589590
]
590591
```
591592

explainers/ai-objectivity-myth.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ <h3 id="concrete-example-compas-audit-01">Concrete Example: COMPAS - Audit 01</h
211211
model = RandomForestClassifier(n_estimators=100, random_state=42)
212212
model.fit(X_train, y_train)
213213
# Fairness Gap: 86.77%</code></pre>
214-
<p>Removing <code>race</code> alone barely moves the number, because <code>CustodyStatus</code> - a record of prior system contact - carries the same signal: communities that were over-policed historically generate more &quot;custody status&quot; records today, independent of actual reoffense risk. Only after <code>race</code> <em>and</em> <code>CustodyStatus</code> are both dropped does the gap fall to 15.69%, a 71% reduction. The model was never neutral. It was a faithful record of an unequal criminal justice system, expressed as a probability.</p>
214+
<p>Removing <code>race</code> alone barely moves the number, because <code>CustodyStatus</code> - a record of prior system contact - carries the same signal: communities that were over-policed historically generate more &quot;custody status&quot; records today, independent of actual reoffense risk. Only after <code>race</code> <em>and</em> <code>CustodyStatus</code> are both dropped does the gap fall to 15.69%, an 82% reduction. The model was never neutral. It was a faithful record of an unequal criminal justice system, expressed as a probability.</p>
215215
<h3 id="detection-code">Detection Code</h3>
216216
<p>This function checks whether a model&#x27;s outcome rates differ across protected groups by more than a configurable threshold, and flags features that correlate with the protected attribute strongly enough to explain the gap on their own.</p>
217217
<pre><code class="language-python">import pandas as pd

explainers/ai-objectivity-myth.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ model.fit(X_train, y_train)
4040
# Fairness Gap: 86.77%
4141
```
4242

43-
Removing `race` alone barely moves the number, because `CustodyStatus` - a record of prior system contact - carries the same signal: communities that were over-policed historically generate more "custody status" records today, independent of actual reoffense risk. Only after `race` *and* `CustodyStatus` are both dropped does the gap fall to 15.69%, a 71% reduction. The model was never neutral. It was a faithful record of an unequal criminal justice system, expressed as a probability.
43+
Removing `race` alone barely moves the number, because `CustodyStatus` - a record of prior system contact - carries the same signal: communities that were over-policed historically generate more "custody status" records today, independent of actual reoffense risk. Only after `race` *and* `CustodyStatus` are both dropped does the gap fall to 15.69%, an 82% reduction. The model was never neutral. It was a faithful record of an unequal criminal justice system, expressed as a probability.
4444

4545
## Detection Code
4646

explainers/confounding-variable.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ <h3 id="concrete-example-compas-and-custodystatus">Concrete Example: COMPAS and
222222
<p>Both arrows are caused by the same systemic factor. <code>CustodyStatus</code> is associated with the recidivism label not because custody history is a reliable individual risk signal, but because the same structural forces that produce elevated custody records also produce more recidivism label events - through monitoring, not through behavior.</p>
223223
<p>Removing both <code>race</code> and <code>CustodyStatus</code> breaks this path:</p>
224224
<pre><code class="language-python"># fair.py: features include only priors_count + charge_degree + age
225-
# Black/White fairness gap: 15.69% - 71% reduction</code></pre>
225+
# Black/White fairness gap: 15.69% - 82% reduction</code></pre>
226226
<p>The residual 15.69% reflects other confounding paths (differential bail rates, charge severity distributions, surveillance-driven label noise) that require changes upstream of the model to eliminate entirely.</p>
227227
<hr>
228228
<h3 id="detection-code">Detection Code</h3>
@@ -315,7 +315,7 @@ <h3 id="limitations">Limitations</h3>
315315
<p>1. <strong>Stratified analysis cannot distinguish confounding from effect modification.</strong> If a feature has a genuinely different causal effect on the outcome across strata - not just a different baseline - that is effect modification, not confounding. The two require different handling. Conflating them produces wrong adjustments.</p>
316316
<p>2. <strong>You can only condition on observed confounders.</strong> If the confounder is unmeasured - historical policing intensity, neighbourhood-level surveillance, differential healthcare access - no statistical adjustment removes its effect. Causal inference methods (instrumental variables, propensity score matching, difference-in-differences) can partially address unmeasured confounding but require strong, often untestable assumptions about the causal structure.</p>
317317
<p>3. <strong>Conditioning on a collider opens new bias.</strong> A collider is a variable caused by both the feature and the outcome - the reverse of a confounder. Controlling for a collider introduces a spurious association rather than removing one. Correctly distinguishing confounders from colliders requires a causal graph (a DAG), not statistical testing alone. Chi-squared tests cannot tell you which direction the arrows point.</p>
318-
<p>4. <strong>Confounder removal reduces but does not eliminate bias.</strong> Removing <code>CustodyStatus</code> from COMPAS cuts the fairness gap from 86.77% to 15.69% - a 71% reduction. The remaining gap reflects additional confounding paths that cannot be closed by feature removal without changing the label generation process itself.</p>
318+
<p>4. <strong>Confounder removal reduces but does not eliminate bias.</strong> Removing <code>CustodyStatus</code> from COMPAS cuts the fairness gap from 86.77% to 15.69% - an 82% reduction. The remaining gap reflects additional confounding paths that cannot be closed by feature removal without changing the label generation process itself.</p>
319319
<p>5. <strong>Adjustment can introduce its own distortions.</strong> Propensity score methods and inverse probability weighting reduce confounding but amplify variance, especially in small subgroups. In high-stakes settings, an overcorrected model may perform worse for the groups it was adjusted to protect.</p>
320320
<hr>
321321
<h3 id="related-concepts">Related Concepts</h3>
@@ -329,7 +329,7 @@ <h4 id="feedback-loop-bias">Feedback Loop Bias</h4>
329329
<p>When a confounded model is deployed and its outputs influence future labels - recidivism surveillance, credit monitoring, healthcare resource allocation - the confounding strengthens across retraining cycles. The model&#x27;s outputs become part of the data-generating process, reinforcing the spurious association with each iteration. See <a href="feedback-loop-bias.html">feedback-loop-bias.md</a>.</p>
330330
<hr>
331331
<h3 id="related-projects-in-this-repo">Related Projects in This Repo</h3>
332-
<ul><li><a href="../index.html#project-compas"><code>COMPAS/</code></a> - the primary worked example. <code>CustodyStatus</code> confounds the race→recidivism path, driving 71% of the Black/White fairness gap. Removing it alongside <code>race</code> reduces the gap from 86.77% to 15.69%.</li><li><a href="../index.html#project-benefits"><code>Benefits Denial/</code></a> - <code>relationship</code> and <code>marital-status</code> act as confounders for sex: historical gender norms independently elevated male-coded relationship statuses and income levels in the census data, creating a spurious association the model amplifies.</li><li><a href="../index.html#project-readmission"><code>Healthcare Readmission/</code></a> - <code>payer_code</code> is confounded by race: differential insurance access is caused by structural factors that also independently predict readmission risk, not only by individual health status.</li></ul>
332+
<ul><li><a href="../index.html#project-compas"><code>COMPAS/</code></a> - the primary worked example. <code>CustodyStatus</code> confounds the race→recidivism path, driving 82% of the Black/White fairness gap. Removing it alongside <code>race</code> reduces the gap from 86.77% to 15.69%.</li><li><a href="../index.html#project-benefits"><code>Benefits Denial/</code></a> - <code>relationship</code> and <code>marital-status</code> act as confounders for sex: historical gender norms independently elevated male-coded relationship statuses and income levels in the census data, creating a spurious association the model amplifies.</li><li><a href="../index.html#project-readmission"><code>Healthcare Readmission/</code></a> - <code>payer_code</code> is confounded by race: differential insurance access is caused by structural factors that also independently predict readmission risk, not only by individual health status.</li></ul>
333333
<hr>
334334
<h3 id="further-reading">Further Reading</h3>
335335
<ul><li><a href="https://doi.org/10.1017/CBO9780511803161" target="_blank" rel="noreferrer noopener">Pearl, J. (2009). <em>Causality: Models, Reasoning and Inference</em> (2nd ed.). Cambridge University Press.</a> - the foundational text on causal graphs, the do-calculus, and the formal definitions of confounders, mediators, and colliders that underpin modern causal fairness work.</li><li><a href="https://doi.org/10.1126/science.aax2342" target="_blank" rel="noreferrer noopener">Obermeyer, Z., Powers, B., Vogeli, C., &amp; Mullainathan, S. (2019). Dissecting racial bias in an algorithm used to manage the health of populations. <em>Science</em>, 366(6464), 447–453.</a> - a documented case of confounding-driven racial bias: healthcare cost (the proxy label) was confounded by differential access, making Black patients appear healthier than white patients with the same conditions, and the algorithm allocated less care as a result.</li><li><a href="https://doi.org/10.1214/12-AOS1058" target="_blank" rel="noreferrer noopener">VanderWeele, T. J., &amp; Shpitser, I. (2013). On the definition of a confounder. <em>Annals of Statistics</em>, 41(1), 196–220.</a> - a rigorous definition of confounding that distinguishes it from colliders and mediators, resolving long-standing disagreements in the epidemiological and statistical literature that carry directly into algorithmic fairness auditing.</li></ul>

explainers/confounding-variable.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ Removing both `race` and `CustodyStatus` breaks this path:
6969

7070
```python
7171
# fair.py: features include only priors_count + charge_degree + age
72-
# Black/White fairness gap: 15.69% - 71% reduction
72+
# Black/White fairness gap: 15.69% - 82% reduction
7373
```
7474

7575
The residual 15.69% reflects other confounding paths (differential bail rates, charge severity distributions, surveillance-driven label noise) that require changes upstream of the model to eliminate entirely.
@@ -181,7 +181,7 @@ For continuous features, replace `chi2_contingency` with a Pearson correlation o
181181

182182
3. **Conditioning on a collider opens new bias.** A collider is a variable caused by both the feature and the outcome - the reverse of a confounder. Controlling for a collider introduces a spurious association rather than removing one. Correctly distinguishing confounders from colliders requires a causal graph (a DAG), not statistical testing alone. Chi-squared tests cannot tell you which direction the arrows point.
183183

184-
4. **Confounder removal reduces but does not eliminate bias.** Removing `CustodyStatus` from COMPAS cuts the fairness gap from 86.77% to 15.69% - a 71% reduction. The remaining gap reflects additional confounding paths that cannot be closed by feature removal without changing the label generation process itself.
184+
4. **Confounder removal reduces but does not eliminate bias.** Removing `CustodyStatus` from COMPAS cuts the fairness gap from 86.77% to 15.69% - an 82% reduction. The remaining gap reflects additional confounding paths that cannot be closed by feature removal without changing the label generation process itself.
185185

186186
5. **Adjustment can introduce its own distortions.** Propensity score methods and inverse probability weighting reduce confounding but amplify variance, especially in small subgroups. In high-stakes settings, an overcorrected model may perform worse for the groups it was adjusted to protect.
187187

@@ -205,7 +205,7 @@ When a confounded model is deployed and its outputs influence future labels - re
205205

206206
## Related Projects in This Repo
207207

208-
- [`COMPAS/`](../COMPAS/) - the primary worked example. `CustodyStatus` confounds the race→recidivism path, driving 71% of the Black/White fairness gap. Removing it alongside `race` reduces the gap from 86.77% to 15.69%.
208+
- [`COMPAS/`](../COMPAS/) - the primary worked example. `CustodyStatus` confounds the race→recidivism path, driving 82% of the Black/White fairness gap. Removing it alongside `race` reduces the gap from 86.77% to 15.69%.
209209
- [`Benefits Denial/`](../Benefits%20Denial/) - `relationship` and `marital-status` act as confounders for sex: historical gender norms independently elevated male-coded relationship statuses and income levels in the census data, creating a spurious association the model amplifies.
210210
- [`Healthcare Readmission/`](../Healthcare%20Readmission/) - `payer_code` is confounded by race: differential insurance access is caused by structural factors that also independently predict readmission risk, not only by individual health status.
211211

0 commit comments

Comments
 (0)