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.
Where:
faircode/strategies.py:155(fit_post_processing's stratify guard).The gap:
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 ay_trainwith exactly 2 classes but a minority class of size 1 passes this check and then crashes insidetrain_test_splitwith a raw sklearn error.Repro:
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 gracefulinsufficient_data-style degradationfaircode.metrics/faircode.significancealready 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 theValueErrorfromtrain_test_splitand fall back tostratify=None, instead oflen(np.unique(y_train)) > 1.