Skip to content

Commit 4c69af9

Browse files
docs: fix fabricated ~20.9pp hiring gap in demographic-parity / disparate-treatment / neural-networks
All three explainers cited a "~20.9 percentage point" gender gap for the AI Fair Recruitment audit. 20.9% is README.md's pull-quote for the *relative* reduction ((21.62 - 17.10) / 21.62 = 20.9%), not an absolute pp gap. The real absolute gap - per README.md's canonical table and matched correctly by disparate-impact.md and supervised-learning.md - is 21.62% vs 17.10%, a 4.51pp gap, closing to 0.12pp (97.3% relative reduction) after dropping gender and age. - demographic-parity.md: replaced the invented ~71%/~50% biased rates and ~67%/~67% fixed rates with the canonical 21.62%/17.10% and a prose statement of the 0.12pp / 97.3% result. - disparate-treatment.md: "20.9pp hire rate gap" -> "4.51pp hire rate gap (21.62% vs 17.10%)". - neural-networks.md: replaced the invented 61.2%/40.3% biased rates, 54.1%/54.0% fixed rates, and the "20.9% -> ~18% -> 0.1% (99.5%)" summary with the canonical 21.62%/17.10%, 4.51pp -> 0.12pp, 97.3% figures. The fabricated post-fix per-group rates (no canonical source) are dropped rather than replaced with new guesses. Regenerated the pages, package mirror, and llms-full.txt. Closes #550
1 parent 9cd70fc commit 4c69af9

10 files changed

Lines changed: 49 additions & 87 deletions

explainers/demographic-parity.html

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -211,11 +211,10 @@ <h3 id="the-formal-definition">The Formal Definition</h3>
211211
<h3 id="real-world-proof-hiring-bias">Real-World Proof: Hiring Bias</h3>
212212
<p>The AI Fair Recruitment audit in this repo is a direct illustration of a demographic parity violation - and its fix.</p>
213213
<p>A model trained with gender and age as features assigned hire recommendations at sharply different rates:</p>
214-
<div class="explainer-table-wrap"><table class="explainer-table"><thead><tr><th>Group</th><th>Hire Rate</th></tr></thead><tbody><tr><td>Male applicants</td><td>~71%</td></tr><tr><td>Female applicants</td><td>~50%</td></tr><tr><td><strong>Fairness Gap</strong></td><td><strong>~20.9 percentage points</strong></td></tr></tbody></table></div>
214+
<div class="explainer-table-wrap"><table class="explainer-table"><thead><tr><th>Group</th><th>Hire Rate</th></tr></thead><tbody><tr><td>Male applicants</td><td>21.62%</td></tr><tr><td>Female applicants</td><td>17.10%</td></tr><tr><td><strong>Fairness Gap</strong></td><td><strong>4.51 percentage points</strong></td></tr></tbody></table></div>
215+
<p>(The disparate-impact ratio here is 17.10 / 21.62 = 0.79, below the 0.80 four-fifths threshold.)</p>
215216
<p>The model was not told to discriminate. It learned to - by treating age as a proxy for gender, because women in the dataset more often had career gaps. Age was correlated with gender, so including it smuggled the gender signal back in even without an explicit gender rule.</p>
216-
<p>After dropping gender and age (the protected attribute and its proxy):</p>
217-
<div class="explainer-table-wrap"><table class="explainer-table"><thead><tr><th>Group</th><th>Hire Rate</th></tr></thead><tbody><tr><td>Male applicants</td><td>~67%</td></tr><tr><td>Female applicants</td><td>~67%</td></tr><tr><td><strong>New Fairness Gap</strong></td><td><strong>~0.12 percentage points</strong></td></tr></tbody></table></div>
218-
<p><strong>97.3% reduction.</strong> The gap wasn&#x27;t in the underlying merit of candidates - it was in which features the model was permitted to see.</p>
217+
<p>After dropping gender and age (the protected attribute and its proxy), the gap closes to <strong>0.12 percentage points</strong> - a <strong>97.3% reduction</strong>. The gap wasn&#x27;t in the underlying merit of candidates - it was in which features the model was permitted to see.</p>
219218
<hr>
220219
<h3 id="detection-code">Detection Code</h3>
221220
<h4 id="measure-demographic-parity-gap">Measure demographic parity gap</h4>

explainers/demographic-parity.md

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -50,21 +50,15 @@ A model trained with gender and age as features assigned hire recommendations at
5050

5151
| Group | Hire Rate |
5252
|---|---|
53-
| Male applicants | ~71% |
54-
| Female applicants | ~50% |
55-
| **Fairness Gap** | **~20.9 percentage points** |
53+
| Male applicants | 21.62% |
54+
| Female applicants | 17.10% |
55+
| **Fairness Gap** | **4.51 percentage points** |
5656

57-
The model was not told to discriminate. It learned to - by treating age as a proxy for gender, because women in the dataset more often had career gaps. Age was correlated with gender, so including it smuggled the gender signal back in even without an explicit gender rule.
58-
59-
After dropping gender and age (the protected attribute and its proxy):
57+
(The disparate-impact ratio here is 17.10 / 21.62 = 0.79, below the 0.80 four-fifths threshold.)
6058

61-
| Group | Hire Rate |
62-
|---|---|
63-
| Male applicants | ~67% |
64-
| Female applicants | ~67% |
65-
| **New Fairness Gap** | **~0.12 percentage points** |
59+
The model was not told to discriminate. It learned to - by treating age as a proxy for gender, because women in the dataset more often had career gaps. Age was correlated with gender, so including it smuggled the gender signal back in even without an explicit gender rule.
6660

67-
**97.3% reduction.** The gap wasn't in the underlying merit of candidates - it was in which features the model was permitted to see.
61+
After dropping gender and age (the protected attribute and its proxy), the gap closes to **0.12 percentage points** - a **97.3% reduction**. The gap wasn't in the underlying merit of candidates - it was in which features the model was permitted to see.
6862

6963
---
7064

explainers/disparate-treatment.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ <h4 id="ai-fair-recruitment-unfair-py">AI Fair Recruitment - <code>unfair.py</co
221221
<pre><code class="language-python"># DISPARATE TREATMENT: Gender and Age are direct model features
222222
features = [&#x27;Gender&#x27;, &#x27;Age&#x27;, &#x27;Experience_Years&#x27;, &#x27;Technical_Test_Score&#x27;,
223223
&#x27;Education_Level&#x27;, &#x27;Previous_Companies&#x27;, &#x27;Distance_from_Company&#x27;]</code></pre>
224-
<p><code>Gender</code> is a direct input. <code>Age</code> is both an input and a proxy for gender (women in the dataset more often have career gaps, so age encodes gender signal twice over - once directly, once through correlation). The model was <em>designed</em> to see these attributes. The 20.9pp hire rate gap is the disparate impact.</p>
224+
<p><code>Gender</code> is a direct input. <code>Age</code> is both an input and a proxy for gender (women in the dataset more often have career gaps, so age encodes gender signal twice over - once directly, once through correlation). The model was <em>designed</em> to see these attributes. The 4.51pp hire rate gap (21.62% vs 17.10%) is the disparate impact.</p>
225225
<h4 id="the-fix-what-removing-disparate-treatment-looks-like">The Fix - What Removing Disparate Treatment Looks Like</h4>
226226
<pre><code class="language-python"># fair.py: protected attribute and its proxy removed
227227
features = [&#x27;Experience_Years&#x27;, &#x27;Technical_Test_Score&#x27;]

explainers/disparate-treatment.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ features = ['Gender', 'Age', 'Experience_Years', 'Technical_Test_Score',
6969
'Education_Level', 'Previous_Companies', 'Distance_from_Company']
7070
```
7171

72-
`Gender` is a direct input. `Age` is both an input and a proxy for gender (women in the dataset more often have career gaps, so age encodes gender signal twice over - once directly, once through correlation). The model was *designed* to see these attributes. The 20.9pp hire rate gap is the disparate impact.
72+
`Gender` is a direct input. `Age` is both an input and a proxy for gender (women in the dataset more often have career gaps, so age encodes gender signal twice over - once directly, once through correlation). The model was *designed* to see these attributes. The 4.51pp hire rate gap (21.62% vs 17.10%) is the disparate impact.
7373

7474
### The Fix - What Removing Disparate Treatment Looks Like
7575

explainers/neural-networks.html

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ <h4 id="what-we-did">What We Did</h4>
285285
&#x27;communication_score&#x27;
286286
]</code></pre>
287287
<p><strong>Results:</strong></p>
288-
<div class="explainer-table-wrap"><table class="explainer-table"><thead><tr><th>Group</th><th>Hire Rate</th></tr></thead><tbody><tr><td>Male candidates</td><td>61.2%</td></tr><tr><td>Female candidates</td><td>40.3%</td></tr><tr><td><strong>Fairness gap</strong></td><td><strong>20.9%</strong></td></tr></tbody></table></div>
288+
<div class="explainer-table-wrap"><table class="explainer-table"><thead><tr><th>Group</th><th>Hire Rate</th></tr></thead><tbody><tr><td>Male candidates</td><td>21.62%</td></tr><tr><td>Female candidates</td><td>17.10%</td></tr><tr><td><strong>Fairness gap</strong></td><td><strong>4.51 percentage points</strong></td></tr></tbody></table></div>
289289
<p>The network didn&#x27;t contain a rule that said &quot;prefer men.&quot; It learned from historical hiring data in which men were hired more. The weights encoded that pattern. The bias was invisible - buried in floating-point numbers across hidden layers.</p>
290290
<hr>
291291
<p><strong>Step 2 - Remove gender + proxy (our fix):</strong></p>
@@ -296,10 +296,9 @@ <h4 id="what-we-did">What We Did</h4>
296296
&#x27;technical_score&#x27;,
297297
&#x27;communication_score&#x27;
298298
]</code></pre>
299-
<p><strong>Results:</strong></p>
300-
<div class="explainer-table-wrap"><table class="explainer-table"><thead><tr><th>Group</th><th>Hire Rate</th></tr></thead><tbody><tr><td>Male candidates</td><td>54.1%</td></tr><tr><td>Female candidates</td><td>54.0%</td></tr><tr><td><strong>Fairness gap</strong></td><td><strong>0.1%</strong></td></tr></tbody></table></div>
299+
<p><strong>Result:</strong> the fairness gap closes to <strong>0.12 percentage points</strong>.</p>
301300
<h4 id="summary">Summary</h4>
302-
<div class="explainer-table-wrap"><table class="explainer-table"><thead><tr><th>Approach</th><th>Fairness Gap</th><th>Reduction</th></tr></thead><tbody><tr><td>Biased model</td><td>20.9%</td><td>-</td></tr><tr><td>Remove gender only</td><td>~18%</td><td>Minimal</td></tr><tr><td>Remove gender + proxy</td><td>0.1%</td><td><strong>99.5%</strong></td></tr></tbody></table></div>
301+
<div class="explainer-table-wrap"><table class="explainer-table"><thead><tr><th>Approach</th><th>Fairness Gap</th><th>Reduction</th></tr></thead><tbody><tr><td>Biased model</td><td>4.51%</td><td>-</td></tr><tr><td>Remove gender only</td><td>barely moves (age still proxies it)</td><td>Minimal</td></tr><tr><td>Remove gender + proxy</td><td>0.12%</td><td><strong>97.3%</strong></td></tr></tbody></table></div>
303302
<p><strong>The network&#x27;s architecture didn&#x27;t change. The training procedure didn&#x27;t change. Only the inputs changed - and the bias disappeared.</strong></p>
304303
<hr>
305304
<h3 id="how-to-inspect-what-a-network-learned">How to Inspect What a Network Learned</h3>

explainers/neural-networks.md

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -152,9 +152,9 @@ features = [
152152

153153
| Group | Hire Rate |
154154
|---|---|
155-
| Male candidates | 61.2% |
156-
| Female candidates | 40.3% |
157-
| **Fairness gap** | **20.9%** |
155+
| Male candidates | 21.62% |
156+
| Female candidates | 17.10% |
157+
| **Fairness gap** | **4.51 percentage points** |
158158

159159
The network didn't contain a rule that said "prefer men." It learned from historical hiring data in which men were hired more. The weights encoded that pattern. The bias was invisible - buried in floating-point numbers across hidden layers.
160160

@@ -172,21 +172,15 @@ features = [
172172
]
173173
```
174174

175-
**Results:**
176-
177-
| Group | Hire Rate |
178-
|---|---|
179-
| Male candidates | 54.1% |
180-
| Female candidates | 54.0% |
181-
| **Fairness gap** | **0.1%** |
175+
**Result:** the fairness gap closes to **0.12 percentage points**.
182176

183177
### Summary
184178

185179
| Approach | Fairness Gap | Reduction |
186180
|---|---|---|
187-
| Biased model | 20.9% | - |
188-
| Remove gender only | ~18% | Minimal |
189-
| Remove gender + proxy | 0.1% | **99.5%** |
181+
| Biased model | 4.51% | - |
182+
| Remove gender only | barely moves (age still proxies it) | Minimal |
183+
| Remove gender + proxy | 0.12% | **97.3%** |
190184

191185
**The network's architecture didn't change. The training procedure didn't change. Only the inputs changed - and the bias disappeared.**
192186

faircode/_explainers/demographic-parity.md

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -50,21 +50,15 @@ A model trained with gender and age as features assigned hire recommendations at
5050

5151
| Group | Hire Rate |
5252
|---|---|
53-
| Male applicants | ~71% |
54-
| Female applicants | ~50% |
55-
| **Fairness Gap** | **~20.9 percentage points** |
53+
| Male applicants | 21.62% |
54+
| Female applicants | 17.10% |
55+
| **Fairness Gap** | **4.51 percentage points** |
5656

57-
The model was not told to discriminate. It learned to - by treating age as a proxy for gender, because women in the dataset more often had career gaps. Age was correlated with gender, so including it smuggled the gender signal back in even without an explicit gender rule.
58-
59-
After dropping gender and age (the protected attribute and its proxy):
57+
(The disparate-impact ratio here is 17.10 / 21.62 = 0.79, below the 0.80 four-fifths threshold.)
6058

61-
| Group | Hire Rate |
62-
|---|---|
63-
| Male applicants | ~67% |
64-
| Female applicants | ~67% |
65-
| **New Fairness Gap** | **~0.12 percentage points** |
59+
The model was not told to discriminate. It learned to - by treating age as a proxy for gender, because women in the dataset more often had career gaps. Age was correlated with gender, so including it smuggled the gender signal back in even without an explicit gender rule.
6660

67-
**97.3% reduction.** The gap wasn't in the underlying merit of candidates - it was in which features the model was permitted to see.
61+
After dropping gender and age (the protected attribute and its proxy), the gap closes to **0.12 percentage points** - a **97.3% reduction**. The gap wasn't in the underlying merit of candidates - it was in which features the model was permitted to see.
6862

6963
---
7064

faircode/_explainers/disparate-treatment.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ features = ['Gender', 'Age', 'Experience_Years', 'Technical_Test_Score',
6969
'Education_Level', 'Previous_Companies', 'Distance_from_Company']
7070
```
7171

72-
`Gender` is a direct input. `Age` is both an input and a proxy for gender (women in the dataset more often have career gaps, so age encodes gender signal twice over - once directly, once through correlation). The model was *designed* to see these attributes. The 20.9pp hire rate gap is the disparate impact.
72+
`Gender` is a direct input. `Age` is both an input and a proxy for gender (women in the dataset more often have career gaps, so age encodes gender signal twice over - once directly, once through correlation). The model was *designed* to see these attributes. The 4.51pp hire rate gap (21.62% vs 17.10%) is the disparate impact.
7373

7474
### The Fix - What Removing Disparate Treatment Looks Like
7575

faircode/_explainers/neural-networks.md

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -152,9 +152,9 @@ features = [
152152

153153
| Group | Hire Rate |
154154
|---|---|
155-
| Male candidates | 61.2% |
156-
| Female candidates | 40.3% |
157-
| **Fairness gap** | **20.9%** |
155+
| Male candidates | 21.62% |
156+
| Female candidates | 17.10% |
157+
| **Fairness gap** | **4.51 percentage points** |
158158

159159
The network didn't contain a rule that said "prefer men." It learned from historical hiring data in which men were hired more. The weights encoded that pattern. The bias was invisible - buried in floating-point numbers across hidden layers.
160160

@@ -172,21 +172,15 @@ features = [
172172
]
173173
```
174174

175-
**Results:**
176-
177-
| Group | Hire Rate |
178-
|---|---|
179-
| Male candidates | 54.1% |
180-
| Female candidates | 54.0% |
181-
| **Fairness gap** | **0.1%** |
175+
**Result:** the fairness gap closes to **0.12 percentage points**.
182176

183177
### Summary
184178

185179
| Approach | Fairness Gap | Reduction |
186180
|---|---|---|
187-
| Biased model | 20.9% | - |
188-
| Remove gender only | ~18% | Minimal |
189-
| Remove gender + proxy | 0.1% | **99.5%** |
181+
| Biased model | 4.51% | - |
182+
| Remove gender only | barely moves (age still proxies it) | Minimal |
183+
| Remove gender + proxy | 0.12% | **97.3%** |
190184

191185
**The network's architecture didn't change. The training procedure didn't change. Only the inputs changed - and the bias disappeared.**
192186

llms-full.txt

Lines changed: 14 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1207,7 +1207,7 @@ features = ['Gender', 'Age', 'Experience_Years', 'Technical_Test_Score',
12071207
'Education_Level', 'Previous_Companies', 'Distance_from_Company']
12081208
```
12091209

1210-
`Gender` is a direct input. `Age` is both an input and a proxy for gender (women in the dataset more often have career gaps, so age encodes gender signal twice over - once directly, once through correlation). The model was *designed* to see these attributes. The 20.9pp hire rate gap is the disparate impact.
1210+
`Gender` is a direct input. `Age` is both an input and a proxy for gender (women in the dataset more often have career gaps, so age encodes gender signal twice over - once directly, once through correlation). The model was *designed* to see these attributes. The 4.51pp hire rate gap (21.62% vs 17.10%) is the disparate impact.
12111211

12121212
### The Fix - What Removing Disparate Treatment Looks Like
12131213

@@ -1992,21 +1992,15 @@ A model trained with gender and age as features assigned hire recommendations at
19921992

19931993
| Group | Hire Rate |
19941994
|---|---|
1995-
| Male applicants | ~71% |
1996-
| Female applicants | ~50% |
1997-
| **Fairness Gap** | **~20.9 percentage points** |
1995+
| Male applicants | 21.62% |
1996+
| Female applicants | 17.10% |
1997+
| **Fairness Gap** | **4.51 percentage points** |
19981998

1999-
The model was not told to discriminate. It learned to - by treating age as a proxy for gender, because women in the dataset more often had career gaps. Age was correlated with gender, so including it smuggled the gender signal back in even without an explicit gender rule.
2000-
2001-
After dropping gender and age (the protected attribute and its proxy):
1999+
(The disparate-impact ratio here is 17.10 / 21.62 = 0.79, below the 0.80 four-fifths threshold.)
20022000

2003-
| Group | Hire Rate |
2004-
|---|---|
2005-
| Male applicants | ~67% |
2006-
| Female applicants | ~67% |
2007-
| **New Fairness Gap** | **~0.12 percentage points** |
2001+
The model was not told to discriminate. It learned to - by treating age as a proxy for gender, because women in the dataset more often had career gaps. Age was correlated with gender, so including it smuggled the gender signal back in even without an explicit gender rule.
20082002

2009-
**97.3% reduction.** The gap wasn't in the underlying merit of candidates - it was in which features the model was permitted to see.
2003+
After dropping gender and age (the protected attribute and its proxy), the gap closes to **0.12 percentage points** - a **97.3% reduction**. The gap wasn't in the underlying merit of candidates - it was in which features the model was permitted to see.
20102004

20112005
---
20122006

@@ -3728,9 +3722,9 @@ features = [
37283722

37293723
| Group | Hire Rate |
37303724
|---|---|
3731-
| Male candidates | 61.2% |
3732-
| Female candidates | 40.3% |
3733-
| **Fairness gap** | **20.9%** |
3725+
| Male candidates | 21.62% |
3726+
| Female candidates | 17.10% |
3727+
| **Fairness gap** | **4.51 percentage points** |
37343728

37353729
The network didn't contain a rule that said "prefer men." It learned from historical hiring data in which men were hired more. The weights encoded that pattern. The bias was invisible - buried in floating-point numbers across hidden layers.
37363730

@@ -3748,21 +3742,15 @@ features = [
37483742
]
37493743
```
37503744

3751-
**Results:**
3752-
3753-
| Group | Hire Rate |
3754-
|---|---|
3755-
| Male candidates | 54.1% |
3756-
| Female candidates | 54.0% |
3757-
| **Fairness gap** | **0.1%** |
3745+
**Result:** the fairness gap closes to **0.12 percentage points**.
37583746

37593747
### Summary
37603748

37613749
| Approach | Fairness Gap | Reduction |
37623750
|---|---|---|
3763-
| Biased model | 20.9% | - |
3764-
| Remove gender only | ~18% | Minimal |
3765-
| Remove gender + proxy | 0.1% | **99.5%** |
3751+
| Biased model | 4.51% | - |
3752+
| Remove gender only | barely moves (age still proxies it) | Minimal |
3753+
| Remove gender + proxy | 0.12% | **97.3%** |
37663754

37673755
**The network's architecture didn't change. The training procedure didn't change. Only the inputs changed - and the bias disappeared.**
37683756

0 commit comments

Comments
 (0)