Skip to content

fix(fairness): count a masked 0/inf ratio as exceeded in fairness_check - #586

Open
shaurya416 wants to merge 1 commit into
ModelOriented:masterfrom
shaurya416:fix/fairness-check-masked-ratio-585
Open

shaurya416 wants to merge 1 commit into
ModelOriented:masterfrom
shaurya416:fix/fairness-check-masked-ratio-585

Conversation

@shaurya416

Copy link
Copy Markdown

Fixes #585.

Problem

calculate_ratio() masks a subgroup ratio that comes out as exactly 0 or inf to NaN, so calculate_parity_loss()'s log stays finite. universal_fairness_check() reused that same masked frame for the pass/fail comparison, and NaN > x / x > NaN are both False in numpy, so a masked ratio was never counted as exceeded — even though a ratio of 0 or inf is the most extreme disparity a metric can show. A model that never predicts the positive class for one subgroup can be reported as fair.

Fix

universal_fairness_check() now recomputes the ratio from the raw scores already stored on the object (self.metric_scores, unmasked) to tell apart why a ratio is NaN:

  • masked because it was really 0 or inf → counts as exceeded (masked_disparity)
  • genuinely 0/0 (both groups scored exactly 0) → stays excluded, since that case really is undecidable

GroupFairnessRegression shares universal_fairness_check but has no metric_scores and no such masking (calculate_regression_measures is a different computation entirely) — guarded with a hasattr check so it's unaffected. calculate_ratio() and calculate_parity_loss() are unchanged.

Testing

Added test_fairness_check_masked_ratio: a subgroup that never predicts positive (TPR/FPR/STP ratios exactly 0, PPV genuinely 0/0). Asserts the printed verdict names TPR/FPR/STP as exceeded, but not PPV, and does not say the model is fair.

Ran the full FairnessTest suite (23 tests, including GroupFairnessRegression) — all pass. As a control, reverting only the fix hunk makes the new test fail exactly as expected (prints "No bias was detected!" on data with three 0-ratio metrics).

calculate_ratio() masks a subgroup ratio that is exactly 0 or inf to
NaN, so calculate_parity_loss()'s log stays finite. universal_fairness_check()
reused that same masked frame for the pass/fail comparison, and NaN
compares False both ways in numpy, so a masked ratio was never counted
as exceeded -- even though 0 or inf is the most extreme disparity a
metric can show. A model that never predicts the positive class for
one subgroup could be reported as fair.

universal_fairness_check() now recomputes the ratio from the raw
scores already stored on GroupFairnessClassification (self.metric_scores,
unmasked) to tell apart why a ratio is NaN: masked because it was
really 0 or inf (now counts as exceeded), or genuinely 0/0 because both
groups scored exactly 0 (stays excluded, since that case really is
undecidable). GroupFairnessRegression has no metric_scores and no such
masking, so it is unaffected (hasattr guard). calculate_ratio() and
calculate_parity_loss() are unchanged.

Adds test_fairness_check_masked_ratio: a subgroup that never predicts
positive (TPR/FPR/STP ratios exactly 0, PPV genuinely 0/0). Asserts the
printed verdict names TPR/FPR/STP as exceeded, not PPV, and does not
say the model is fair. Full FairnessTest suite (23 tests) passes.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fairness_check() reports "No bias was detected!" when a subgroup ratio is exactly 0 or infinite (ratio masked to NaN before the epsilon comparison)

1 participant