Fairness Metrics Specifications
View SourceOverview
This document provides detailed mathematical specifications for all fairness metrics implemented in ExFairness.
Notation
- $Y$: True label
- $\hat{Y}$: Predicted label
- $A$: Sensitive attribute (e.g., race, gender)
- $X$: Feature vector
- $P(\cdot)$: Probability
- $E[\cdot]$: Expectation
Group Fairness Metrics
1. Demographic Parity (Statistical Parity)
Definition: The probability of a positive prediction should be equal across groups.
$$ P(\hat{Y} = 1 | A = 0) = P(\hat{Y} = 1 | A = 1) $$
Disparity Measure: $$ \Delta_{DP} = |P(\hat{Y} = 1 | A = 0) - P(\hat{Y} = 1 | A = 1)| $$
When to Use:
- When equal representation in positive outcomes is required
- Advertising, content recommendation
- When base rates can differ between groups
Limitations:
- Ignores base rate differences in actual outcomes
- May conflict with accuracy if base rates differ
- Can be satisfied by a random classifier
Implementation:
def demographic_parity(predictions, sensitive_attr, opts \\ []) do
threshold = Keyword.get(opts, :threshold, 0.1)
group_a_mask = Nx.equal(sensitive_attr, 0)
group_b_mask = Nx.equal(sensitive_attr, 1)
rate_a = positive_rate(predictions, group_a_mask)
rate_b = positive_rate(predictions, group_b_mask)
disparity = Nx.abs(Nx.subtract(rate_a, rate_b)) |> Nx.to_number()
%{
group_a_rate: Nx.to_number(rate_a),
group_b_rate: Nx.to_number(rate_b),
disparity: disparity,
passes: disparity <= threshold,
threshold: threshold
}
end2. Equalized Odds
Definition: True positive and false positive rates should be equal across groups.
| $$P(\hat{Y} = 1 | Y = 1, A = 0) = P(\hat{Y} = 1 | Y = 1, A = 1)$$ |
| $$P(\hat{Y} = 1 | Y = 0, A = 0) = P(\hat{Y} = 1 | Y = 0, A = 1)$$ |
Disparity Measures: $$ \Delta_{TPR} = |TPR_{A=0} - TPR_{A=1}| $$ $$ \Delta_{FPR} = |FPR_{A=0} - FPR_{A=1}| $$
When to Use:
- When both false positives and false negatives matter
- Criminal justice (both wrongful conviction and wrongful acquittal are serious)
- Medical diagnosis (both missed diagnoses and false alarms matter)
Limitations:
- Requires ground truth labels
- Can be impossible to achieve with demographic parity when base rates differ
- May reduce overall accuracy
Implementation:
def equalized_odds(predictions, labels, sensitive_attr, opts \\ []) do
threshold = Keyword.get(opts, :threshold, 0.1)
group_a_mask = Nx.equal(sensitive_attr, 0)
group_b_mask = Nx.equal(sensitive_attr, 1)
# Group A
tpr_a = true_positive_rate(predictions, labels, group_a_mask)
fpr_a = false_positive_rate(predictions, labels, group_a_mask)
# Group B
tpr_b = true_positive_rate(predictions, labels, group_b_mask)
fpr_b = false_positive_rate(predictions, labels, group_b_mask)
tpr_disparity = Nx.abs(Nx.subtract(tpr_a, tpr_b)) |> Nx.to_number()
fpr_disparity = Nx.abs(Nx.subtract(fpr_a, fpr_b)) |> Nx.to_number()
%{
group_a_tpr: Nx.to_number(tpr_a),
group_b_tpr: Nx.to_number(tpr_b),
group_a_fpr: Nx.to_number(fpr_a),
group_b_fpr: Nx.to_number(fpr_b),
tpr_disparity: tpr_disparity,
fpr_disparity: fpr_disparity,
passes: tpr_disparity <= threshold and fpr_disparity <= threshold
}
end3. Equal Opportunity
Definition: True positive rate (recall) should be equal across groups.
$$ P(\hat{Y} = 1 | Y = 1, A = 0) = P(\hat{Y} = 1 | Y = 1, A = 1) $$
Disparity Measure: $$ \Delta_{EO} = |TPR_{A=0} - TPR_{A=1}| $$
When to Use:
- When the cost of false negatives varies by group
- Hiring (missing qualified candidates)
- College admissions
- Opportunity allocation
Limitations:
- Only considers true positive rate, ignores false positive rate
- Requires ground truth labels
- May allow different false positive rates
Implementation:
def equal_opportunity(predictions, labels, sensitive_attr, opts \\ []) do
threshold = Keyword.get(opts, :threshold, 0.1)
group_a_mask = Nx.equal(sensitive_attr, 0)
group_b_mask = Nx.equal(sensitive_attr, 1)
tpr_a = true_positive_rate(predictions, labels, group_a_mask)
tpr_b = true_positive_rate(predictions, labels, group_b_mask)
disparity = Nx.abs(Nx.subtract(tpr_a, tpr_b)) |> Nx.to_number()
%{
group_a_tpr: Nx.to_number(tpr_a),
group_b_tpr: Nx.to_number(tpr_b),
disparity: disparity,
passes: disparity <= threshold,
interpretation: interpret_equal_opportunity(disparity, threshold)
}
end4. Predictive Parity (Outcome Test)
Definition: Positive predictive value (precision) should be equal across groups.
$$ P(Y = 1 | \hat{Y} = 1, A = 0) = P(Y = 1 | \hat{Y} = 1, A = 1) $$
Disparity Measure: $$ \Delta_{PP} = |PPV_{A=0} - PPV_{A=1}| $$
When to Use:
- When the meaning of a positive prediction should be consistent
- Risk assessment tools
- Credit scoring
Limitations:
- Can be incompatible with equalized odds when base rates differ
- Requires ground truth labels
- May allow different selection rates
5. Calibration
Definition: For any predicted probability, actual outcomes should be equal across groups.
$$ P(Y = 1 | S(X) = s, A = 0) = P(Y = 1 | S(X) = s, A = 1) $$
where $S(X)$ is the model's score function.
Disparity Measure (per bin): $$ \Delta_{Cal}(b) = |P(Y = 1 | S(X) \in bin_b, A = 0) - P(Y = 1 | S(X) \in bin_b, A = 1)| $$
When to Use:
- When probability estimates must be interpretable
- Medical risk prediction
- Weather forecasting
- Any application where probabilities guide decisions
Implementation:
def calibration(probabilities, labels, sensitive_attr, opts \\ []) do
bins = Keyword.get(opts, :bins, 10)
threshold = Keyword.get(opts, :threshold, 0.1)
group_a_mask = Nx.equal(sensitive_attr, 0)
group_b_mask = Nx.equal(sensitive_attr, 1)
calibration_a = compute_calibration_curve(probabilities, labels, group_a_mask, bins)
calibration_b = compute_calibration_curve(probabilities, labels, group_b_mask, bins)
disparities = Enum.zip(calibration_a, calibration_b)
|> Enum.map(fn {a, b} -> abs(a - b) end)
max_disparity = Enum.max(disparities)
%{
calibration_a: calibration_a,
calibration_b: calibration_b,
disparities: disparities,
max_disparity: max_disparity,
passes: max_disparity <= threshold
}
endIndividual Fairness Metrics
6. Individual Fairness (Lipschitz Continuity)
Definition: Similar individuals should receive similar predictions.
$$ d(\hat{Y}(x_1), \hat{Y}(x_2)) \leq L \cdot d(x_1, x_2) $$
where $L$ is the Lipschitz constant and $d$ is a distance metric.
Measurement: For a set of similar pairs $(x_i, x_j)$: $$ \text{Fairness} = \frac{1}{|P|} \sum_{(i,j) \in P} \mathbb{1}[|f(x_i) - f(x_j)| \leq \epsilon] $$
When to Use:
- When individual treatment is important
- Personalized recommendations
- Custom pricing
Challenges:
- Requires defining similarity metric
- Computationally expensive for large datasets
- Similarity metric may be domain-specific
7. Counterfactual Fairness
Definition: A prediction is counterfactually fair if it is the same in the actual world and in a counterfactual world where the sensitive attribute is different.
$$ P(\hat{Y}_{A \leftarrow a}(U) = y | X = x, A = a) = P(\hat{Y}_{A \leftarrow a'}(U) = y | X = x, A = a) $$
When to Use:
- When causal understanding is important
- Legal compliance (disparate treatment)
- High-stakes decisions
Challenges:
- Requires causal graph
- Unobserved confounders problematic
- Computationally intensive
Disparate Impact Measures
80% Rule (4/5ths Rule)
Definition: The selection rate for the protected group should be at least 80% of the selection rate for the reference group.
$$ \frac{P(\hat{Y} = 1 | A = 1)}{P(\hat{Y} = 1 | A = 0)} \geq 0.8 $$
Legal Context: Used by EEOC in employment discrimination cases.
Implementation:
def disparate_impact(predictions, sensitive_attr) do
group_a_rate = positive_rate(predictions, Nx.equal(sensitive_attr, 0))
group_b_rate = positive_rate(predictions, Nx.equal(sensitive_attr, 1))
ratio = Nx.divide(group_b_rate, group_a_rate) |> Nx.to_number()
%{
ratio: ratio,
passes_80_percent_rule: ratio >= 0.8,
interpretation: interpret_disparate_impact(ratio)
}
endImpossibility Theorems
Chouldechova's Theorem
Statement: If base rates differ between groups ($P(Y=1|A=0) \neq P(Y=1|A=1)$), it is impossible to simultaneously satisfy:
- Predictive parity
- Equal false positive rates
- Equal false negative rates
Implication: Must choose which fairness definition to prioritize.
Kleinberg et al. Theorem
Statement: Except in degenerate cases, it is impossible to simultaneously satisfy:
- Calibration
- Balance for the positive class (equal TPR)
- Balance for the negative class (equal TNR)
Implication: Trade-offs are necessary when base rates differ.
Metric Selection Guide
graph TD
A[Start] --> B{Ground truth available?}
B -->|No| C[Demographic Parity]
B -->|Yes| D{What matters most?}
D -->|Equal opportunity| E[Equal Opportunity]
D -->|Both error types| F[Equalized Odds]
D -->|Prediction meaning| G[Predictive Parity]
D -->|Probability interpretation| H[Calibration]
D -->|Individual treatment| I[Individual Fairness]
D -->|Causal reasoning| J[Counterfactual Fairness]
C --> K{Legal compliance?}
K -->|Yes| L[Add 80% rule check]
K -->|No| M[Use demographic parity]
E --> N[Check impossibility theorems]
F --> N
G --> N
H --> NIntersectional Fairness
For multiple sensitive attributes $(A_1, A_2, ..., A_k)$, fairness metrics can be extended:
Additive Intersectionality: Measure fairness for each attribute independently.
Multiplicative Intersectionality: Measure fairness for all combinations of attributes.
Example: Gender × Race creates 4 groups: (Male, White), (Male, Black), (Female, White), (Female, Black)
Confidence Intervals
All metrics should include confidence intervals:
Bootstrap Method:
def demographic_parity_with_ci(predictions, sensitive_attr, opts \\ []) do
result = demographic_parity(predictions, sensitive_attr, opts)
# Bootstrap CI
bootstrap_samples = Keyword.get(opts, :bootstrap_samples, 1000)
ci_level = Keyword.get(opts, :confidence_level, 0.95)
bootstrap_disparities = bootstrap(predictions, sensitive_attr, bootstrap_samples, fn p, s ->
demographic_parity(p, s).disparity
end)
ci = percentile_ci(bootstrap_disparities, ci_level)
Map.put(result, :confidence_interval, ci)
endReferences
- Hardt, M., Price, E., & Srebro, N. (2016). Equality of opportunity in supervised learning. NeurIPS.
- Chouldechova, A. (2017). Fair prediction with disparate impact. Big Data, 5(2), 153-163.
- Kleinberg, J., Mullainathan, S., & Raghavan, M. (2016). Inherent trade-offs in the fair determination of risk scores. ITCS.
- Dwork, C., Hardt, M., Pitassi, T., Reingold, O., & Zemel, R. (2012). Fairness through awareness. ITCS.
- Kusner, M. J., Loftus, J., Russell, C., & Silva, R. (2017). Counterfactual fairness. NeurIPS.