Skip to content

Commit cacb640

Browse files
authored
Merge pull request #567 from propcgamer20-png/fix/reject-inference-stale-output
Fix reject-inference.md Script Execution Output to match its own code
2 parents 01a2110 + ac3810d commit cacb640

4 files changed

Lines changed: 38 additions & 17 deletions

File tree

explainers/reject-inference.html

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@
112112
"url": "https://www.thefaircode.xyz/index.html#explainers"
113113
},
114114
"datePublished": "2026-08-14",
115-
"dateModified": "2026-08-14"
115+
"dateModified": "2026-09-10"
116116
},
117117
{
118118
"@type": "FAQPage",
@@ -376,10 +376,13 @@ <h3 id="detection-and-mitigation-code">Detection and Mitigation Code</h3>
376376
<h4 id="script-execution-output">Script Execution Output</h4>
377377
<pre><code>=== Reject Inference Correction Benchmark (Evaluated on Full Population U) ===
378378
Population AUC Population Accuracy Older Approval Rate Younger Approval Rate Age Fairness Gap
379-
Naive (Approved Only) 0.7812 0.7410 0.8120 0.6540 0.1580
380-
IPW Reweighted 0.8345 0.7985 0.7650 0.7420 0.0230
381-
Soft Parceled 0.8115 0.7730 0.7840 0.7110 0.0730</code></pre>
382-
<p>The baseline <strong>Naive Model</strong> trained strictly on approved data exhibits a <strong>15.80 percentage point age fairness gap</strong> on the full population, even though the true ground-truth outcome <code>Y</code> was generated independent of age. The <strong>IPW Reweighted Model</strong> corrects for selection propensity, restoring population AUC from 0.7812 to 0.8345 and shrinking the age fairness gap to <strong>2.30 percentage points</strong>.</p>
379+
Naive (Approved Only) 0.9460 0.8851 0.5413 0.5366 0.0047
380+
IPW Reweighted 0.9456 0.8813 0.5514 0.5453 0.0062
381+
Soft Parceled 0.9238 0.8852 0.5413 0.5369 0.0045</code></pre>
382+
<p>(Figures are the deterministic output of the seeded script in this repository&#x27;s reference environment; a <code>RandomForestClassifier</code> with a fixed seed is not guaranteed bit-identical across CPU architectures and BLAS backends, so the last one or two digits can move on other machines. The story below only depends on the leading digits.)</p>
383+
<p>The historical gate in this simulation rejects young applicants far more often than older ones - the <code>-0.8 * age_young</code> term cuts a typical young applicant&#x27;s approval odds from roughly 50% to 35%, so young applicants are 34.6% of the population but only 27.1% of the approved pool. Despite that, all three models land within <strong>half a percentage point</strong> of demographic parity on the full population, and IPW and Soft Parceling barely move the near-zero gap the Naive model already shows.</p>
384+
<p>That is the expected result here, not a bug: <code>y_true</code> is generated with no age term (older and younger applicants both repay about 55% of the time), and every model is trained on <code>credit_score</code> and <code>income_k</code> only - both drawn independently of <code>age_young</code>. Selection that acts on age alone is therefore ignorable for estimating <code>P(Y | X)</code>, so a well-specified learner recovers a near-parity score distribution with or without a correction, and there is no naive-model gap for IPW to close.</p>
385+
<p>The disparity this simulation <em>does</em> contain lives entirely in the selection gate (older approval rate ~50%, younger ~35%). A fairness audit run on model scores - or on the approved-only rows, the only rows a real lender keeps - sees the near-parity table above and never detects it. That is the point of <em>Why It Matters</em> item 2: reconstructing the full population <code>U</code> is the only way the selection-gate disparity becomes visible at all. IPW and parceling earn their keep in the harder case where selection also depends on features the outcome model omits, or on the latent outcome itself (MNAR) - conditions this deliberately minimal simulation does not create.</p>
383386
<hr>
384387
<h3 id="limitations-and-trade-offs">Limitations and Trade-offs</h3>
385388
<h4 id="1-the-mar-assumption-is-unverifiable">1. The MAR Assumption Is Unverifiable</h4>

explainers/reject-inference.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -237,12 +237,18 @@ if __name__ == "__main__":
237237
```
238238
=== Reject Inference Correction Benchmark (Evaluated on Full Population U) ===
239239
Population AUC Population Accuracy Older Approval Rate Younger Approval Rate Age Fairness Gap
240-
Naive (Approved Only) 0.7812 0.7410 0.8120 0.6540 0.1580
241-
IPW Reweighted 0.8345 0.7985 0.7650 0.7420 0.0230
242-
Soft Parceled 0.8115 0.7730 0.7840 0.7110 0.0730
240+
Naive (Approved Only) 0.9460 0.8851 0.5413 0.5366 0.0047
241+
IPW Reweighted 0.9456 0.8813 0.5514 0.5453 0.0062
242+
Soft Parceled 0.9238 0.8852 0.5413 0.5369 0.0045
243243
```
244244

245-
The baseline **Naive Model** trained strictly on approved data exhibits a **15.80 percentage point age fairness gap** on the full population, even though the true ground-truth outcome `Y` was generated independent of age. The **IPW Reweighted Model** corrects for selection propensity, restoring population AUC from 0.7812 to 0.8345 and shrinking the age fairness gap to **2.30 percentage points**.
245+
(Figures are the deterministic output of the seeded script in this repository's reference environment; a `RandomForestClassifier` with a fixed seed is not guaranteed bit-identical across CPU architectures and BLAS backends, so the last one or two digits can move on other machines. The story below only depends on the leading digits.)
246+
247+
The historical gate in this simulation rejects young applicants far more often than older ones - the `-0.8 * age_young` term cuts a typical young applicant's approval odds from roughly 50% to 35%, so young applicants are 34.6% of the population but only 27.1% of the approved pool. Despite that, all three models land within **half a percentage point** of demographic parity on the full population, and IPW and Soft Parceling barely move the near-zero gap the Naive model already shows.
248+
249+
That is the expected result here, not a bug: `y_true` is generated with no age term (older and younger applicants both repay about 55% of the time), and every model is trained on `credit_score` and `income_k` only - both drawn independently of `age_young`. Selection that acts on age alone is therefore ignorable for estimating `P(Y | X)`, so a well-specified learner recovers a near-parity score distribution with or without a correction, and there is no naive-model gap for IPW to close.
250+
251+
The disparity this simulation *does* contain lives entirely in the selection gate (older approval rate ~50%, younger ~35%). A fairness audit run on model scores - or on the approved-only rows, the only rows a real lender keeps - sees the near-parity table above and never detects it. That is the point of *Why It Matters* item 2: reconstructing the full population `U` is the only way the selection-gate disparity becomes visible at all. IPW and parceling earn their keep in the harder case where selection also depends on features the outcome model omits, or on the latent outcome itself (MNAR) - conditions this deliberately minimal simulation does not create.
246252

247253
---
248254

faircode/_explainers/reject-inference.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -237,12 +237,18 @@ if __name__ == "__main__":
237237
```
238238
=== Reject Inference Correction Benchmark (Evaluated on Full Population U) ===
239239
Population AUC Population Accuracy Older Approval Rate Younger Approval Rate Age Fairness Gap
240-
Naive (Approved Only) 0.7812 0.7410 0.8120 0.6540 0.1580
241-
IPW Reweighted 0.8345 0.7985 0.7650 0.7420 0.0230
242-
Soft Parceled 0.8115 0.7730 0.7840 0.7110 0.0730
240+
Naive (Approved Only) 0.9460 0.8851 0.5413 0.5366 0.0047
241+
IPW Reweighted 0.9456 0.8813 0.5514 0.5453 0.0062
242+
Soft Parceled 0.9238 0.8852 0.5413 0.5369 0.0045
243243
```
244244

245-
The baseline **Naive Model** trained strictly on approved data exhibits a **15.80 percentage point age fairness gap** on the full population, even though the true ground-truth outcome `Y` was generated independent of age. The **IPW Reweighted Model** corrects for selection propensity, restoring population AUC from 0.7812 to 0.8345 and shrinking the age fairness gap to **2.30 percentage points**.
245+
(Figures are the deterministic output of the seeded script in this repository's reference environment; a `RandomForestClassifier` with a fixed seed is not guaranteed bit-identical across CPU architectures and BLAS backends, so the last one or two digits can move on other machines. The story below only depends on the leading digits.)
246+
247+
The historical gate in this simulation rejects young applicants far more often than older ones - the `-0.8 * age_young` term cuts a typical young applicant's approval odds from roughly 50% to 35%, so young applicants are 34.6% of the population but only 27.1% of the approved pool. Despite that, all three models land within **half a percentage point** of demographic parity on the full population, and IPW and Soft Parceling barely move the near-zero gap the Naive model already shows.
248+
249+
That is the expected result here, not a bug: `y_true` is generated with no age term (older and younger applicants both repay about 55% of the time), and every model is trained on `credit_score` and `income_k` only - both drawn independently of `age_young`. Selection that acts on age alone is therefore ignorable for estimating `P(Y | X)`, so a well-specified learner recovers a near-parity score distribution with or without a correction, and there is no naive-model gap for IPW to close.
250+
251+
The disparity this simulation *does* contain lives entirely in the selection gate (older approval rate ~50%, younger ~35%). A fairness audit run on model scores - or on the approved-only rows, the only rows a real lender keeps - sees the near-parity table above and never detects it. That is the point of *Why It Matters* item 2: reconstructing the full population `U` is the only way the selection-gate disparity becomes visible at all. IPW and parceling earn their keep in the harder case where selection also depends on features the outcome model omits, or on the latent outcome itself (MNAR) - conditions this deliberately minimal simulation does not create.
246252

247253
---
248254

llms-full.txt

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9395,12 +9395,18 @@ if __name__ == "__main__":
93959395
```
93969396
=== Reject Inference Correction Benchmark (Evaluated on Full Population U) ===
93979397
Population AUC Population Accuracy Older Approval Rate Younger Approval Rate Age Fairness Gap
9398-
Naive (Approved Only) 0.7812 0.7410 0.8120 0.6540 0.1580
9399-
IPW Reweighted 0.8345 0.7985 0.7650 0.7420 0.0230
9400-
Soft Parceled 0.8115 0.7730 0.7840 0.7110 0.0730
9398+
Naive (Approved Only) 0.9460 0.8851 0.5413 0.5366 0.0047
9399+
IPW Reweighted 0.9456 0.8813 0.5514 0.5453 0.0062
9400+
Soft Parceled 0.9238 0.8852 0.5413 0.5369 0.0045
94019401
```
94029402

9403-
The baseline **Naive Model** trained strictly on approved data exhibits a **15.80 percentage point age fairness gap** on the full population, even though the true ground-truth outcome `Y` was generated independent of age. The **IPW Reweighted Model** corrects for selection propensity, restoring population AUC from 0.7812 to 0.8345 and shrinking the age fairness gap to **2.30 percentage points**.
9403+
(Figures are the deterministic output of the seeded script in this repository's reference environment; a `RandomForestClassifier` with a fixed seed is not guaranteed bit-identical across CPU architectures and BLAS backends, so the last one or two digits can move on other machines. The story below only depends on the leading digits.)
9404+
9405+
The historical gate in this simulation rejects young applicants far more often than older ones - the `-0.8 * age_young` term cuts a typical young applicant's approval odds from roughly 50% to 35%, so young applicants are 34.6% of the population but only 27.1% of the approved pool. Despite that, all three models land within **half a percentage point** of demographic parity on the full population, and IPW and Soft Parceling barely move the near-zero gap the Naive model already shows.
9406+
9407+
That is the expected result here, not a bug: `y_true` is generated with no age term (older and younger applicants both repay about 55% of the time), and every model is trained on `credit_score` and `income_k` only - both drawn independently of `age_young`. Selection that acts on age alone is therefore ignorable for estimating `P(Y | X)`, so a well-specified learner recovers a near-parity score distribution with or without a correction, and there is no naive-model gap for IPW to close.
9408+
9409+
The disparity this simulation *does* contain lives entirely in the selection gate (older approval rate ~50%, younger ~35%). A fairness audit run on model scores - or on the approved-only rows, the only rows a real lender keeps - sees the near-parity table above and never detects it. That is the point of *Why It Matters* item 2: reconstructing the full population `U` is the only way the selection-gate disparity becomes visible at all. IPW and parceling earn their keep in the harder case where selection also depends on features the outcome model omits, or on the latent outcome itself (MNAR) - conditions this deliberately minimal simulation does not create.
94049410

94059411
---
94069412

0 commit comments

Comments
 (0)