Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
A direct command skips the review prompt. Inspect the source before running it.
One-line summary: Audit ML models for algorithmic fairness violations using demographic parity, equalized odds, and calibration metrics; apply philosophical frameworks to AI value alignment problems.
When to Use This Skill
When measuring demographic parity, equalized odds, or calibration gaps in models
When auditing hiring, credit, or criminal justice AI systems for disparate impact
When analyzing trade-offs between competing fairness notions
When applying utilitarian, deontological, or virtue ethics frameworks to AI decisions
When designing fairness-aware reweighting or threshold-adjustment interventions
When writing ethics impact assessments for AI systems
Trigger keywords: AI ethics, algorithmic fairness, bias audit, demographic parity, equalized odds, disparate impact, fairness metric, value alignment, moral philosophy, utilitarian, deontological, discrimination, protected attribute, AIF360, fairlearn
Background & Key Concepts
Fairness Definitions
Metric
Definition
Formula
Demographic Parity
Equal selection rates
$P(\hat{Y}=1
Equal Opportunity
Equal TPR across groups
$P(\hat{Y}=1
Equalized Odds
Equal TPR and FPR
Both TPR and FPR equal across groups
Calibration
Equal predicted probabilities match actual rates
$P(Y=1
Impossibility theorem (Chouldechova, 2017): When base rates differ, no classifier can simultaneously satisfy calibration, equal FPR, and equal FPR. Choose the metric appropriate to the decision context.
Disparate Impact (80% Rule)
EEOC 4/5ths rule: selection rate for any protected group should not be less than 80% of the rate for the highest-selected group.
$$
DI = \frac{P(\hat{Y}=1|A=\text{protected})}{P(\hat{Y}=1|A=\text{majority})}
$$
import numpy as np
import matplotlib.pyplot as plt
from sklearn.metrics import roc_curve
# ------------------------------------------------------------------ ## Post-processing threshold adjustment for equalized odds# (Hardt et al., 2016)# ------------------------------------------------------------------ #deffind_threshold_equalized_odds(y_true, y_prob, sensitive, majority=0, minority=1):
"""
Find group-specific classification thresholds to achieve equalized odds.
Returns (threshold_majority, threshold_minority).
"""from sklearn.metrics import roc_curve
# ROC curves by group
fpr_maj, tpr_maj, thr_maj = roc_curve(
y_true[sensitive==majority], y_prob[sensitive==majority])
fpr_min, tpr_min, thr_min = roc_curve(
y_true[sensitive==minority], y_prob[sensitive==minority])
# Target operating point: equalize TPR at 0.70
target_tpr = 0.70deffind_threshold_at_tpr(fpr, tpr, thr, target):
idx = np.argmin(np.abs(tpr - target))
return thr[idx], fpr[idx], tpr[idx]
t_maj, fpr_at_t_maj, tpr_at_t_maj = find_threshold_at_tpr(fpr_maj, tpr_maj, thr_maj, target_tpr)
t_min, fpr_at_t_min, tpr_at_t_min = find_threshold_at_tpr(fpr_min, tpr_min, thr_min, target_tpr)
return t_maj, t_min, (fpr_at_t_maj, tpr_at_t_maj), (fpr_at_t_min, tpr_at_t_min)
g_test_arr = g_test.values
t_maj, t_min, op_maj, op_min = find_threshold_equalized_odds(
y_test.values, y_prob, g_test_arr)
# Apply group-specific thresholds
y_pred_adj = np.where(g_test_arr == 0,
(y_prob >= t_maj).astype(int),
(y_prob >= t_min).astype(int))
# Compare original vs. adjustedfor method, preds, name in [(y_pred, 'Original (uniform threshold)'),
(y_pred_adj, 'Adjusted (group thresholds)')]:
print(f"\n{name}:")
for g, gn in [(0, 'Majority'), (1, 'Minority')]:
mask = g_test_arr == g
tp_mask = y_test.values[mask] == 1
tpr = preds[mask][tp_mask].mean() if tp_mask.sum() > 0else0
sel = preds[mask].mean()
acc = np.mean(preds[mask] == y_test.values[mask])
print(f" {gn}: sel={sel:.3f}, TPR={tpr:.3f}, acc={acc:.3f}")
# Plot ROC curves with operating points
fig, ax = plt.subplots(figsize=(7, 6))
from sklearn.metrics import roc_curve as roc_c
for g, gn, color in [(0,'Majority','#3498db'), (1,'Minority','#e74c3c')]:
fpr, tpr, _ = roc_c(y_test.values[g_test_arr==g], y_prob[g_test_arr==g])
ax.plot(fpr, tpr, color=color, linewidth=2, label=gn)
if g == 0:
ax.plot(op_maj[0], op_maj[1], 'o', color=color, markersize=10,
label=f'{gn} threshold (TPR={op_maj[1]:.2f})')
else:
ax.plot(op_min[0], op_min[1], 's', color=color, markersize=10,
label=f'{gn} threshold (TPR={op_min[1]:.2f})')
ax.plot([0,1],[0,1],'k--',alpha=0.5)
ax.set_xlabel("FPR"); ax.set_ylabel("TPR"); ax.set_title("ROC Curves — Equalized Odds Adjustment")
ax.legend(fontsize=9); ax.grid(alpha=0.3)
plt.tight_layout(); plt.savefig("equalized_odds.png", dpi=150); plt.show()
Step 3: Philosophical Framework Analysis
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# ------------------------------------------------------------------ ## Apply three moral frameworks to a concrete AI ethics scenario:# Should an AI parole board system deny release to a high-risk individual?# ------------------------------------------------------------------ #print("""
=== AI Ethics Analysis: Automated Parole Decision System ===
SCENARIO: An AI system predicts recidivism risk. A defendant
is classified as "high risk" (predicted probability = 0.72).
The base rate for this demographic group is 0.35. The model
has a false positive rate of 0.28 for this group.
QUESTION: Should the parole board rely on this AI recommendation?
""")
frameworks = {
"Utilitarian Analysis": {
"core_principle": "Maximize aggregate welfare; minimize harm across all stakeholders.",
"relevant_facts": [
"If high-risk prediction correct (72% confidence): denying parole prevents ~0.72 recidivism events",
"False positive rate 28%: 28% of actual non-recidivists in this group are incorrectly flagged",
"Incarceration costs ~$35,000/year; victim costs of crime vary widely",
"Evidence suggests incarceration has mixed effects on recidivism long-term",
],
"analysis": """
Expected utility calculation:
- Let p=0.72 (predicted recid. prob), p_true≈0.35 (base rate)
- Expected harm prevented = p_true × harm_of_crime
- Expected harm imposed = (1-p_true) × cost_of_unjust_incarceration
At base rate 0.35: denying parole has 65% chance of being wrong.
Utilitarian calculation depends on harm weights — not obvious that denial maximizes welfare.
HIGH-RISK DESIGNATION IS NOT SUFFICIENT for utilitarian justification.
""",
"verdict": "CONTEXT-DEPENDENT (depends on harm magnitudes)",
},
"Deontological Analysis": {
"core_principle": "Respect persons as ends; follow categorical duties regardless of outcomes.",
"relevant_facts": [
"Individual has right to liberty and due process",
"Statistical prediction punishes based on group membership, not individual action",
"Kant's categorical imperative: act only on maxims you could universalize",
"False positives violate individuals' rights even if outcomes-good aggregate",
],
"analysis": """
Kantian objections to statistical risk prediction:
1. Treats individuals as means (to aggregate crime reduction) not ends
2. Statistical group-based predictions cannot be universalized without contradiction
3. Due process requires individual-level evidence, not probabilistic grouping
4. High FPR (28%) means systematic rights violations for the innocent
The Rawlsian difference principle: acceptable only if worst-off benefit.
Biased FPR (if higher for minorities) fails Rawlsian justice.
""",
"verdict": "REJECT AI-ONLY DECISION (rights violations are impermissible)",
},
"Virtue Ethics Analysis": {
"core_principle": "What would a person of good character (prudent, just, compassionate) do?",
"relevant_facts": [
"A just judge weighs individual circumstances, not just actuarial scores",
"Prudence requires understanding model limitations and error rates",
"Compassion requires considering the human cost of false positives",
"Epistemic humility: model ≠ ground truth about future behavior",
],
"analysis": """
Virtues applied:
- Justice: mechanical application of risk score without deliberation lacks justice
- Prudence: a prudent judge uses the score as one input, not the final word
- Integrity: honest acknowledgment that 28% FPR means significant uncertainty
- Compassion: humanizes the defendant beyond statistical categories
The virtuous judge treats AI as a tool for reflection, not delegation.
""",
"verdict": "USE AS ADVISORY INPUT ONLY (not decisive)",
},
}
for name, framework in frameworks.items():
print(f"\n{'='*60}")
print(f" {name}")
print(f"{'='*60}")
print(f"Core principle: {framework['core_principle']}")
print("\nKey facts:")
for fact in framework['relevant_facts']:
print(f" • {fact}")
print(f"\nAnalysis: {framework['analysis']}")
print(f"\nVERDICT: {framework['verdict']}")
# ---- Summary visualization -------------------------------------- #
fig, ax = plt.subplots(figsize=(10, 4))
framework_names = list(frameworks.keys())
verdicts = [f['verdict'].split('(')[0].strip() for f in frameworks.values()]
colors = ['#f39c12', '#e74c3c', '#2ecc71']
y_pos = range(len(framework_names))
for pos, (name, verdict, color) inenumerate(zip(framework_names, verdicts, colors)):
ax.barh(pos, 1, left=0, color=color, height=0.6, alpha=0.8, edgecolor='black', linewidth=0.7)
ax.text(0.5, pos, verdict, ha='center', va='center', fontsize=11, fontweight='bold')
ax.set_yticks(y_pos)
ax.set_yticklabels([n.replace(' Analysis','') for n in framework_names], fontsize=10)
ax.set_xlim(0, 1); ax.set_xticks([])
ax.set_title("Philosophical Framework Analysis:\nAI Parole Decision System")
ax.grid(False)
plt.tight_layout()
plt.savefig("ethics_frameworks.png", dpi=150)
plt.show()
When fairness metrics conflict, it's expected (not a code bug):
# If base rates differ significantly, equalized odds AND calibration CANNOT both holdprint(f"Group 0 base rate: {y_test.values[g_test_arr==0].mean():.3f}")
print(f"Group 1 base rate: {y_test.values[g_test_arr==1].mean():.3f}")
# Large difference → fairness metrics will be in tension
Chouldechova, A. (2017). Fair prediction with disparate impact. Big Data.
Hardt, M., Price, E. & Srebro, N. (2016). Equality of opportunity in supervised learning. NeurIPS.
Dwork, C. et al. (2012). Fairness through awareness. ITCS.
Examples
Example 1: Counterfactual Fairness Check
import numpy as np
import pandas as pd
from sklearn.linear_model import LogisticRegression
# Counterfactual fairness: would the outcome change if group membership were flipped?
np.random.seed(42)
X_cf = X_test.copy()
y_pred_cf_0 = clf.predict_proba(X_cf[g_test_arr==1])[:,1] # Minority features
y_pred_orig_1 = y_prob[g_test_arr==1]
# How much does predicted probability change on average?
cf_change = np.abs(y_pred_cf_0 - y_pred_orig_1)
print(f"Mean counterfactual prediction change (minority): {cf_change.mean():.4f}")
print(f"Counterfactually fair if ~0; actual: {cf_change.mean():.4f}")
Example 2: SHAP Values for Bias Attribution
import numpy as np
# Without SHAP: approximate feature importance for bias attribution
coefs = dict(zip(X_train.columns, clf.coef_[0]))
print("Logistic regression coefficients (proxy for feature importance):")
for feat, coef insorted(coefs.items(), key=lambda x: abs(x[1]), reverse=True):
print(f" {feat}: {coef:+.4f}")
print("\nNote: high coefficient for 'income' or 'credit_score' may encode")
print("indirect discrimination if these features are correlated with protected group.")
Last updated: 2026-03-17 | Maintainer: @xjtulycIssues: GitHub Issues