Equalized Odds API Reference¶
Equalized Odds detects shortcuts by measuring True Positive Rate (TPR) and False Positive Rate (FPR) disparities across protected groups. Based on Hardt et al. (2016), this method checks whether predictions satisfy fairness constraints across demographic groups.
This page documents the standalone EqualizedOddsDetector and its integration with the unified ShortcutDetector API.
EqualizedOddsDetector¶
Description¶
EqualizedOddsDetector trains a lightweight classifier on embeddings and computes per-group TPR and FPR metrics.
Large gaps between groups indicate that embeddings encode shortcuts correlated with protected attributes.
Constructor¶
EqualizedOddsDetector(
estimator: Optional[LogisticRegression] = None,
min_group_size: int = 10,
tpr_gap_threshold: float = 0.1,
fpr_gap_threshold: float = 0.1
)
Parameters¶
| Name | Type | Default | Description |
|---|---|---|---|
estimator |
Estimator, optional | LogisticRegression(max_iter=1000) | Classifier to train on embeddings |
min_group_size |
int | 10 | Minimum samples per group; smaller groups get NaN metrics |
tpr_gap_threshold |
float | 0.1 | Threshold for flagging TPR disparity |
fpr_gap_threshold |
float | 0.1 | Threshold for flagging FPR disparity |
Methods¶
fit¶
fit(
embeddings: np.ndarray,
labels: np.ndarray,
group_labels: np.ndarray
) -> EqualizedOddsDetector
Parameters¶
| Name | Type | Description |
|---|---|---|
embeddings |
ndarray (n_samples, d) |
Precomputed embeddings |
labels |
ndarray (n_samples,) |
Binary task labels (required) |
group_labels |
ndarray (n_samples,) |
Group membership (required) |
Returns¶
self
Raises¶
ValueErrorifgroup_labelsis not providedValueErroriflabelsis not binaryValueErrorif shapes don't align
Attributes (after fit)¶
| Attribute | Type | Description |
|---|---|---|
group_metrics_ |
dict | Per-group TPR, FPR, support, and confusion matrix counts |
tpr_gap_ |
float | Max TPR - Min TPR across groups |
fpr_gap_ |
float | Max FPR - Min FPR across groups |
overall_accuracy_ |
float | Classifier accuracy on all samples |
report_ |
EqualizedOddsReport | Structured report object |
EqualizedOddsReport¶
Dataclass containing the analysis results.
Fields¶
| Field | Type | Description |
|---|---|---|
group_metrics |
dict | Per-group metrics (TPR, FPR, support, tp, fp, tn, fn) |
tpr_gap |
float | Maximum TPR gap across groups |
fpr_gap |
float | Maximum FPR gap across groups |
overall_accuracy |
float | Overall classifier accuracy |
risk_level |
str | "low", "moderate", or "high" |
notes |
str | Human-readable interpretation |
reference |
str | "Hardt et al. 2016" |
Group Metrics Structure¶
For each group in group_metrics:
{
"group_id": {
"tpr": 0.85, # True Positive Rate
"fpr": 0.12, # False Positive Rate
"support": 150, # Number of samples
"tp": 85, # True Positives
"fp": 12, # False Positives
"tn": 88, # True Negatives
"fn": 15 # False Negatives
}
}
Risk Assessment¶
| Risk Level | Condition | Description |
|---|---|---|
| Low | Both gaps < threshold | Equalized odds approximately satisfied |
| Moderate | Any gap >= threshold | Noticeable disparity |
| High | Any gap >= 2x threshold | Large disparity, strong shortcut evidence |
Default thresholds: tpr_gap_threshold=0.1, fpr_gap_threshold=0.1
Unified API Integration¶
Equalized Odds is also accessible via ShortcutDetector:
from shortcut_detect import ShortcutDetector
detector = ShortcutDetector(methods=["equalized_odds"])
detector.fit(embeddings, labels, group_labels=groups)
results = detector.get_results()["equalized_odds"]
print(results["tpr_gap"])
print(results["fpr_gap"])
print(results["risk_level"])
All reports and visualizations are automatically integrated into:
detector.summary()- HTML / PDF / Markdown reports
- CSV export
Example¶
from shortcut_detect.fairness import EqualizedOddsDetector
import numpy as np
# Sample data
np.random.seed(42)
embeddings = np.random.randn(500, 20)
labels = (embeddings[:, 0] > 0).astype(int)
groups = np.array([0] * 250 + [1] * 250)
# Fit detector
detector = EqualizedOddsDetector(
tpr_gap_threshold=0.1,
fpr_gap_threshold=0.1
)
detector.fit(embeddings, labels, groups)
# Access results
print(f"TPR Gap: {detector.tpr_gap_:.3f}")
print(f"FPR Gap: {detector.fpr_gap_:.3f}")
print(f"Risk: {detector.report_.risk_level}")
# Per-group metrics
for group, metrics in detector.group_metrics_.items():
print(f"Group {group}: TPR={metrics['tpr']:.3f}, FPR={metrics['fpr']:.3f}")
Notes and Caveats¶
- Binary labels are required. Multi-class is not supported.
- Group labels are required. Cannot operate without them.
- Small groups (<
min_group_size) will have NaN metrics. - Measures correlation, not causation—use alongside other methods.