Skip to content

Binary classification

The default task. Credit scoring, fraud detection, lapse prediction — anything with a positive class.

context = StructuredGateContext(
    model=model,
    X=X_val,
    y_true=y_val,
    y_pred=model.predict_proba(X_val)[:, 1],  # positive-class probability
    protected_df=protected_val,
    task="binary",
)

Metrics

Metric Expects Notes
roc_auc scores threshold-independent; the "auto" default
average_precision scores better than AUC when the positive class is rare
accuracy labels works without scikit-learn
balanced_accuracy labels for skewed classes
f1, precision, recall labels

Label-based metrics binarise a continuous y_pred at config.performance.decision_threshold (default 0.5). Ranking metrics ignore it.

All are gated with min_score, since higher is better for every one.

config = GateConfig()
config.performance.metric = "average_precision"
config.performance.min_score = 0.30

Imbalanced problems

At a 5% base rate, ROC AUC looks comfortable while the model is barely usable. average_precision does not flatter.

A custom metric

Any fn(y_true, y_pred) -> float. It is called with y_pred exactly as supplied — no thresholding, since only you know what your metric expects.

from sklearn.metrics import fbeta_score


def f2_at_30pct(y_true, y_pred):
    return fbeta_score(y_true, (np.asarray(y_pred) >= 0.30).astype(int), beta=2)


config.performance.metric = f2_at_30pct  # reported under the name "f2_at_30pct"

Custom callables are assumed higher-is-better and gated with min_score. Negate inside your function if that is wrong.

Fairness

All four checks apply.

Check Question
proxy_correlation does a feature encode a protected attribute the model cannot see?
disparate_impact do selection rates differ across groups?
shap_subgroup_gap does a feature drive outcomes differently per group?
counterfactual_flip does flipping the attribute change the prediction?

proxy_correlation is usually the most valuable: it catches the case where a team believes it removed a protected attribute and did not. Drop region from the features and distance_to_branch_km will happily reconstruct it.

Demographic parity needs hard labels

It counts predictions equal to 1. Continuous predictions are binarised for you at FairnessConfig.decision_threshold. Before 0.2.1 they were not, and the check silently reported 0.000 for any probability y_pred — perfectly fair, however skewed the model.

The "auto" metric fallback

With metric="auto" and no scikit-learn installed, the gate falls back from roc_auc to accuracy. That fallback is never silent: it is logged at WARNING, marked metric_is_fallback: true in the result metadata, and named in the detail string.

Naming a metric explicitly opts out of fallback entirely — if it cannot run, the gate reports a blocking CHECK_ERROR rather than scoring you on something you did not ask for.

Worked end to end in notebook 01.