================================================================================
AI FAIRNESS 360 (AIF360) — COMPREHENSIVE ALGORITHM DOCUMENTATION
Version: 0.6.1 (stable)
Source: https://aif360.readthedocs.io/en/stable/modules/algorithms.html
================================================================================

TABLE OF CONTENTS
-----------------
1. Overview
2. Base Class: aif360.algorithms.Transformer
3. PREPROCESSING ALGORITHMS
   3.1  DisparateImpactRemover          (aif360.algorithms.preprocessing)
   3.2  LFR                             (aif360.algorithms.preprocessing)
   3.3  OptimPreproc                    (aif360.algorithms.preprocessing)
   3.4  Reweighing                      (aif360.algorithms.preprocessing)
   3.5  LearnedFairRepresentations      (aif360.sklearn.preprocessing)
   3.6  FairAdapt                       (aif360.sklearn.preprocessing)
   3.7  Reweighing (sklearn)            (aif360.sklearn.preprocessing)
   3.8  ReweighingMeta                  (aif360.sklearn.preprocessing)
4. INPROCESSING ALGORITHMS
   4.1  AdversarialDebiasing            (aif360.algorithms.inprocessing)
   4.2  ARTClassifier                   (aif360.algorithms.inprocessing)
   4.3  GerryFairClassifier             (aif360.algorithms.inprocessing)
   4.4  GridSearchReduction             (aif360.algorithms.inprocessing)
   4.5  ExponentiatedGradientReduction  (aif360.algorithms.inprocessing)
   4.6  MetaFairClassifier              (aif360.algorithms.inprocessing)
   4.7  PrejudiceRemover                (aif360.algorithms.inprocessing)
   4.8  AdversarialDebiasing (sklearn)  (aif360.sklearn.inprocessing)
   4.9  ExponentiatedGradientReduction (sklearn) (aif360.sklearn.inprocessing)
   4.10 GridSearchReduction (sklearn)   (aif360.sklearn.inprocessing)
5. POSTPROCESSING ALGORITHMS
   5.1  CalibratedEqOddsPostprocessing  (aif360.algorithms.postprocessing)
   5.2  EqOddsPostprocessing            (aif360.algorithms.postprocessing)
   5.3  RejectOptionClassification      (aif360.algorithms.postprocessing)
   5.4  CalibratedEqualizedOdds (sklearn)(aif360.sklearn.postprocessing)
   5.5  RejectOptionClassifierCV        (aif360.sklearn.postprocessing)
   5.6  PostProcessingMeta              (aif360.sklearn.postprocessing)
6. SCENARIO GUIDE: CHOOSING THE RIGHT ALGORITHM
7. INSTALLATION NOTES FOR ALGORITHM DEPENDENCIES


================================================================================
1. OVERVIEW
================================================================================

AIF360 is an extensible open-source library containing techniques developed by
the research community to help detect and mitigate bias in machine learning
models throughout the AI application lifecycle.

AIF360 has two interfaces for handling data formats:
  - A legacy interface (aif360.algorithms.*)
  - A scikit-learn-compatible interface (aif360.sklearn.*)

The sklearn API is preferred going forward and all future development will be
focused there. Note: there may be slight differences in results between the
two versions due to the implementations but both are valid.

AIF360 estimators are machine learning models which mitigate unfair bias in
some way. They may also be referred to as algorithms. AIF360 estimators inherit
from scikit-learn Estimators and are intended to work in conjunction with each
other.

Note: while scikit-learn estimators can generally be used in an AIF360 pipeline,
the reverse is not always true due to the data limitations. This is ongoing work
to reduce friction between the libraries.

Algorithms are divided into three classes based on how they mitigate bias:

  - Pre-processors: Act on data and produce fair representations which are
    subsequently used by another machine learning model. Under scikit-learn
    conventions, these work like Transformers.

        >>> from aif360.sklearn.preprocessing import LearnedFairRepresentations
        >>> Xt = LearnedFairRepresentations().fit_transform(X, y)

    In some cases, AIF360 pre-processors necessarily break scikit-learn
    conventions, e.g., Reweighing and FairAdapt. This may cause difficulty when
    using them within a Pipeline. Typically, a MetaEstimator may be used to
    perform pre-processing and estimation in a single step.

  - In-processors: Apply mitigation during model training in order to produce a
    fair model. Under scikit-learn conventions, these are simply
    Classifiers/Regressors.

        >>> from aif360.sklearn.inprocessing import AdversarialDebiasing
        >>> y_pred = AdversarialDebiasing().fit(X_train, y_train).predict(X_test)

  - Post-processors: Act on the outputs of a model (and possibly the inputs as
    well) and produce new, fairer outputs. These types of models are classified
    as MetaEstimators in scikit-learn.

        >>> from sklearn.linear_model import LogisticRegression
        >>> from aif360.sklearn.postprocessing import RejectOptionClassifierCV, PostProcessingMeta
        >>> y_pred = PostProcessingMeta(
        ...     LogisticRegression(),
        ...     RejectOptionClassifierCV('sex', scoring='disparate_impact')
        ... ).fit(X_train, y_train).predict(X_test)

Dataset Format (sklearn API):
    The dataset format for aif360.sklearn is a pandas.DataFrame with protected
    attributes in the index.

        >>> from aif360.sklearn.datasets import fetch_compas
        >>> fetch_compas().X.head()
        sex      age  age_cat              race  juv_fel_count  ...
        sex   race
        Male  Other      69  Greater than 45       Other              0  ...
              African-American  34        25 - 45  African-American       0  ...
              ...

    Per-sample protected attributes are stored in the index of the DataFrame.
    If multiple protected attributes are present, a single attribute or subset
    may be selected for use in a specific algorithm/metric by using the relevant
    prot_attr argument.


================================================================================
2. BASE CLASS: aif360.algorithms.Transformer
================================================================================

class aif360.algorithms.Transformer(**kwargs)[source]

    Abstract base class for transformers. Transformers are an abstraction for
    any process which acts on a Dataset and returns a new, modified Dataset.
    This definition encompasses pre-processing, in-processing, and
    post-processing algorithms.

    Initialize a Transformer object. Algorithm-specific configuration parameters
    should be passed here.

    Methods
    -------

    fit(dataset)[source]
        Train a model on the input.

    fit_predict(dataset)[source]
        Train a model on the input and predict the labels. Equivalent to calling
        fit(dataset) followed by predict(dataset).

    fit_transform(dataset)[source]
        Train a model on the input and transform the dataset accordingly.
        Equivalent to calling fit(dataset) followed by transform(dataset).

    predict(dataset)[source]
        Return a new dataset with labels predicted by running this Transformer
        on the input.

    transform(dataset)[source]
        Return a new dataset generated by running this Transformer on the input.


================================================================================
3. PREPROCESSING ALGORITHMS
================================================================================

Preprocessing algorithms act on the training data before a model is trained.
They modify the dataset to reduce bias in features or labels, or assign
instance weights so that a downstream classifier sees a more balanced training
signal.

--------------------------------------------------------------------------------
3.1 DisparateImpactRemover
    Module: aif360.algorithms.preprocessing
--------------------------------------------------------------------------------

class aif360.algorithms.preprocessing.DisparateImpactRemover(
    repair_level=1.0, sensitive_attribute='')[source]

Description:
    Disparate impact remover is a preprocessing technique that edits feature
    values increase group fairness while preserving rank-ordering within
    groups [1].

References:
    [1] M. Feldman, S. A. Friedler, J. Moeller, C. Scheidegger, and
        S. Venkatasubramanian, "Certifying and removing disparate impact."
        ACM SIGKDD International Conference on Knowledge Discovery and Data
        Mining, 2015.

Parameters:
    repair_level (float)
        Repair amount. 0.0 is no repair while 1.0 is full repair.

    sensitive_attribute (str)
        Single protected attribute with which to do repair.

Constructor (source):
    def __init__(self, repair_level=1.0, sensitive_attribute=''):
        super(DisparateImpactRemover, self).__init__(repair_level=repair_level)
        # avoid importing early since this package can throw warnings in some
        # jupyter notebooks
        from BlackBoxAuditing.repairers.GeneralRepairer import Repairer
        self.Repairer = Repairer
        if not 0.0 <= repair_level <= 1.0:
            raise ValueError("'repair_level' must be between 0.0 and 1.0.")
        self.repair_level = repair_level
        self.sensitive_attribute = sensitive_attribute

Methods:
    fit_transform(dataset)[source]
        Run a repairer on the non-protected features and return the transformed
        dataset.

        Args:
            dataset (BinaryLabelDataset): Dataset that needs repair.

        Returns:
            dataset (BinaryLabelDataset): Transformed Dataset.

        Note:
            In order to transform test data in the same manner as training data,
            the distributions of attributes conditioned on the protected attribute
            must be the same.

When to Use:
    - When disparate impact is measurably present in the raw feature distributions
      and you want to correct it before any model is trained.
    - When you need to preserve rank-ordering within each group (e.g., credit
      scoring, hiring pipelines) while equalizing the marginal distributions.
    - Best suited for scenarios with continuous or ordinal features where the
      rank-ordering constraint is meaningful.
    - Use repair_level < 1.0 to balance between fairness correction and
      information loss. Full repair (1.0) can degrade accuracy.
    - Not suitable when the sensitive attribute is not known at inference time.

Extra dependency:
    pip install aif360[DisparateImpactRemover]
    (requires BlackBoxAuditing package)


--------------------------------------------------------------------------------
3.2 LFR — Learning Fair Representations
    Module: aif360.algorithms.preprocessing
--------------------------------------------------------------------------------

class aif360.algorithms.preprocessing.LFR(
    unprivileged_groups, privileged_groups,
    k=5, Ax=0.01, Ay=1.0, Az=50.0,
    print_interval=250, verbose=1, seed=None)[source]

Description:
    Learning fair representations is a pre-processing technique that finds a
    latent representation which encodes the data well but obfuscates information
    about protected attributes [2].

References:
    [2] R. Zemel, Y. Wu, K. Swersky, T. Pitassi, and C. Dwork, "Learning Fair
        Representations." International Conference on Machine Learning, 2013.

Parameters:
    unprivileged_groups (list(dict))
        Representation for unprivileged group.

    privileged_groups (list(dict))
        Representation for privileged group.

    k (int, optional)
        Number of prototypes. Default is 5.

    Ax (float, optional)
        Input reconstruction quality term weight. Default is 0.01.

    Ay (float, optional)
        Output prediction error term weight. Default is 1.0.

    Az (float, optional)
        Fairness constraint term weight. Default is 50.0.

    print_interval (int, optional)
        Print loss values every print_interval iterations. Default is 250.

    verbose (int, optional)
        If zero, then no output. Default is 1.

    seed (int, optional)
        Seed to make fit() and predict() repeatable.

Methods:
    fit(dataset)
        Fit the model with the given training data.

    transform(dataset)
        Transform the dataset using the learned fair representation.

    fit_transform(dataset)
        Fit and transform the dataset in sequence.

When to Use:
    - When the goal is to produce a general-purpose latent representation that
      can be used with any downstream classifier, not just one specific model.
    - When you need a representation that is simultaneously (a) informative about
      the target label, (b) reconstructive of the original features, and (c)
      statistically independent of the protected attribute.
    - Suitable for transfer learning scenarios: learn the representation on a
      labeled fairness-critical dataset, then reuse the encoder elsewhere.
    - The three weights (Ax, Ay, Az) allow explicit trade-off control between
      reconstruction fidelity, predictive accuracy, and fairness.
    - Not ideal when interpretability of features is required, since the output
      is a latent prototype-based encoding.

Extra dependency:
    pip install aif360[LFR]


--------------------------------------------------------------------------------
3.3 OptimPreproc — Optimized Preprocessing
    Module: aif360.algorithms.preprocessing
--------------------------------------------------------------------------------

class aif360.algorithms.preprocessing.OptimPreproc(
    optimizer, optim_options,
    unprivileged_groups=None, privileged_groups=None,
    verbose=False, seed=None)[source]

Description:
    Optimized preprocessing is a preprocessing technique that learns a
    probabilistic transformation that edits the features and labels in the data
    with group fairness, individual distortion, and data fidelity constraints
    and objectives [3].

References:
    [3] F. P. Calmon, D. Wei, B. Vinzamuri, K. Natesan Ramamurthy, and
        K. R. Varshney. "Optimized Pre-Processing for Discrimination Prevention."
        Conference on Neural Information Processing Systems, 2017.
        Based on code available at: https://github.com/fair-preprocessing/nips2017

Parameters:
    optimizer (class)
        Optimizer class.

    optim_options (dict)
        Options for optimization to estimate the transformation.

    unprivileged_groups (dict)
        Representation for unprivileged group.

    privileged_groups (dict)
        Representation for privileged group.

    verbose (bool, optional)
        Verbosity flag for optimization.

    seed (int, optional)
        Seed to make fit() and predict() repeatable.

Note:
    This algorithm does not use the privileged and unprivileged groups that are
    specified during initialization yet. Instead, it automatically attempts to
    reduce statistical parity difference between all possible combinations of
    groups in the dataset.

    Warning: Optimized pre-processing will ignore instance_weights in the
    dataset during fit.

    Warning: The transformed dataset will have all instance weights set to 1.

Methods:
    fit(dataset, sep='=')[source]
        Compute optimal pre-processing transformation based on distortion
        constraint.

    fit_transform(dataset, sep='=', transform_Y=True)[source]
        Perform fit() and transform() sequentially.

    transform(dataset, sep='=', transform_Y=True)[source]
        Transform the dataset to a new dataset based on the estimated
        transformation.

        If transform_Y is True, a randomized mapping is used when Y is
        requested to be transformed:

            dfP_withY = self.OpT.dfP.applymap(lambda x: 0 if x < 1e-8 else x)
            dfP_withY = dfP_withY.divide(dfP_withY.sum(axis=1), axis=0)

Internal fit logic (abbreviated):
    def fit(self, dataset, sep='='):
        if len(np.unique(dataset.instance_weights)) > 1:
            warn("Optimized pre-processing will ignore instance_weights in "
                 "the dataset during fit.")
        df, _ = dataset.convert_to_dataframe(de_dummy_code=True,
                                              sep=sep, set_category=True)
        self.protected_attribute_names = dataset.protected_attribute_names
        self.privileged_protected_attributes = dataset.privileged_protected_attributes
        self.unprivileged_protected_attributes = dataset.unprivileged_protected_attributes
        self.Y_feature_names = dataset.label_names
        self.X_feature_names = [n for n in df.columns.tolist()
                                 if n not in self.Y_feature_names
                                 and n not in self.protected_attribute_names]
        self.feature_names = (self.X_feature_names
                              + self.Y_feature_names
                              + self.protected_attribute_names)
        self.OpT = self.optimizer(df=df, features=self.feature_names)
        ...

When to Use:
    - When a probabilistic, theoretically grounded transformation of both
      features and labels is needed, jointly satisfying group fairness,
      individual distortion, and data fidelity objectives.
    - Suitable when you can afford the computational cost of solving a
      constrained optimization problem over the joint data distribution.
    - Best applied when the full feature-label-protected attribute joint
      distribution can be estimated reliably (i.e., sufficient training data).
    - The algorithm considers all group combinations automatically, so it is
      appropriate when there are multiple protected groups to balance.
    - Not recommended when instance weights in the dataset must be preserved.

Extra dependency:
    pip install aif360[OptimPreproc]
    (requires CVXPY: install instructions at https://www.cvxpy.org/install/)


--------------------------------------------------------------------------------
3.4 Reweighing
    Module: aif360.algorithms.preprocessing
--------------------------------------------------------------------------------

class aif360.algorithms.preprocessing.Reweighing(
    unprivileged_groups, privileged_groups)[source]

Description:
    Reweighing is a preprocessing technique that weights the examples in each
    (group, label) combination differently to ensure fairness before
    classification [4].

References:
    [4] F. Kamiran and T. Calders, "Data Preprocessing Techniques for
        Classification without Discrimination," Knowledge and Information
        Systems, 2012.

Parameters:
    unprivileged_groups (list(dict))
        Representation for unprivileged group.

    privileged_groups (list(dict))
        Representation for privileged group.

Methods:
    fit(dataset)[source]
        Compute the weights for reweighing the dataset.

        Returns:
            Reweighing – Returns self.

    fit_transform(dataset)[source]
        Train a model on the input and transform the dataset accordingly.
        Equivalent to calling fit(dataset) followed by transform(dataset).

    transform(dataset)[source]
        Transform the dataset to a new dataset based on the estimated
        transformation.

When to Use:
    - The simplest and most interpretable preprocessing debiasing algorithm.
    - When you do not want to alter feature values or labels, only the
      importance (weight) assigned to each training example.
    - Applicable when your downstream classifier supports sample_weight (e.g.,
      logistic regression, gradient-boosted trees, SVMs).
    - When disparate impact or statistical parity difference is the target
      fairness metric.
    - Particularly useful as a first baseline to establish whether weighting
      alone can resolve observed bias.
    - Works with any model family — the preprocessing is model-agnostic.


--------------------------------------------------------------------------------
3.5 LearnedFairRepresentations (sklearn API)
    Module: aif360.sklearn.preprocessing
--------------------------------------------------------------------------------

class aif360.sklearn.preprocessing.LearnedFairRepresentations(
    prot_attr=None, n_prototypes=5,
    reconstruct_weight=0.01, target_weight=1.0, fairness_weight=50.0,
    tol=0.0001, max_iter=200, verbose=0, random_state=None)[source]

Description:
    Learned Fair Representations. Learned fair representations is a
    pre-processing technique that finds a latent representation which encodes
    the data well but obfuscates information about protected attributes [1]. It
    can also be used as an in-processing method by utilizing the learned target
    coefficients.

References:
    [1] R. Zemel, Y. Wu, K. Swersky, T. Pitassi, and C. Dwork, "Learning Fair
        Representations." International Conference on Machine Learning, 2013.

Parameters:
    prot_attr (single label or list-like, optional)
        Protected attribute(s) to use in the reweighing process. If more than
        one attribute, all combinations of values (intersections) are
        considered. Default is None meaning all protected attributes from the
        dataset are used.

    n_prototypes (int, optional)
        Number of prototypes. Default is 5.

    reconstruct_weight (float, optional)
        Input reconstruction quality term weight (Ax). Default is 0.01.

    target_weight (float, optional)
        Output prediction error term weight (Ay). Default is 1.0.

    fairness_weight (float, optional)
        Fairness constraint term weight (Az). Default is 50.0.

    tol (float, optional)
        Tolerance for convergence. Default is 0.0001.

    max_iter (int, optional)
        Maximum number of iterations. Default is 200.

    verbose (int, optional)
        Verbosity. Default is 0.

    random_state (int or None, optional)
        Seed for reproducibility.

Methods:
    fit(X, y, priv_group=1, sample_weight=None)[source]
        Compute the transformation parameters that lead to fair representations.

    transform(X, y=None)[source]
        Transform the targets using the learned model parameters.

    fit_transform(X, y)
        Equivalent to fit(X, y) followed by transform(X, y).

Usage Example:
    >>> from aif360.sklearn.preprocessing import LearnedFairRepresentations
    >>> Xt = LearnedFairRepresentations().fit_transform(X, y)

When to Use:
    - Same use cases as the legacy LFR class (Section 3.2), but within the
      scikit-learn-compatible pipeline.
    - Can be placed in a sklearn Pipeline as a Transformer step.
    - Can also serve as an in-processor by using the learned target coefficients
      directly, without a separate downstream classifier.


--------------------------------------------------------------------------------
3.6 FairAdapt (sklearn API)
    Module: aif360.sklearn.preprocessing
--------------------------------------------------------------------------------

class aif360.sklearn.preprocessing.FairAdapt(
    prot_attr=None, adj_mat=None, ...)[source]

Description:
    Fair Data Adaptation. Fairadapt is a pre-processing technique that can be
    used for both fair classification and fair regression [1]. The method is a
    causal inference approach to bias removal and it relies on the causal graph
    for the dataset. The original implementation is in R [2].

References:
    [1] D. Plečko and N. Meinshausen, "Fair Data Adaptation with Quantile
        Preservation," Journal of Machine Learning Research, 2020.
    [2] D. Plečko and N. Bennett and N. Meinshausen, "FairAdapt: Causal
        Reasoning for Fair Data Pre-processing," arXiv, 2021.

Key Parameter:
    adj_mat (array-like)
        A 2-dimensional array representing the adjacency matrix of the causal
        diagram of the data generating process. Row/column order must match
        X_train.

Note:
    FairAdapt necessarily breaks scikit-learn conventions. Use ReweighingMeta
    (or an equivalent MetaEstimator pattern) for Pipeline compatibility.

When to Use:
    - When a causal graph of the data-generating process is known or can be
      reasonably specified.
    - When the goal is causal fairness — ensuring protected attributes do not
      causally influence the outcome through pathways deemed inadmissible.
    - Suitable for both classification and regression tasks.
    - Applicable when counterfactual reasoning is desired: "what would the
      outcome be if the person belonged to the other group?"
    - Not appropriate when causal structure is unknown or heavily disputed,
      since the transformation is sensitive to the assumed adj_mat.

Extra dependency:
    pip install aif360[FairAdapt]


--------------------------------------------------------------------------------
3.7 Reweighing (sklearn API)
    Module: aif360.sklearn.preprocessing
--------------------------------------------------------------------------------

class aif360.sklearn.preprocessing.Reweighing(prot_attr=None)[source]

Description:
    Sample reweighing. Reweighing is a preprocessing technique that weights
    the examples in each (group, label) combination differently to ensure
    fairness before classification [1].

    This breaks the scikit-learn API by returning new sample weights from
    fit_transform(). See ReweighingMeta for a workaround.

References:
    [1] F. Kamiran and T. Calders, "Data Preprocessing Techniques for
        Classification without Discrimination," Knowledge and Information
        Systems, 2012.

Parameters:
    prot_attr (single label or list-like, optional)
        Protected attribute(s) to use in the reweighing process. If more than
        one attribute, all combinations of values (intersections) are
        considered. Default is None meaning all protected attributes from the
        dataset are used.

Attributes (post-fit):
    reweigh_factors_ (array, shape (n_groups, n_labels))
        Reweighing factors for each combination of group and class labels used
        to debias samples. Existing sample weights are multiplied by the
        corresponding factor for that sample's group and class.

Methods:
    fit_transform(X, y, sample_weight=None)[source]
        Compute the factors for reweighing the dataset and transform the sample
        weights.

        Args:
            X (pandas.DataFrame) – Training samples.
            y – Target labels.
            sample_weight – Optional pre-existing sample weights.

        Returns:
            X – Unchanged samples.
            (new sample weights)

        Note:
            Only fit_transform() is allowed for this algorithm.

Metadata routing:
    set_fit_transform_request(*, sample_weight=...) → Reweighing
        Request metadata passed to the fit method. Only relevant if
        enable_metadata_routing=True (see sklearn.set_config()).

When to Use:
    - Same as legacy Reweighing (Section 3.4), but for the sklearn-compatible
      API.
    - Note: Due to API incompatibility (returning weights alongside X), prefer
      ReweighingMeta (Section 3.8) when using inside a Pipeline.


--------------------------------------------------------------------------------
3.8 ReweighingMeta (sklearn API)
    Module: aif360.sklearn.preprocessing
--------------------------------------------------------------------------------

class aif360.sklearn.preprocessing.ReweighingMeta(
    estimator, reweigher=None)[source]

Description:
    A meta-estimator which wraps a given estimator with a reweighing
    preprocessing step. This is necessary for use in a Pipeline, etc.

Parameters:
    estimator
        A scikit-learn estimator (classifier or regressor) to wrap.

    reweigher (optional)
        A Reweighing instance. If None, a default Reweighing() is used.

Metadata routing:
    set_score_request(*, sample_weight=...) → ReweighingMeta
        Request metadata passed to the score method. Only relevant if
        enable_metadata_routing=True (see sklearn.set_config()).
        Options: True (pass if provided), False (do not pass), None (raise
        error if provided), str (alias).

When to Use:
    - When Reweighing needs to be integrated inside a sklearn Pipeline.
    - Provides a compliant wrapper that handles the non-standard fit_transform
      return signature of Reweighing transparently.


================================================================================
4. INPROCESSING ALGORITHMS
================================================================================

In-processing algorithms integrate fairness constraints or adversarial
objectives directly into the model training loop. The result is a single model
that is both predictive and fair, without requiring separate pre- or
post-processing steps.

--------------------------------------------------------------------------------
4.1 AdversarialDebiasing
    Module: aif360.algorithms.inprocessing
--------------------------------------------------------------------------------

class aif360.algorithms.inprocessing.AdversarialDebiasing(
    unprivileged_groups, privileged_groups, scope_name, sess,
    seed=None, adversary_loss_weight=0.1, num_epochs=50,
    batch_size=128, classifier_num_hidden_units=200,
    debias=True)[source]

Description:
    Adversarial debiasing is an in-processing technique that learns a classifier
    to maximize prediction accuracy and simultaneously reduce an adversary's
    ability to determine the protected attribute from the predictions [5]. This
    approach leads to a fair classifier as the predictions cannot carry any group
    discrimination information that the adversary can exploit.

References:
    [5] B. H. Zhang, B. Lemoine, and M. Mitchell, "Mitigating Unwanted Biases
        with Adversarial Learning," AAAI/ACM Conference on Artificial
        Intelligence, Ethics, and Society, 2018.

Parameters:
    unprivileged_groups (list(dict))
        Representation for unprivileged group.

    privileged_groups (list(dict))
        Representation for privileged group.

    scope_name (str)
        TensorFlow variable scope name for the classifier.

    sess (tensorflow.Session)
        Active TensorFlow session.

    seed (int, optional)
        Seed to make fit() and predict() repeatable. Default is None.

    adversary_loss_weight (float, optional)
        Hyperparameter that controls how strongly the adversary loss is weighted
        against the classifier loss during training. Default is 0.1.

    num_epochs (int, optional)
        Number of training epochs. Default is 50.

    batch_size (int, optional)
        Batch size for mini-batch gradient descent. Default is 128.

    classifier_num_hidden_units (int, optional)
        Number of hidden units in the classifier's single hidden layer.
        Default is 200.

    debias (bool, optional)
        If True, apply adversarial debiasing. If False, train a standard
        classifier with no fairness constraint (useful for baseline comparison).
        Default is True.

Methods:
    fit(dataset)[source]
        Compute the model parameters of the fair classifier using gradient
        descent.

    fit_predict(dataset)
        Train a model on the input and predict the labels. Equivalent to calling
        fit(dataset) followed by predict(dataset).

    predict(dataset)[source]
        Obtain the predictions for the provided dataset using the fair
        classifier learned.

When to Use:
    - When a deep learning model is needed and fairness must be enforced at
      training time rather than as a post-hoc correction.
    - When you have sufficient data to train both a classifier and an adversary
      network jointly.
    - When prediction accuracy is paramount and you want to minimize the
      trade-off: the adversarial objective specifically targets only the
      protected attribute information in predictions, not in features.
    - Setting debias=False provides an unbiased baseline classifier for
      comparison.
    - Not suitable for very small datasets (neural network-based).
    - Requires TensorFlow.

Extra dependency:
    pip install aif360[AdversarialDebiasing]
    (requires TensorFlow; on macOS may require Xcode Command Line Tools first)


--------------------------------------------------------------------------------
4.2 ARTClassifier
    Module: aif360.algorithms.inprocessing
--------------------------------------------------------------------------------

class aif360.algorithms.inprocessing.ARTClassifier(art_classifier)[source]

Description:
    Wraps an instance of an art.classifiers.Classifier to extend Transformer.
    This allows Adversarial Robustness Toolbox (ART) classifiers to be used
    within the AIF360 pipeline, enabling fairness-aware adversarial training.

Parameters:
    art_classifier (art.classifiers.Classifier)
        An instantiated ART classifier object.

Methods:
    __init__(art_classifier)[source]
        Initialize ARTClassifier.

    fit(dataset, batch_size=128, nb_epochs=20)[source]
        Train a classifier on the input.

        Args:
            dataset – AIF360 dataset.
            batch_size (int) – Mini-batch size. Default is 128.
            nb_epochs (int) – Number of training epochs. Default is 20.

    fit_predict(dataset)
        Train a model on the input and predict the labels. Equivalent to calling
        fit(dataset) followed by predict(dataset).

    predict(dataset, logits=False)[source]
        Perform prediction for the input.

        Args:
            logits (bool) – If True, return raw logits instead of class labels.

When to Use:
    - When you want to combine adversarial robustness (via ART) with fairness
      evaluation and mitigation (via AIF360).
    - Useful in security-sensitive applications where both robustness to
      adversarial examples and group fairness are required simultaneously.
    - Requires a pre-configured ART classifier (any ART-supported framework).

Extra dependency:
    pip install aif360[ART]


--------------------------------------------------------------------------------
4.3 GerryFairClassifier
    Module: aif360.algorithms.inprocessing
--------------------------------------------------------------------------------

class aif360.algorithms.inprocessing.GerryFairClassifier(
    C=10, printflag=False, heatmapflag=False,
    heatmap_iter=10, heatmap_path='.',
    max_iters=10, gamma=0.01,
    fairness_def='FP',
    predictor=LinearRegression())[source]

Description:
    Model is an algorithm for learning classifiers that are fair with respect to
    rich subgroups. Rich subgroups are defined by (linear) functions over the
    sensitive attributes, and fairness notions are statistical: false positive,
    false negative, and statistical parity rates. This implementation uses a max
    of two regressions as a cost-sensitive classification oracle, and supports
    linear regression, support vector machines, decision trees, and kernel
    regression.

References:
    "Preventing Fairness Gerrymandering: Auditing and Learning for Subgroup
     Fairness." Michael Kearns, Seth Neel, Aaron Roth, Steven Wu. ICML '18.

    "An Empirical Study of Rich Subgroup Fairness for Machine Learning."
     Michael Kearns, Seth Neel, Aaron Roth, Steven Wu.

Parameters:
    C (float, optional)
        Fairness regularization hyperparameter. Controls the trade-off between
        accuracy and fairness. Default is 10.

    printflag (bool, optional)
        If True, print progress information during training. Default is False.

    heatmapflag (bool, optional)
        If True, generate heatmap visualizations. Default is False.

    heatmap_iter (int, optional)
        Number of iterations between heatmap saves. Default is 10.

    heatmap_path (str, optional)
        Path to save heatmap figures. Default is '.'.

    max_iters (int, optional)
        Maximum number of training iterations. Default is 10.

    gamma (float, optional)
        Fairness approximation error bound. Default is 0.01.

    fairness_def (str, optional)
        Fairness definition to use. Options:
            'FP'  – False positive rate parity
            'FN'  – False negative rate parity
            'SP'  – Statistical parity
        Default is 'FP'.

    predictor (sklearn estimator, optional)
        The regression oracle used in the reduction. Supports LinearRegression,
        SVR, DecisionTreeRegressor, and kernel-based regressors.
        Default is LinearRegression().

Methods:
    __init__(...)[source]
        Initialize Model Object and set hyperparameters.

    fit(dataset)[source]
        Train the GerryFairClassifier.

    predict(dataset)[source]
        Return predictions.

    predict_proba(dataset)[source]
        Return probability estimates.

When to Use:
    - When standard group fairness (privileged vs. unprivileged) is insufficient
      and you need fairness guarantees over arbitrary subgroups defined by
      intersections and linear combinations of sensitive attributes.
    - Particularly powerful against "fairness gerrymandering" — where a model
      may appear fair on average but is unfair to specific sub-combinations.
    - When you can specify whether false positive, false negative, or statistical
      parity is the relevant fairness criterion for your application.
    - Suitable for high-stakes domains (criminal justice, lending) where
      subgroup accountability is legally or ethically required.
    - Computationally more expensive than simpler methods; best for moderate-
      sized datasets.


--------------------------------------------------------------------------------
4.4 GridSearchReduction
    Module: aif360.algorithms.inprocessing
--------------------------------------------------------------------------------

class aif360.algorithms.inprocessing.GridSearchReduction(
    estimator, constraints,
    prot_attr=None, constraint_weight=0.5,
    grid_size=10, grid_limit=2.0, grid=None,
    drop_prot_attr=True,
    loss='ZeroOne', min_val=None, max_val=None)[source]

Description:
    Grid search reduction for fair classification or regression. Grid search is
    an in-processing technique that can be used for fair classification or fair
    regression. For classification it reduces fair classification to a sequence
    of cost-sensitive classification problems, returning the deterministic
    classifier with the lowest empirical error subject to fair classification
    constraints [1] among the candidates searched. For regression it uses the
    same principle to return a deterministic regressor with the lowest empirical
    error subject to the constraint of bounded group loss [2].

References:
    [1] A. Agarwal, A. Beygelzimer, M. Dudik, J. Langford, and H. Wallach,
        "A Reductions Approach to Fair Classification," International Conference
        on Machine Learning, 2018.
    [2] A. Agarwal, M. Dudik, and Z. Wu, "Fair Regression: Quantitative
        Definitions and Reduction-based Algorithms," International Conference on
        Machine Learning, 2019.

Parameters:
    estimator
        An estimator implementing methods fit(X, y, sample_weight) and
        predict(X), where X is the matrix of features, y is the vector of
        labels, and sample_weight is a vector of weights; labels y and
        predictions returned by predict(X) are either 0 or 1 – e.g. scikit-learn
        classifiers/regressors.

    constraints (str or fairlearn.reductions.Moment)
        If string, keyword denoting the fairlearn.reductions.Moment object
        defining the disparity constraints – e.g., "DemographicParity" or
        "EqualizedOdds". For a full list of possible options see
        self.model.moments.

    prot_attr (str or list, optional)
        Protected attribute(s). Default is None (use all from dataset).

    constraint_weight (float, optional)
        Specifies the relative weight put on the constraint violation when
        selecting the best model. The weight on the error rate is
        (1 - constraint_weight). Default is 0.5.

    grid_size (int, optional)
        Number of Lagrange multipliers to search over for the constraint.
        Default is 10.

    grid_limit (float, optional)
        Upper bound on the Lagrange multiplier search range. Default is 2.0.

    grid (pandas.DataFrame or None, optional)
        Custom grid of Lagrange multipliers to use. If None, the grid is
        generated automatically.

    drop_prot_attr (bool, optional)
        If True, remove protected attributes from the feature matrix before
        fitting each estimator. Default is True.

    loss (str, optional)
        String identifying the loss function for regression. Options are:
        'ZeroOne', 'Square', 'Absolute'. Default is 'ZeroOne'.

    min_val (float or None, optional)
        Loss function parameter for "Square" and "Absolute," typically the
        minimum of the range of y values.

    max_val (float or None, optional)
        Loss function parameter for "Square" and "Absolute," typically the
        maximum of the range of y values.

Methods:
    fit(X, y=None)[source]
        Return GridSearchReduction – Returns self.

    predict(X)[source]
        Return predicted labels.

When to Use:
    - When you want to apply fairness constraints to an existing scikit-learn
      compatible estimator without modifying its internal training procedure.
    - Best when a deterministic (not randomized) fair classifier is desired.
    - Supports both classification and regression fairness tasks.
    - The grid search over Lagrange multipliers is interpretable and
      controllable via grid_size and grid_limit.
    - When you need to satisfy specific disparity constraints (e.g.,
      DemographicParity, EqualizedOdds) on a standard ML model.

Extra dependency:
    pip install aif360[Reductions]
    (requires fairlearn)


--------------------------------------------------------------------------------
4.5 ExponentiatedGradientReduction
    Module: aif360.algorithms.inprocessing
--------------------------------------------------------------------------------

class aif360.algorithms.inprocessing.ExponentiatedGradientReduction(
    estimator, constraints,
    eps=0.01, max_iter=50, nu=None, eta0=2.0,
    run_linprog_step=True,
    drop_prot_attr=True)[source]

Description:
    Exponentiated gradient reduction for fair classification. Exponentiated
    gradient reduction is an in-processing technique that reduces fair
    classification to a sequence of cost-sensitive classification problems,
    returning a randomized classifier with the lowest empirical error subject to
    fair classification constraints [1].

References:
    [1] A. Agarwal, A. Beygelzimer, M. Dudik, J. Langford, and H. Wallach,
        "A Reductions Approach to Fair Classification," International Conference
        on Machine Learning, 2018.

Parameters:
    estimator
        An estimator implementing methods fit(X, y, sample_weight) and
        predict(X), where X is the matrix of features, y is the vector of
        labels, and sample_weight is a vector of weights; labels y and
        predictions returned by predict(X) are either 0 or 1 – e.g. scikit-learn
        classifiers/regressors.

    constraints (str or fairlearn.reductions.Moment)
        If string, keyword denoting the fairlearn.reductions.Moment object
        defining the disparity constraints – e.g., "DemographicParity" or
        "EqualizedOdds".

    eps (float, optional)
        Allowed fairness constraint violation. Default is 0.01.

    max_iter (int, optional)
        Maximum number of iterations. Default is 50.

    nu (float or None, optional)
        Convergence threshold parameter. Default is None (auto-selected).

    eta0 (float, optional)
        Initial learning rate for the exponentiated gradient. Default is 2.0.

    run_linprog_step (bool, optional)
        If True, run a linear programming step to compute the best mixture
        weights. Default is True.

    drop_prot_attr (bool, optional)
        If True, protected attribute columns are removed from X before passing
        to the estimator. Default is True.

Methods:
    fit(X, y=None)[source]
        Return ExponentiatedGradientReduction – Returns self.

    predict(X)[source]
        Return predictions from the randomized classifier.

Metadata routing:
    set_score_request(*, sample_weight=...) → ExponentiatedGradientReduction
        Request metadata passed to the score method. Only relevant if
        enable_metadata_routing=True. Options: True, False, None, str (alias).

When to Use:
    - Similar to GridSearchReduction but returns a randomized classifier
      (mixture of base classifiers) instead of a deterministic one.
    - Preferred when a theoretically near-optimal solution to the constrained
      fairness problem is desired rather than a grid-searched approximation.
    - The eps parameter directly controls the allowable fairness violation,
      making it suitable when there is a specific numerical fairness budget.
    - Works well with any scikit-learn compatible base classifier.

Extra dependency:
    pip install aif360[Reductions]
    (requires fairlearn)


--------------------------------------------------------------------------------
4.6 MetaFairClassifier
    Module: aif360.algorithms.inprocessing
--------------------------------------------------------------------------------

class aif360.algorithms.inprocessing.MetaFairClassifier(
    tau=0.8, sensitive_attr='', type='fdr')[source]

Description:
    The meta algorithm here takes the fairness metric as part of the input and
    returns a classifier optimized w.r.t. that fairness metric [11].

References:
    [11] L. E. Celis, L. Huang, V. Keswani, and N. K. Vishnoi.
         "Classification with Fairness Constraints: A Meta-Algorithm with
         Provable Guarantees." ACM Conference on Fairness, Accountability, and
         Transparency, 2019.

Parameters:
    tau (float, optional)
        Fairness penalty parameter. Controls the allowed violation of the
        fairness metric. Default is 0.8.

    sensitive_attr (str, optional)
        Name of the protected/sensitive attribute column. Default is ''.

    type (str, optional)
        The fairness metric type to optimize. Options:
            'fdr' – False discovery rate parity
            'sr'  – Statistical rate (demographic parity)
        Default is 'fdr'.

Methods:
    fit(dataset)[source]
        Learns the fair classifier.

    fit_predict(dataset)
        Train a model on the input and predict the labels. Equivalent to calling
        fit(dataset) followed by predict(dataset).

    predict(dataset)[source]
        Obtain the predictions for the provided dataset using the learned
        classifier model.

When to Use:
    - When a specific, named fairness metric (e.g., false discovery rate parity
      or statistical rate) must be directly optimized during training.
    - Provides provable guarantees with respect to the specified metric.
    - Suitable when regulatory or policy requirements mandate a specific fairness
      criterion be satisfied.
    - For binary classification tasks where a single sensitive attribute is
      defined.


--------------------------------------------------------------------------------
4.7 PrejudiceRemover
    Module: aif360.algorithms.inprocessing
--------------------------------------------------------------------------------

class aif360.algorithms.inprocessing.PrejudiceRemover(
    eta=1.0, sensitive_attr='', class_attr='')[source]

Description:
    Prejudice remover is an in-processing technique that adds a
    discrimination-aware regularization term to the learning objective [6].

References:
    [6] T. Kamishima, S. Akaho, H. Asoh, and J. Sakuma, "Fairness-Aware
        Classifier with Prejudice Remover Regularizer," Joint European
        Conference on Machine Learning and Knowledge Discovery in Databases,
        2012.

Parameters:
    eta (float, optional)
        Fairness penalty parameter. Controls the strength of the
        discrimination-aware regularization term. Higher values enforce
        stronger fairness but may reduce accuracy. Default is 1.0.

    sensitive_attr (str, optional)
        Name of the sensitive attribute column. Default is ''.

    class_attr (str, optional)
        Name of the class/label attribute column. Default is ''.

Methods:
    fit(dataset)[source]
        Learns the regularized logistic regression model.

        Returns:
            PrejudiceRemover – Returns self.

    fit_predict(dataset)
        Train a model on the input and predict the labels. Equivalent to calling
        fit(dataset) followed by predict(dataset).

    predict(dataset)[source]
        Obtain predictions.

When to Use:
    - When you want to add a fairness-aware regularization term to a logistic
      regression without rewriting the training loop from scratch.
    - Simple and computationally lightweight compared to neural adversarial
      approaches.
    - The eta parameter provides a clear, interpretable control knob for the
      accuracy–fairness trade-off.
    - Best suited for tabular binary classification with a single sensitive
      attribute.
    - Does not require TensorFlow or any heavy ML framework.


--------------------------------------------------------------------------------
4.8 AdversarialDebiasing (sklearn API)
    Module: aif360.sklearn.inprocessing
--------------------------------------------------------------------------------

class aif360.sklearn.inprocessing.AdversarialDebiasing(
    prot_attr=None, scope_name='classifier',
    adversary_loss_weight=0.1, num_epochs=50,
    batch_size=128, classifier_num_hidden_units=200,
    debias=True, verbose=False,
    random_state=None)[source]

Description:
    Debiasing with adversarial learning. Adversarial debiasing is an
    in-processing technique that learns a classifier to maximize prediction
    accuracy and simultaneously reduce an adversary's ability to determine the
    protected attribute from the predictions [1]. This approach leads to a fair
    classifier as the predictions cannot carry any group discrimination
    information that the adversary can exploit.

References:
    [1] B. H. Zhang, B. Lemoine, and M. Mitchell, "Mitigating Unwanted Biases
        with Adversarial Learning," AAAI/ACM Conference on Artificial
        Intelligence, Ethics, and Society, 2018.

Parameters:
    prot_attr (single label or list-like, optional)
        Protected attribute(s). Default is None (uses all from index).

    scope_name (str, optional)
        TensorFlow variable scope name. Default is 'classifier'.

    adversary_loss_weight (float, optional)
        Weight of the adversary's loss in the joint objective. Default is 0.1.

    num_epochs (int, optional)
        Number of training epochs. Default is 50.

    batch_size (int, optional)
        Mini-batch size. Default is 128.

    classifier_num_hidden_units (int, optional)
        Number of hidden units in the classifier. Default is 200.

    debias (bool, optional)
        If True, apply adversarial debiasing; if False, standard training.
        Default is True.

    verbose (bool, optional)
        If True, print training progress. Default is False.

    random_state (int or None, optional)
        Seed for reproducibility. Default is None.

Methods:
    fit(X, y)[source]
        Train the adversarial debiasing classifier.

    predict(X)[source]
        Predict class labels.

    predict_proba(X)[source]
        Soft prediction scores.

Usage Example:
    >>> from aif360.sklearn.inprocessing import AdversarialDebiasing
    >>> y_pred = AdversarialDebiasing().fit(X_train, y_train).predict(X_test)

When to Use:
    - Same use cases as the legacy AdversarialDebiasing (Section 4.1), but
      designed for the sklearn-compatible pipeline.
    - Can be used directly in sklearn workflows without manual TF session
      management.

Extra dependency:
    pip install aif360[AdversarialDebiasing]


--------------------------------------------------------------------------------
4.9 ExponentiatedGradientReduction (sklearn API)
    Module: aif360.sklearn.inprocessing
--------------------------------------------------------------------------------

class aif360.sklearn.inprocessing.ExponentiatedGradientReduction(
    prot_attr, estimator, constraints,
    eps=0.01, max_iter=50, nu=None, eta0=2.0,
    run_linprog_step=True,
    drop_prot_attr=True)[source]

Description:
    Exponentiated gradient reduction for fair classification. Exponentiated
    gradient reduction is an in-processing technique that reduces fair
    classification to a sequence of cost-sensitive classification problems,
    returning a randomized classifier with the lowest empirical error subject to
    fair classification constraints [1].

References:
    [1] A. Agarwal, A. Beygelzimer, M. Dudik, J. Langford, and H. Wallach,
        "A Reductions Approach to Fair Classification," International Conference
        on Machine Learning, 2018.

Parameters:
    prot_attr (single label or list-like)
        Protected attribute(s) to use. (Required in sklearn version, unlike
        legacy version where it is optional.)

    estimator
        A scikit-learn compatible base classifier/regressor supporting
        fit(X, y, sample_weight) and predict(X).

    constraints (str or fairlearn.reductions.Moment)
        Disparity constraints – e.g., "DemographicParity" or "EqualizedOdds".

    eps (float, optional)
        Allowed fairness constraint violation. Default is 0.01.

    max_iter (int, optional)
        Maximum number of iterations. Default is 50.

    nu, eta0, run_linprog_step, drop_prot_attr
        Same as legacy version (Section 4.5).

Metadata routing:
    set_score_request(*, sample_weight=...) → ExponentiatedGradientReduction
        See sklearn metadata routing documentation.

Extra dependency:
    pip install aif360[Reductions]


--------------------------------------------------------------------------------
4.10 GridSearchReduction (sklearn API)
    Module: aif360.sklearn.inprocessing
--------------------------------------------------------------------------------

class aif360.sklearn.inprocessing.GridSearchReduction(
    prot_attr, estimator, constraints,
    constraint_weight=0.5, grid_size=10, grid_limit=2.0,
    grid=None, drop_prot_attr=True,
    loss='ZeroOne', min_val=None, max_val=None)[source]

Description:
    Grid search reduction for fair classification or regression. Same algorithm
    as the legacy version (Section 4.4), adapted for the sklearn-compatible API.

Parameters:
    prot_attr (single label or list-like)
        Protected attribute(s). (Required in sklearn version.)

    All other parameters are identical to the legacy GridSearchReduction
    (Section 4.4).

Extra dependency:
    pip install aif360[Reductions]


================================================================================
5. POSTPROCESSING ALGORITHMS
================================================================================

Post-processing algorithms take the predictions of an already-trained model and
adjust them to be fairer. They require access to ground truth labels during
fitting (on a held-out calibration or validation set) but do not modify the
underlying model.

--------------------------------------------------------------------------------
5.1 CalibratedEqOddsPostprocessing
    Module: aif360.algorithms.postprocessing
--------------------------------------------------------------------------------

class aif360.algorithms.postprocessing.CalibratedEqOddsPostprocessing(
    unprivileged_groups, privileged_groups,
    cost_constraint='weighted',
    seed=None)[source]

Description:
    Calibrated equalized odds postprocessing is a post-processing technique
    that optimizes over calibrated classifier score outputs to find probabilities
    with which to change output labels with an equalized odds objective [7].

References:
    [7] G. Pleiss, M. Raghavan, F. Wu, J. Kleinberg, and K. Q. Weinberger,
        "On Fairness and Calibration," Conference on Neural Information
        Processing Systems, 2017.
    Adapted from:
        https://github.com/gpleiss/equalized_odds_and_calibration/blob/master/calib_eq_odds.py

Parameters:
    unprivileged_groups (dict or list(dict))
        Representation for unprivileged group.

    privileged_groups (dict or list(dict))
        Representation for privileged group.

    cost_constraint (str, optional)
        The cost constraint to optimize. Options:
            'weighted' – Weighted combination of false positive and false
                         negative costs.
            'fpr'      – Optimize for false positive rate parity.
            'fnr'      – Optimize for false negative rate parity.
        Default is 'weighted'.

    seed (int, optional)
        Seed for reproducibility.

Methods:
    fit(dataset_true, dataset_pred)[source]
        Compute parameters for equalizing generalized odds using true and
        predicted scores, while preserving calibration.

        Returns:
            CalibratedEqOddsPostprocessing – Returns self.

    fit_predict(dataset_true, dataset_pred, threshold=0.5)[source]
        Fit and predict in sequence.

    predict(dataset, threshold=0.5)[source]
        Perturb the predicted scores to obtain new labels that satisfy equalized
        odds constraints, while preserving calibration.

Source (abbreviated):
    from aif360.metrics import ClassificationMetric, utils

    class CalibratedEqOddsPostprocessing(Transformer):
        """Calibrated equalized odds postprocessing is a post-processing technique
        that optimizes over calibrated classifier score outputs to find
        probabilities with which to change output labels with an equalized odds
        objective [7].

        References:
            .. [7] G. Pleiss, M. Raghavan, F. Wu, J. Kleinberg, and
               K. Q. Weinberger, "On Fairness and Calibration," Conference on Neural
               Information Processing Systems, 2017

        Adapted from:
        https://github.com/gpleiss/equalized_odds_and_calibration/blob/master/calib_eq_odds.py
        """
        def __init__(self, unprivileged_groups, privileged_groups,
                     cost_constraint='weighted', seed=None):
            """
            Args:
                unprivileged_groups (dict or list(dict)): Representation for
                ...

When to Use:
    - When a model already produces calibrated probability scores and you want
      to post-process them to achieve equalized odds without disrupting
      calibration.
    - Key advantage over EqOddsPostprocessing: it preserves calibration, which
      is important when score values carry meaning (e.g., in medical or
      financial risk scoring).
    - The cost_constraint parameter allows tuning between false positive and
      false negative parity, which is critical when the two types of errors
      have asymmetric real-world costs.
    - Use 'fpr' when false positive harm is dominant (e.g., false positive
      arrest); use 'fnr' when false negative harm is dominant (e.g., missing
      a sick patient); use 'weighted' for a balanced correction.
    - Requires an existing trained model's score outputs; does not modify
      the model itself.


--------------------------------------------------------------------------------
5.2 EqOddsPostprocessing
    Module: aif360.algorithms.postprocessing
--------------------------------------------------------------------------------

class aif360.algorithms.postprocessing.EqOddsPostprocessing(
    unprivileged_groups, privileged_groups,
    seed=None)[source]

Description:
    Equalized odds postprocessing is a post-processing technique that solves a
    linear program to find probabilities with which to change output labels to
    optimize equalized odds [8] [9].

References:
    [8] M. Hardt, E. Price, and N. Srebro, "Equality of Opportunity in
        Supervised Learning," Conference on Neural Information Processing
        Systems, 2016.
    [9] G. Pleiss, M. Raghavan, F. Wu, J. Kleinberg, and K. Q. Weinberger,
        "On Fairness and Calibration," Conference on Neural Information
        Processing Systems, 2017.

Parameters:
    unprivileged_groups (list(dict))
        Representation for unprivileged group.

    privileged_groups (list(dict))
        Representation for privileged group.

    seed (int, optional)
        Seed for reproducibility.

Methods:
    fit(dataset_true, dataset_pred)[source]
        Compute parameters for equalizing odds using true and predicted labels.

        Returns:
            EqOddsPostprocessing – Returns self.

    fit_predict(dataset_true, dataset_pred)[source]
        fit and predict methods sequentially.

    predict(dataset)[source]
        Perturb the predicted labels to obtain new labels that satisfy equalized
        odds constraints.

Source (abbreviated):
    from aif360.metrics import ClassificationMetric, utils

    class EqOddsPostprocessing(Transformer):
        """Equalized odds postprocessing is a post-processing technique that solves
        a linear program to find probabilities with which to change output labels to
        optimize equalized odds [8]_ [9]_.

        References:
            .. [8] M. Hardt, E. Price, and N. Srebro, "Equality of Opportunity in
               Supervised Learning," Conference on Neural Information Processing
               Systems, 2016.
            .. [9] G. Pleiss, M. Raghavan, F. Wu, J. Kleinberg, and
               K. Q. Weinberger, "On Fairness and Calibration," Conference on Neural
               Information Processing Systems, 2017.
        """
        def __init__(self, unprivileged_groups, privileged_groups, seed=None):
            """
            Args:
                unprivileged_groups (list(dict)): Representation for unprivileged
                ...

When to Use:
    - When equalized odds (equal TPR and FPR across groups) is the target
      fairness constraint.
    - Simpler than CalibratedEqOddsPostprocessing: works directly on predicted
      labels (not scores) and does not require calibration.
    - Use when calibration is less important and binary label parity is
      sufficient.
    - The linear program optimally solves for the mixing probabilities, so the
      result is globally optimal given the constraint.
    - Best when you cannot retrain the original model but need to satisfy
      equalized odds before deployment.
    - Works on top of any binary classifier.


--------------------------------------------------------------------------------
5.3 RejectOptionClassification
    Module: aif360.algorithms.postprocessing
--------------------------------------------------------------------------------

class aif360.algorithms.postprocessing.RejectOptionClassification(
    unprivileged_groups, privileged_groups,
    low_class_thresh=0.01, high_class_thresh=0.99,
    num_class_thresh=100, num_ROC_margin=50,
    metric_name='Statistical parity difference',
    metric_ub=0.05, metric_lb=-0.05)[source]

Description:
    Reject option classification is a postprocessing technique that gives
    favorable outcomes to unprivileged groups and unfavorable outcomes to
    privileged groups in a confidence band around the decision boundary with the
    highest uncertainty [10].

References:
    [10] F. Kamiran, A. Karim, and X. Zhang, "Decision Theory for
         Discrimination-Aware Classification," IEEE International Conference on
         Data Mining, 2012.

Parameters:
    unprivileged_groups (dict or list(dict))
        Representation for unprivileged group.

    privileged_groups (dict or list(dict))
        Representation for privileged group.

    low_class_thresh (float, optional)
        Smallest classification threshold to use in the optimization.
        Should be between 0. and 1. Default is 0.01.

    high_class_thresh (float, optional)
        Highest classification threshold to use in the optimization.
        Default is 0.99.

    num_class_thresh (int, optional)
        Number of classification thresholds between low_class_thresh and
        high_class_thresh for the optimization search. Default is 100.

    num_ROC_margin (int, optional)
        Number of margin values between 0 and the maximum margin for the ROC
        optimization. Default is 50.

    metric_name (str, optional)
        Name of the metric to optimize in the fairness-accuracy trade-off.
        Default is 'Statistical parity difference'.
        Other options include 'Average odds difference', 'Equal opportunity
        difference', etc.

    metric_ub (float, optional)
        Upper bound on the fairness metric value. Default is 0.05.

    metric_lb (float, optional)
        Lower bound on the fairness metric value. Default is -0.05.

Methods:
    fit(dataset_true, dataset_pred)[source]
        Estimates the optimal classification threshold and margin for reject
        option classification that optimizes the metric provided.

        Note:
            The fit function is a no-op for this algorithm (the optimization
            is done in fit).

    fit_predict(dataset_true, dataset_pred)[source]
        Obtain fair predictions using the ROC method.

    predict(dataset)[source]
        Uses the estimated threshold and margin to predict.
        Args:
            dataset (BinaryLabelDataset) – Dataset containing scores that will
            be used to compute predicted labels.

Source (abbreviated):
    from aif360.metrics import BinaryLabelDatasetMetric, ClassificationMetric

    class RejectOptionClassification(Transformer):
        """Reject option classification is a postprocessing technique that gives
        favorable outcomes to unpriviliged groups and unfavorable outcomes to
        priviliged groups in a confidence band around the decision boundary with the
        highest uncertainty [10].

        References:
            .. [10] F. Kamiran, A. Karim, and X. Zhang, "Decision Theory for
               Discrimination-Aware Classification," IEEE International Conference
               on Data Mining, 2012.
        """
        def __init__(self, unprivileged_groups, privileged_groups,
                    low_class_thresh=0.01, high_class_thresh=0.99,
                    num_class_thresh=100, num_ROC_margin=50,
                    metric_name="Statistical parity difference",
                    metric_ub=0.05, metric_lb=-0.05):
            """
            Args:
                unprivileged_groups (dict or list(dict)): Repres...

When to Use:
    - When you want a simple, intuitive post-processing approach that flips
      predictions in the "uncertain zone" near the decision boundary to favor
      the unprivileged group.
    - Particularly useful when the classifier already has reasonable accuracy but
      produces systematically biased outcomes near the decision boundary.
    - The metric_name, metric_ub, and metric_lb parameters allow flexible
      specification of which fairness metric to satisfy and how tightly.
    - Use low_class_thresh and high_class_thresh to limit changes to only the
      most uncertain predictions, minimizing accuracy degradation.
    - Suitable when the downstream fairness requirement is statistical parity,
      average odds, or equal opportunity.
    - Does not require the training data — only the model's score output.


--------------------------------------------------------------------------------
5.4 CalibratedEqualizedOdds (sklearn API)
    Module: aif360.sklearn.postprocessing
--------------------------------------------------------------------------------

class aif360.sklearn.postprocessing.CalibratedEqualizedOdds(
    prot_attr=None,
    cost_constraint='weighted',
    random_state=None)[source]

Description:
    Calibrated equalized odds post-processor. Calibrated equalized odds is a
    post-processing technique that optimizes over calibrated classifier score
    outputs to find probabilities with which to change output labels with an
    equalized odds objective [1].

    Note: A Pipeline expects a single estimation step but this class requires an
    estimator's predictions as input. See PostProcessingMeta for a workaround.

References:
    [1] G. Pleiss, M. Raghavan, F. Wu, J. Kleinberg, and K. Q. Weinberger,
        "On Fairness and Calibration," Conference on Neural Information
        Processing Systems, 2017.
    Adapted from:
        https://github.com/gpleiss/equalized_odds_and_calibration/blob/master/calib_eq_odds.py

Parameters:
    prot_attr (single label or list-like, optional)
        Protected attribute(s). Default is None.

    cost_constraint (str, optional)
        Cost constraint type: 'weighted', 'fpr', or 'fnr'. Default is 'weighted'.

    random_state (int or None, optional)
        Seed for reproducibility.

Attributes (post-fit):
    groups_ (array, shape (2,))
        A list of group labels known to the classifier.

Methods:
    fit(X, y, labels=None)[source]
        Compute the mixing rates required to satisfy the cost constraint.

    predict(X)[source]
        Predict class labels for the given scores.

    predict_proba(X)[source]
        The returned estimates for all classes are ordered by the label of
        classes.

When to Use:
    - Same as legacy CalibratedEqOddsPostprocessing (Section 5.1), but designed
      for the sklearn-compatible pipeline.
    - Use with PostProcessingMeta (Section 5.6) for Pipeline compatibility.


--------------------------------------------------------------------------------
5.5 RejectOptionClassifierCV (sklearn API)
    Module: aif360.sklearn.postprocessing
--------------------------------------------------------------------------------

class aif360.sklearn.postprocessing.RejectOptionClassifierCV(
    prot_attr, scoring='statistical_parity', ...)[source]

Description:
    Cross-validated variant of reject option classification for the sklearn API.
    Automatically selects the optimal threshold and margin via cross-validation
    over the specified fairness scoring metric.

Parameters:
    prot_attr (single label or list-like)
        Protected attribute(s).

    scoring (str or callable, optional)
        Fairness metric to optimize during cross-validation. Example values:
            'disparate_impact'
            'statistical_parity'
            'average_odds_difference'
        Default is 'statistical_parity'.

Usage Example:
    >>> from sklearn.linear_model import LogisticRegression
    >>> from aif360.sklearn.postprocessing import RejectOptionClassifierCV, PostProcessingMeta
    >>> y_pred = PostProcessingMeta(
    ...     LogisticRegression(),
    ...     RejectOptionClassifierCV('sex', scoring='disparate_impact')
    ... ).fit(X_train, y_train).predict(X_test)

When to Use:
    - When you want automatic, data-driven selection of the optimal reject option
      threshold and margin, rather than manually specifying them.
    - Best combined with PostProcessingMeta for end-to-end pipeline use.


--------------------------------------------------------------------------------
5.6 PostProcessingMeta (sklearn API)
    Module: aif360.sklearn.postprocessing
--------------------------------------------------------------------------------

class aif360.sklearn.postprocessing.PostProcessingMeta(
    estimator, postprocessor, *,
    prefit=False, val_size=0.25,
    **options)[source]

Description:
    A meta-estimator which wraps a given estimator with a post-processing step.
    The post-processor trains on a separate training set from the estimator to
    prevent leakage.

    Note: Because of the dataset splitting, if a Pipeline is necessary it should
    be used as the input to this meta-estimator not the other way around.

Parameters:
    estimator
        A scikit-learn estimator (classifier or regressor) to wrap.

    postprocessor
        An AIF360 post-processing object (e.g., CalibratedEqualizedOdds,
        RejectOptionClassifierCV).

    prefit (bool, optional)
        If True, it is assumed that estimator has been fitted already and all
        data is used to train postprocessor. Default is False.

    val_size (int or float, optional)
        Size of validation set used to fit the postprocessor. The estimator fits
        on the remainder of the training set. See train_test_split() for details.
        Note: 'train_size' and 'test_size' will be ignored in favor of
        'val_size'. Default is 0.25.

    **options
        Keyword options passed through to train_test_split().

Metadata routing:
    set_fit_request(*, sample_weight=...) → PostProcessingMeta
        Request metadata passed to the fit method. Only relevant if
        enable_metadata_routing=True. Options: True, False, None, str.

    set_score_request(*, sample_weight=...) → PostProcessingMeta
        Request metadata passed to the score method. Same options as above.

Methods:
    fit(X, y, **fit_params)[source]
        Fit estimator on training split and postprocessor on validation split.

    predict(X)[source]
        Predict using the fitted estimator and postprocessor.

Usage Example:
    >>> from sklearn.linear_model import LogisticRegression
    >>> from aif360.sklearn.postprocessing import RejectOptionClassifierCV, PostProcessingMeta
    >>> y_pred = PostProcessingMeta(
    ...     LogisticRegression(),
    ...     RejectOptionClassifierCV('sex', scoring='disparate_impact')
    ... ).fit(X_train, y_train).predict(X_test)

When to Use:
    - Essential for integrating any post-processor into an sklearn Pipeline.
    - Handles the train/validation split automatically to prevent the
      post-processor from seeing the same data used to train the base estimator.
    - Use prefit=True when the base estimator is already trained (e.g., a
      deployed model) and only the post-processor needs to be fitted.
    - val_size controls how much data is reserved for post-processor training;
      adjust based on dataset size and desired accuracy of each component.


================================================================================
6. SCENARIO GUIDE: CHOOSING THE RIGHT ALGORITHM
================================================================================

This section provides guidance on matching algorithms to real-world scenarios.

--------------------------------------------------------------------------------
A. YOU CANNOT RETRAIN THE MODEL (Black-box, Deployed Model)
--------------------------------------------------------------------------------
Use a POSTPROCESSING algorithm. The model is fixed; only its outputs are adjusted.

  - CalibratedEqOddsPostprocessing / CalibratedEqualizedOdds
      → When the model outputs well-calibrated probability scores AND you need
        to preserve calibration in the adjusted outputs.
      → When different error types (FP vs. FN) have different costs by group.

  - EqOddsPostprocessing
      → When equalized odds (equal TPR + FPR across groups) is the strict target.
      → When calibration preservation is not required.
      → Simpler than CalibratedEq; use as a baseline.

  - RejectOptionClassification / RejectOptionClassifierCV
      → When you want to give favorable outcomes to the unprivileged group
        specifically in uncertain/borderline cases near the decision boundary.
      → When statistical parity, average odds, or equal opportunity is the
        target metric.
      → When minimizing intervention (changing as few predictions as possible)
        is important.

--------------------------------------------------------------------------------
B. YOU CAN RETRAIN THE MODEL (Training data and model architecture available)
--------------------------------------------------------------------------------
Use a PREPROCESSING or INPROCESSING algorithm.

  Preprocessing (modify data, then train any model):
  ---------------------------------------------------
  - Reweighing
      → Simplest option. Does not alter features or labels; only assigns
        different training weights.
      → First approach to try as a baseline.
      → Works with any scikit-learn model that supports sample_weight.

  - DisparateImpactRemover
      → When the disparate impact ratio of features (not labels) is the root
        cause of bias.
      → When rank-ordering within groups must be preserved (e.g., credit scoring).
      → Use repair_level to tune the fairness–accuracy trade-off.

  - LFR / LearnedFairRepresentations
      → When a model-agnostic latent representation is desired that can be
        reused across multiple downstream classifiers.
      → When the protected attribute information should be entirely obfuscated
        in the learned space.

  - OptimPreproc
      → When a principled probabilistic transformation of both features and
        labels is needed with explicit distortion and fidelity constraints.
      → When dealing with multi-group fairness (all combinations are handled
        automatically).
      → Computationally expensive; requires CVXPY.

  - FairAdapt
      → When a causal graph of the data generating process is available.
      → When counterfactual fairness (causal fairness) is required.
      → Applicable to both classification and regression.

  Inprocessing (modify training procedure):
  -----------------------------------------
  - PrejudiceRemover
      → Lightest-weight inprocessing option. Adds a regularization term to
        logistic regression.
      → When a transparent, single-model solution with an explicit fairness
        penalty is preferred.

  - AdversarialDebiasing
      → When a neural network model is acceptable and fairness must be enforced
        at training time.
      → When prediction accuracy is the primary goal and you want the adversary
        to specifically suppress protected attribute information in predictions.
      → Requires TensorFlow and substantial training data.

  - GerryFairClassifier
      → When standard group fairness is insufficient: you need fairness over
        rich subgroups (intersectional combinations of sensitive attributes).
      → When "fairness gerrymandering" (being fair on average but unfair to
        subgroups) is a concern.
      → When a specific fairness notion (FP, FN, or SP) must be satisfied
        across all possible linear subgroups.

  - MetaFairClassifier
      → When a provably fair classifier with respect to a specific metric (false
        discovery rate or statistical rate) is required.
      → When regulatory requirements mandate a specific fairness criterion.

  - ExponentiatedGradientReduction / GridSearchReduction
      → When fairness must be applied to an existing scikit-learn model without
        changing its architecture.
      → ExponentiatedGradient: returns a randomized classifier; theoretically
        near-optimal; better for tight fairness budgets (use eps to set budget).
      → GridSearch: returns a deterministic classifier; easier to interpret;
        use when a single deployable model is required.
      → Both support a wide range of fairness constraints via fairlearn.

  - ARTClassifier
      → When adversarial robustness (ART) AND fairness (AIF360) are both
        requirements simultaneously.

--------------------------------------------------------------------------------
C. BY FAIRNESS METRIC TARGET
--------------------------------------------------------------------------------
  Statistical Parity / Demographic Parity:
      → Reweighing, DisparateImpactRemover, RejectOptionClassification,
         ExponentiatedGradientReduction (constraints='DemographicParity'),
         GridSearchReduction (constraints='DemographicParity'),
         MetaFairClassifier (type='sr')

  Equalized Odds (equal TPR + FPR):
      → EqOddsPostprocessing, CalibratedEqOddsPostprocessing,
         ExponentiatedGradientReduction (constraints='EqualizedOdds'),
         GridSearchReduction (constraints='EqualizedOdds'),
         AdversarialDebiasing

  Equal Opportunity (equal TPR only):
      → RejectOptionClassification (metric_name='Equal opportunity difference'),
         ExponentiatedGradientReduction (constraints='TruePositiveRateParity')

  False Positive Rate Parity:
      → CalibratedEqOddsPostprocessing (cost_constraint='fpr'),
         GerryFairClassifier (fairness_def='FP')

  False Negative Rate Parity:
      → CalibratedEqOddsPostprocessing (cost_constraint='fnr'),
         GerryFairClassifier (fairness_def='FN')

  False Discovery Rate Parity:
      → MetaFairClassifier (type='fdr')

  Calibration Preservation:
      → CalibratedEqOddsPostprocessing / CalibratedEqualizedOdds

  Causal / Counterfactual Fairness:
      → FairAdapt

  Rich Subgroup / Intersectional Fairness:
      → GerryFairClassifier

--------------------------------------------------------------------------------
D. BY DATA / RESOURCE CONSTRAINTS
--------------------------------------------------------------------------------
  Small datasets:
      → Reweighing, PrejudiceRemover, Reweighing (sklearn), RejectOptionClassification
      → Avoid: AdversarialDebiasing (needs many samples for neural net training)

  No retraining possible:
      → CalibratedEqOddsPostprocessing, EqOddsPostprocessing,
         RejectOptionClassification

  No feature modification desired:
      → Reweighing (only weights change), postprocessing algorithms

  Regression tasks (not just classification):
      → GridSearchReduction (loss='Square' or 'Absolute'),
         ExponentiatedGradientReduction,
         FairAdapt

  Multiple protected attributes simultaneously:
      → OptimPreproc (handles all combinations automatically),
         GerryFairClassifier,
         Reweighing (sklearn, supports intersections via prot_attr list),
         ExponentiatedGradientReduction / GridSearchReduction

  Need sklearn Pipeline compatibility:
      → All aif360.sklearn.* classes
      → ReweighingMeta (wraps Reweighing for Pipeline use)
      → PostProcessingMeta (wraps post-processors for Pipeline use)


================================================================================
7. INSTALLATION NOTES FOR ALGORITHM DEPENDENCIES
================================================================================

Install the latest stable version of AIF360:

    pip install aif360

Some algorithms require additional dependencies. To install with specific
algorithm dependencies:

    pip install aif360[OptimPreproc]      # CVXPY for OptimPreproc
    pip install aif360[LFR]               # for LFR
    pip install aif360[AdversarialDebiasing]  # TensorFlow for AdversarialDebiasing
    pip install aif360[DisparateImpactRemover]  # BlackBoxAuditing
    pip install aif360[LIME]              # for LIME explainability
    pip install aif360[ART]               # Adversarial Robustness Toolbox
    pip install aif360[Reductions]        # fairlearn for Reductions algorithms
    pip install aif360[FairAdapt]         # FairAdapt
    pip install aif360[inFairness]        # inFairness algorithms
    pip install aif360[LawSchoolGPA]      # LawSchoolGPA dataset
    pip install aif360[all]               # Install all optional dependencies

Notes:
    - TensorFlow is ONLY required for aif360.algorithms.inprocessing.AdversarialDebiasing
      (and aif360.sklearn.inprocessing.AdversarialDebiasing).
    - On macOS, you may first have to install the Xcode Command Line Tools:
        xcode-select --install
    - On Windows, you may need to download the Microsoft C++ Build Tools for
      Visual Studio 2019. See the CVXPY Install page for up-to-date instructions.
      CVXPY is only required for aif360.algorithms.preprocessing.OptimPreproc.
    - It is recommended to install AIF360 in a virtual environment (e.g., conda).
    - AIF360 is also available as an R package:
        install.packages("aif360")  # from CRAN

For guidance on choosing an algorithm, see the Resources page:
    https://aif360.readthedocs.io/en/stable/

For tutorials and demo notebooks, see the AIF360 examples directory:
    https://github.com/Trusted-AI/AIF360/tree/main/examples

Technical description of AI Fairness 360:
    @misc{aif360-oct-2018,
      title = "{AI Fairness} 360: An Extensible Toolkit for Detecting,
               Understanding, and Mitigating Unwanted Algorithmic Bias",
      author = {Rachel K. E. Bellamy and Kuntal Dey and Michael Hind and
                Samuel C. Hoffman and Stephanie Houde and Kalapriya Kannan and
                Pranay Lohia and Jacquelyn Martino and Sameep Mehta and
                Aleksandra Mojsilovic and Seema Nagar and Karthikeyan Natesan
                Ramamurthy and John Richards and Diptikalyan Saha and
                Prasanna Sattigeri and Moninder Singh and Kush R. Varshney and
                Yunfeng Zhang},
      month = oct,
      year = {2018},
      url = {https://arxiv.org/abs/1810.01943}
    }

================================================================================
END OF DOCUMENTATION
================================================================================
