From 4c69af9988d312d259a1bd8878ffaa8cdd66113d Mon Sep 17 00:00:00 2001 From: propcgamer20-png Date: Thu, 10 Sep 2026 01:48:44 +0530 Subject: [PATCH] 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 --- explainers/demographic-parity.html | 7 ++-- explainers/demographic-parity.md | 18 ++++------ explainers/disparate-treatment.html | 2 +- explainers/disparate-treatment.md | 2 +- explainers/neural-networks.html | 7 ++-- explainers/neural-networks.md | 20 ++++------- faircode/_explainers/demographic-parity.md | 18 ++++------ faircode/_explainers/disparate-treatment.md | 2 +- faircode/_explainers/neural-networks.md | 20 ++++------- llms-full.txt | 40 ++++++++------------- 10 files changed, 49 insertions(+), 87 deletions(-) diff --git a/explainers/demographic-parity.html b/explainers/demographic-parity.html index 60eecf8..1a442ea 100644 --- a/explainers/demographic-parity.html +++ b/explainers/demographic-parity.html @@ -211,11 +211,10 @@

The Formal Definition

Real-World Proof: Hiring Bias

The AI Fair Recruitment audit in this repo is a direct illustration of a demographic parity violation - and its fix.

A model trained with gender and age as features assigned hire recommendations at sharply different rates:

-
GroupHire Rate
Male applicants~71%
Female applicants~50%
Fairness Gap~20.9 percentage points
+
GroupHire Rate
Male applicants21.62%
Female applicants17.10%
Fairness Gap4.51 percentage points
+

(The disparate-impact ratio here is 17.10 / 21.62 = 0.79, below the 0.80 four-fifths threshold.)

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.

-

After dropping gender and age (the protected attribute and its proxy):

-
GroupHire Rate
Male applicants~67%
Female applicants~67%
New Fairness Gap~0.12 percentage points
-

97.3% reduction. The gap wasn't in the underlying merit of candidates - it was in which features the model was permitted to see.

+

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.


Detection Code

Measure demographic parity gap

diff --git a/explainers/demographic-parity.md b/explainers/demographic-parity.md index 300ad1e..00401dd 100644 --- a/explainers/demographic-parity.md +++ b/explainers/demographic-parity.md @@ -50,21 +50,15 @@ A model trained with gender and age as features assigned hire recommendations at | Group | Hire Rate | |---|---| -| Male applicants | ~71% | -| Female applicants | ~50% | -| **Fairness Gap** | **~20.9 percentage points** | +| Male applicants | 21.62% | +| Female applicants | 17.10% | +| **Fairness Gap** | **4.51 percentage points** | -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. - -After dropping gender and age (the protected attribute and its proxy): +(The disparate-impact ratio here is 17.10 / 21.62 = 0.79, below the 0.80 four-fifths threshold.) -| Group | Hire Rate | -|---|---| -| Male applicants | ~67% | -| Female applicants | ~67% | -| **New Fairness Gap** | **~0.12 percentage points** | +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. -**97.3% reduction.** The gap wasn't in the underlying merit of candidates - it was in which features the model was permitted to see. +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. --- diff --git a/explainers/disparate-treatment.html b/explainers/disparate-treatment.html index 8a3325f..b276303 100644 --- a/explainers/disparate-treatment.html +++ b/explainers/disparate-treatment.html @@ -221,7 +221,7 @@

AI Fair Recruitment - unfair.py# DISPARATE TREATMENT: Gender and Age are direct model features features = ['Gender', 'Age', 'Experience_Years', 'Technical_Test_Score', 'Education_Level', 'Previous_Companies', 'Distance_from_Company'] -

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.

+

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.

The Fix - What Removing Disparate Treatment Looks Like

# fair.py: protected attribute and its proxy removed
 features = ['Experience_Years', 'Technical_Test_Score']
diff --git a/explainers/disparate-treatment.md b/explainers/disparate-treatment.md
index fe9fb96..62403bb 100644
--- a/explainers/disparate-treatment.md
+++ b/explainers/disparate-treatment.md
@@ -69,7 +69,7 @@ features = ['Gender', 'Age', 'Experience_Years', 'Technical_Test_Score',
             'Education_Level', 'Previous_Companies', 'Distance_from_Company']
 ```
 
-`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.
+`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.
 
 ### The Fix - What Removing Disparate Treatment Looks Like
 
diff --git a/explainers/neural-networks.html b/explainers/neural-networks.html
index 96210e8..0ba23c8 100644
--- a/explainers/neural-networks.html
+++ b/explainers/neural-networks.html
@@ -285,7 +285,7 @@ 

What We Did

'communication_score' ]

Results:

-
GroupHire Rate
Male candidates61.2%
Female candidates40.3%
Fairness gap20.9%
+
GroupHire Rate
Male candidates21.62%
Female candidates17.10%
Fairness gap4.51 percentage points

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.


Step 2 - Remove gender + proxy (our fix):

@@ -296,10 +296,9 @@

What We Did

'technical_score', 'communication_score' ]
-

Results:

-
GroupHire Rate
Male candidates54.1%
Female candidates54.0%
Fairness gap0.1%
+

Result: the fairness gap closes to 0.12 percentage points.

Summary

-
ApproachFairness GapReduction
Biased model20.9%-
Remove gender only~18%Minimal
Remove gender + proxy0.1%99.5%
+
ApproachFairness GapReduction
Biased model4.51%-
Remove gender onlybarely moves (age still proxies it)Minimal
Remove gender + proxy0.12%97.3%

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


How to Inspect What a Network Learned

diff --git a/explainers/neural-networks.md b/explainers/neural-networks.md index 6947a7a..16b1da3 100644 --- a/explainers/neural-networks.md +++ b/explainers/neural-networks.md @@ -152,9 +152,9 @@ features = [ | Group | Hire Rate | |---|---| -| Male candidates | 61.2% | -| Female candidates | 40.3% | -| **Fairness gap** | **20.9%** | +| Male candidates | 21.62% | +| Female candidates | 17.10% | +| **Fairness gap** | **4.51 percentage points** | 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. @@ -172,21 +172,15 @@ features = [ ] ``` -**Results:** - -| Group | Hire Rate | -|---|---| -| Male candidates | 54.1% | -| Female candidates | 54.0% | -| **Fairness gap** | **0.1%** | +**Result:** the fairness gap closes to **0.12 percentage points**. ### Summary | Approach | Fairness Gap | Reduction | |---|---|---| -| Biased model | 20.9% | - | -| Remove gender only | ~18% | Minimal | -| Remove gender + proxy | 0.1% | **99.5%** | +| Biased model | 4.51% | - | +| Remove gender only | barely moves (age still proxies it) | Minimal | +| Remove gender + proxy | 0.12% | **97.3%** | **The network's architecture didn't change. The training procedure didn't change. Only the inputs changed - and the bias disappeared.** diff --git a/faircode/_explainers/demographic-parity.md b/faircode/_explainers/demographic-parity.md index 300ad1e..00401dd 100644 --- a/faircode/_explainers/demographic-parity.md +++ b/faircode/_explainers/demographic-parity.md @@ -50,21 +50,15 @@ A model trained with gender and age as features assigned hire recommendations at | Group | Hire Rate | |---|---| -| Male applicants | ~71% | -| Female applicants | ~50% | -| **Fairness Gap** | **~20.9 percentage points** | +| Male applicants | 21.62% | +| Female applicants | 17.10% | +| **Fairness Gap** | **4.51 percentage points** | -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. - -After dropping gender and age (the protected attribute and its proxy): +(The disparate-impact ratio here is 17.10 / 21.62 = 0.79, below the 0.80 four-fifths threshold.) -| Group | Hire Rate | -|---|---| -| Male applicants | ~67% | -| Female applicants | ~67% | -| **New Fairness Gap** | **~0.12 percentage points** | +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. -**97.3% reduction.** The gap wasn't in the underlying merit of candidates - it was in which features the model was permitted to see. +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. --- diff --git a/faircode/_explainers/disparate-treatment.md b/faircode/_explainers/disparate-treatment.md index fe9fb96..62403bb 100644 --- a/faircode/_explainers/disparate-treatment.md +++ b/faircode/_explainers/disparate-treatment.md @@ -69,7 +69,7 @@ features = ['Gender', 'Age', 'Experience_Years', 'Technical_Test_Score', 'Education_Level', 'Previous_Companies', 'Distance_from_Company'] ``` -`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. +`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. ### The Fix - What Removing Disparate Treatment Looks Like diff --git a/faircode/_explainers/neural-networks.md b/faircode/_explainers/neural-networks.md index 6947a7a..16b1da3 100644 --- a/faircode/_explainers/neural-networks.md +++ b/faircode/_explainers/neural-networks.md @@ -152,9 +152,9 @@ features = [ | Group | Hire Rate | |---|---| -| Male candidates | 61.2% | -| Female candidates | 40.3% | -| **Fairness gap** | **20.9%** | +| Male candidates | 21.62% | +| Female candidates | 17.10% | +| **Fairness gap** | **4.51 percentage points** | 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. @@ -172,21 +172,15 @@ features = [ ] ``` -**Results:** - -| Group | Hire Rate | -|---|---| -| Male candidates | 54.1% | -| Female candidates | 54.0% | -| **Fairness gap** | **0.1%** | +**Result:** the fairness gap closes to **0.12 percentage points**. ### Summary | Approach | Fairness Gap | Reduction | |---|---|---| -| Biased model | 20.9% | - | -| Remove gender only | ~18% | Minimal | -| Remove gender + proxy | 0.1% | **99.5%** | +| Biased model | 4.51% | - | +| Remove gender only | barely moves (age still proxies it) | Minimal | +| Remove gender + proxy | 0.12% | **97.3%** | **The network's architecture didn't change. The training procedure didn't change. Only the inputs changed - and the bias disappeared.** diff --git a/llms-full.txt b/llms-full.txt index a99002b..852dab3 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -1207,7 +1207,7 @@ features = ['Gender', 'Age', 'Experience_Years', 'Technical_Test_Score', 'Education_Level', 'Previous_Companies', 'Distance_from_Company'] ``` -`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. +`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. ### The Fix - What Removing Disparate Treatment Looks Like @@ -1992,21 +1992,15 @@ A model trained with gender and age as features assigned hire recommendations at | Group | Hire Rate | |---|---| -| Male applicants | ~71% | -| Female applicants | ~50% | -| **Fairness Gap** | **~20.9 percentage points** | +| Male applicants | 21.62% | +| Female applicants | 17.10% | +| **Fairness Gap** | **4.51 percentage points** | -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. - -After dropping gender and age (the protected attribute and its proxy): +(The disparate-impact ratio here is 17.10 / 21.62 = 0.79, below the 0.80 four-fifths threshold.) -| Group | Hire Rate | -|---|---| -| Male applicants | ~67% | -| Female applicants | ~67% | -| **New Fairness Gap** | **~0.12 percentage points** | +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. -**97.3% reduction.** The gap wasn't in the underlying merit of candidates - it was in which features the model was permitted to see. +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. --- @@ -3728,9 +3722,9 @@ features = [ | Group | Hire Rate | |---|---| -| Male candidates | 61.2% | -| Female candidates | 40.3% | -| **Fairness gap** | **20.9%** | +| Male candidates | 21.62% | +| Female candidates | 17.10% | +| **Fairness gap** | **4.51 percentage points** | 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. @@ -3748,21 +3742,15 @@ features = [ ] ``` -**Results:** - -| Group | Hire Rate | -|---|---| -| Male candidates | 54.1% | -| Female candidates | 54.0% | -| **Fairness gap** | **0.1%** | +**Result:** the fairness gap closes to **0.12 percentage points**. ### Summary | Approach | Fairness Gap | Reduction | |---|---|---| -| Biased model | 20.9% | - | -| Remove gender only | ~18% | Minimal | -| Remove gender + proxy | 0.1% | **99.5%** | +| Biased model | 4.51% | - | +| Remove gender only | barely moves (age still proxies it) | Minimal | +| Remove gender + proxy | 0.12% | **97.3%** | **The network's architecture didn't change. The training procedure didn't change. Only the inputs changed - and the bias disappeared.**