v0.15.0#
Summary#
This release adds ROC curves by sensitive group, automatic plotting of
bootstrap confidence intervals, and output feature names for
CorrelationRemover. It fixes incorrect
fairness results for weighted single-sample groups, centering of multiple
sensitive features, and reuse of reductions constraints across fits.
Compatibility changes: Python 3.11 or newer and scikit-learn 1.6.0 or
newer are now required, and Python 3.14 is supported. The bank marketing
and credit card dataset loaders now use the authoritative UCI column names;
code selecting columns by the previous OpenML names needs updating.
MetricFrame now rejects missing sensitive or
control feature values, and
CorrelationRemover rejects reordered or
renamed columns in named transform inputs. The fitted reductions constraint
is now available as constraints_, while the user-supplied
constraints object remains unmodified.
New features#
Added
plot_roc_curve_by_group()to plot ROC curves disaggregated by sensitive feature, together with the overall curve and a chance-level baseline. The corresponding AUC scores can be obtained withMetricFrameandsklearn.metrics.roc_auc_score: #869 by kstohr and Roman Lutz.When bootstrap confidence intervals are available on a
MetricFrame,plot_metric_frame()now auto-detects them fromby_group_ciwhen the conf_intervals argument is not supplied: #1674 by Dresden.Added
get_feature_names_outtoCorrelationRemover, so it can be used withset_outputand reports its output feature names in pipelines andColumnTransformer: #1745 by Raashish Aggarwal.
Bug fixes#
CorrelationRemoverrejects reordered or renamed columns in named transform inputs instead of silently applying the fitted sensitive-column positions to different features: #1739 by @nawaaaaaAaar.MetricFramenow raises aValueErrorwhen a sensitive or control feature contains missing values, naming the offending feature. Previously a missing value was silently dropped fromby_groupwhile still counting towardsoverall, and a feature passed as a list instead of apandas.Seriesturned the missing value into a category named'nan': #1698 by Edward Yi.Fixed
AssertionError: data can be loaded only onceraised when aMomentinstance is passed asconstraintsto more than one call ofExponentiatedGradient.fitorGridSearch.fit, or shared between estimators. The fitted estimators now deep-copy the user-supplied constraints before loading data, leave the original object unmodified, and expose the loaded copy as the fitted attributeconstraints_: #1632 by BALOGUN DAVID TAIWO.Calling
Moment.load_datamore than once on the same instance now raises aValueErrorinstead of anAssertionError: #1705 by @SpiliosDimakopoulos.Fixed
fetch_diabetes_hospitalso thatas_frame=Falsereturns numpy arrays instead of raising. The data is now always fetched as a pandas DataFrame internally and converted to numpy when needed. The skipped tests and the obsoletetest_fetch_diabetes_hospital_as_ndarray_raises_value_errortest were removed: #1636 by Richard Ogundele.BackendEngineused to readX.shape[1]without checking how many dimensionsXhad, so passing data with more than two dimensions (for example an array of images) would silently build a predictor model with the wrong input size instead of failing. It now raises aValueErrorup front whenXis not two-dimensional: #1671 by Arnav.Fixed
FloatTransformer.transformandFloatTransformer.inverse_transformso that they raiseNotFittedErrorinstead ofAttributeErrorwhen called beforefit: #1709 by @SpiliosDimakopoulos.Fixed
plot_roc_curve_by_group()so that rows with missing sensitive feature values are included in the overall curve but omitted from per-group curves, instead of raising aValueError: #1713 by @SpiliosDimakopoulos.Already-fitted adversarial estimators now enforce the same two-dimensional input requirement in
predictandpartial_fit, including when input validation is skipped: #1711 by Roman Lutz.Fixed
CorrelationRemoverso that each sensitive feature is centered by its own mean. With more than one sensitive feature,fitused a single mean over all sensitive columns, so the transformed features stayed correlated with the sensitive features whenever those columns had different means (for example several one-hot encoded groups).sensitive_mean_now holds one mean per sensitive feature: #1732 by @breken-ai.MetricFramenow accepts NumPy integers (for examplenp.int64) forn_bootandrandom_state. Previously they raised aValueError: #1737 by @Arthur031221.Fixed
selection_rate()returning a one-element array instead of a scalar whensample_weightis passed for a single sample. In a weightedMetricFramethis happens for any group with one member, anddemographic_parity_difference()anddemographic_parity_ratio()then reported 0 and 1 (no disparity) regardless of the actual selection rates: #1734 by @breken-ai.
Documentation#
Removed stale deprecation notices in docstrings for changes that completed in v0.10 (
ThresholdOptimizer.predict_methoddefault,InterpolatedThresholder.predict_methoddefault, andMetricFrameconstructor positional-argument deprecation).Added
versionadded/versionchangeddirectives to theCorrelationRemoverAPI reference, documenting when the estimator was introduced (v0.6.0) and subsequently changed (v0.8.0, v0.12.0): #1748 by Peace Amhanesi.
Other improvements#
Tightened the internal projection of Lagrange multipliers for difference-based reductions constraints by removing the redundant per-event group-membership component while preserving the classifier-dependent Lagrangian term: #1714 by quinnarnold.
Behavior change.
fetch_bank_marketing()andfetch_credit_card()now report the authoritative UCI column names (age,job, … andLIMIT_BAL,SEX, …) instead of passing through the placeholder names carried by the underlying OpenML records (V1…V16andx1…x23), so a sensitive feature can be selected by its documented name. The names are applied consistently tofeature_names, thedataandframeDataFrame columns, and thereturn_X_y=Trueresult; categorical metadata keys in non-frame Bunches are remapped to match. Each returned Bunch has its ownfeature_nameslist, so modifying it does not affect other results or later loads. Code that selects columns by the previous OpenML names will need updating; positional access, OpenML data IDs, targets, arrays, and dtypes are unchanged. The loaders now raise aValueErrorrather than relabelling if a record’s column count ever stops matching: #1728 by @breken-ai.Preserved multi-column sensitive feature data in
fairlearn.postprocessing.ThresholdOptimizerdata reformatting helpers: #1679 by BALOGUN-DAVID.Replaced the PyTorch adversarial engine’s hard-coded activation string checks with a lookup table and added support for
Tanh,GELU,ELU, andSELUactivation strings. Activation strings are now matched case-insensitively on both the PyTorch and TensorFlow backends (the TensorFlow backend previously failed on capitalized strings such asSigmoid): #1665 by Aryan.Added support for Python 3.14: #1649 by Roman Lutz.
Bumped the minimum
sphinxversion from 7.3.7 to 8.3.0 (and bumpedsphinx-promptfrom 1.8.0 to 1.9.0 andsphinx-autodoc-typehintsfrom 2.0.1 to 3.5.2 in the minimum-version pins for compatibility) so the documentation build picks up newer sphinx theme and extension improvements.Modernized internal type annotations to PEP 585 / PEP 604 syntax (
list[T],tuple[T, ...],T | U) where the imports fromtypingwere no longer necessary.Removed support for Python 3.10. The minimum supported Python version is now 3.11: #1653 by Roman Lutz.
Raised the minimum supported
scikit-learnversion to1.6.0and removed thefairlearn.utils._fixescompatibility shim, which existed to bridge thescikit-learn1.5 ↔ 1.6 API change (validate_dataimport location,force_all_finite→ensure_all_finiterename, and the wrappedparametrize_with_checks). Internal callers now import directly fromsklearn.utils.validationandsklearn.utils.estimator_checks. Also added a second set ofpytest-mplbaseline snapshots undertest/unit/plot_snapshots_mpl310/formatplotlib==3.10.x, alongside thematplotlib>=3.11snapshots, since the two render-engine versions produce noticeably different output: #1654 by Roman Lutz.Moved package metadata and runtime dependencies from
setup.pyandrequirements.txtintopyproject.toml. Builds continue to use setuptools and the version infairlearn/__init__.py: #1629 by Christian Heimes.Removed dead
# noqacomments throughoutfairlearn/whose suppressed rule codes are not enabled in the project’s ruff configuration, plus the invalid# noqa : G003(extra space) typo variants.Worked around a
pytest.PytestRemovedIn10Warningcollection failure onpytest>=9by adding a thinparametrize_with_checkswrapper intest/_sklearn_compat.pythat materializes the generator returned bysklearn.utils.estimator_checks.parametrize_with_checksinto a list before handing it topytest.mark.parametrize: #1666 by Roman Lutz.Removed the
fairlearn.utils._compatibilityshim, which only existed to bridge theOneHotEncoder(sparse=...)→sparse_output=...rename completed inscikit-learn1.4. No longer needed now that the minimum supportedscikit-learnis ≥ 1.6: #1664 by Roman Lutz.Enabled the
pydocstyle(D) ruff rule set with the numpy convention. Added docstrings where missing for public APIs and per-file ignores for tests, examples, and scripts.Enabled the
flake8-logging-format(G) ruff rule set and converted existing logging calls to use lazy%sformatting.Clarified the fitted-state check in
_AdversarialFairness._validate_inputby replacing atry/except NotFittedErrorblock used purely for control flow with a directhasattr(self, "_is_setup")probe against the class’s canonical fitted flag, and removed the now-unusedNotFittedErrorimport: #1673 by Dresden.Updated Ruff to
0.15.20, enabled theUP,B,SIM, andC4rule families, and applied the resulting lint cleanups across the codebase: #1675 by Roman Lutz.