Skip to content

fit_post_processing() crashes on a minority class with only 1 member instead of degrading gracefully #446

Description

@yakew7

Where: faircode/strategies.py:155 (fit_post_processing's stratify guard).

The gap:

stratify = y_train if len(np.unique(y_train)) > 1 else None

This only guards the fully-degenerate single-class case. sklearn.train_test_split(..., stratify=y) requires every class to have at least 2 members, not just 2+ distinct classes to exist - so a y_train with exactly 2 classes but a minority class of size 1 passes this check and then crashes inside train_test_split with a raw sklearn error.

Repro:

>>> import numpy as np, pandas as pd
>>> from sklearn.ensemble import RandomForestClassifier
>>> from faircode.strategies import fit_post_processing
>>> X_train = pd.DataFrame({'a': np.random.default_rng(0).random(20), 'b': np.random.default_rng(1).random(20)})
>>> y_train = np.zeros(20, dtype=int); y_train[0] = 1   # 2 classes, minority has 1 member
>>> sensitive_train = np.array(['g1','g2'] * 10)
>>> fit_post_processing(RandomForestClassifier(random_state=42, n_estimators=10), X_train, y_train, sensitive_train, random_state=42)
Traceback (most recent call last):
  ...
ValueError: The least populated classes in y have only 1 member, which is too few. The minimum number of groups for any class cannot be less than 2. Classes with too few members are: [1]

Why it matters: S4 (post_processing) is one of the five strategies run for every (audit, model, protected-attribute) cell in the benchmark harness. A small audit, a heavily row-filtered subset, or an intersectional subgroup can plausibly leave a rare positive/negative class with only 1 training example, crashing the whole strategy with an unhandled sklearn internal error - unlike the graceful insufficient_data-style degradation faircode.metrics/faircode.significance already use elsewhere for other degenerate inputs.

Suggested fix: check the minority class count, not just the number of distinct classes - e.g. min(np.bincount(y_train)) >= 2 - or catch the ValueError from train_test_split and fall back to stratify=None, instead of len(np.unique(y_train)) > 1.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinghelp wantedExtra attention is needed

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions