From 1b829d7d6d962fb4febc74f478081f8103d99de5 Mon Sep 17 00:00:00 2001 From: Samuel Hoffman Date: Wed, 19 Feb 2020 15:29:32 -0500 Subject: [PATCH] various fixes to address PR comments * added one-hot encoding example and random_states to demo notebook * added 'prefit' option to PostProcessingMeta * multiple fixes to docstring wordings * added additional links/disclaimers in docstrings * renamed CalibratedEqualizedOdds args to X and y --- aif360/sklearn/datasets/openml_datasets.py | 37 +- .../inprocessing/adversarial_debiasing.py | 2 +- aif360/sklearn/metrics/metrics.py | 35 +- aif360/sklearn/postprocessing/__init__.py | 81 ++- .../calibrated_equalized_odds.py | 69 +-- aif360/sklearn/preprocessing/reweighing.py | 3 + aif360/sklearn/utils.py | 27 +- examples/sklearn/demo_new_features.ipynb | 557 +++++------------- 8 files changed, 324 insertions(+), 487 deletions(-) diff --git a/aif360/sklearn/datasets/openml_datasets.py b/aif360/sklearn/datasets/openml_datasets.py index 16d3165f..f4c78e67 100644 --- a/aif360/sklearn/datasets/openml_datasets.py +++ b/aif360/sklearn/datasets/openml_datasets.py @@ -41,6 +41,10 @@ def fetch_adult(subset='all', data_home=None, binary_race=True, usecols=[], unprivileged). The outcome variable is 'annual-income': '>50K' (favorable) or '<=50K' (unfavorable). + Note: + By default, the data is downloaded from OpenML. See the `adult + `_ page for details. + Args: subset ({'train', 'test', or 'all'}, optional): Select the dataset to load: 'train' for the training set, 'test' for the test set, 'all' @@ -60,6 +64,9 @@ def fetch_adult(subset='all', data_home=None, binary_race=True, usecols=[], namedtuple: Tuple containing X, y, and sample_weights for the Adult dataset accessible by index or name. + See also: + :func:`sklearn.datasets.fetch_openml` + Examples: >>> adult = fetch_adult() >>> adult.X.shape @@ -103,11 +110,9 @@ def fetch_german(data_home=None, binary_age=True, usecols=[], dropcols=[], unprivileged; see the binary_age flag to keep this continuous). The outcome variable is 'credit-risk': 'good' (favorable) or 'bad' (unfavorable). - References: - .. [#kamiran09] `F. Kamiran and T. Calders, "Classifying without - discriminating," 2nd International Conference on Computer, - Control and Communication, 2009. - `_ + Note: + By default, the data is downloaded from OpenML. See the `credit-g + `_ page for details. Args: data_home (string, optional): Specify another download and cache folder @@ -126,6 +131,15 @@ def fetch_german(data_home=None, binary_age=True, usecols=[], dropcols=[], namedtuple: Tuple containing X and y for the German dataset accessible by index or name. + See also: + :func:`sklearn.datasets.fetch_openml` + + References: + .. [#kamiran09] `F. Kamiran and T. Calders, "Classifying without + discriminating," 2nd International Conference on Computer, + Control and Communication, 2009. + `_ + Examples: >>> german = fetch_german() >>> german.X.shape @@ -142,7 +156,6 @@ def fetch_german(data_home=None, binary_age=True, usecols=[], dropcols=[], >>> disparate_impact_ratio(y, y_pred, prot_attr='age', priv_group=True, ... pos_label='good') 0.9483094846144106 - """ df = to_dataframe(fetch_openml(data_id=31, target_column=None, data_home=data_home or DATA_HOME_DEFAULT)) @@ -175,7 +188,11 @@ def fetch_bank(data_home=None, percent10=False, usecols=[], dropcols='duration', """Load the Bank Marketing Dataset. The protected attribute is 'age' (left as continuous). The outcome variable - is 'deposit': ``True`` or ``False``. + is 'deposit': 'yes' or 'no'. + + Note: + By default, the data is downloaded from OpenML. See the `bank-marketing + `_ page for details. Args: data_home (string, optional): Specify another download and cache folder @@ -193,6 +210,9 @@ def fetch_bank(data_home=None, percent10=False, usecols=[], dropcols='duration', namedtuple: Tuple containing X and y for the Bank dataset accessible by index or name. + See also: + :func:`sklearn.datasets.fetch_openml` + Examples: >>> bank = fetch_bank() >>> bank.X.shape @@ -214,7 +234,8 @@ def fetch_bank(data_home=None, percent10=False, usecols=[], dropcols='duration', 'housing', 'loan', 'contact', 'day', 'month', 'duration', 'campaign', 'pdays', 'previous', 'poutcome', 'deposit'] # remap target - df.deposit = df.deposit.map({'1': False, '2': True}).astype('bool') + df.deposit = df.deposit.map({'1': 'no', '2': 'yes'}).astype('category') + df.deposit = df.deposit.cat.as_ordered() # 'no' < 'yes' # replace 'unknown' marker with NaN df.apply(lambda s: s.cat.remove_categories('unknown', inplace=True) if hasattr(s, 'cat') and 'unknown' in s.cat.categories else s) diff --git a/aif360/sklearn/inprocessing/adversarial_debiasing.py b/aif360/sklearn/inprocessing/adversarial_debiasing.py index ca3de37d..e2328e00 100644 --- a/aif360/sklearn/inprocessing/adversarial_debiasing.py +++ b/aif360/sklearn/inprocessing/adversarial_debiasing.py @@ -67,7 +67,7 @@ def __init__(self, prot_attr=None, scope_name='classifier', adversary. verbose (bool, optional): If ``True``, print losses every 200 steps. random_state (int or numpy.RandomState, optional): Seed of pseudo- - random number generator for shuffling data. + random number generator for shuffling data and seeding weights. """ self.prot_attr = prot_attr diff --git a/aif360/sklearn/metrics/metrics.py b/aif360/sklearn/metrics/metrics.py index 4fda5c67..956621c0 100644 --- a/aif360/sklearn/metrics/metrics.py +++ b/aif360/sklearn/metrics/metrics.py @@ -210,8 +210,8 @@ def generalized_fpr(y_true, probas_pred, pos_label=1, sample_weight=None): r"""Return the ratio of generalized false positives to negative examples in the dataset, :math:`GFPR = \tfrac{GFP}{N}`. - The generalized confusion matrix is calculated by summing the probabilities - of the positive class instead of the hard predictions. + Generalized confusion matrix measures such as this are calculated by summing + the probabilities of the positive class instead of the hard predictions. Args: y_true (array-like): Ground-truth (correct) target values. @@ -237,8 +237,8 @@ def generalized_fnr(y_true, probas_pred, pos_label=1, sample_weight=None): r"""Return the ratio of generalized false negatives to positive examples in the dataset, :math:`GFNR = \tfrac{GFN}{P}`. - The generalized confusion matrix is calculated by summing the probabilities - of the positive class instead of the hard predictions. + Generalized confusion matrix measures such as this are calculated by summing + the probabilities of the positive class instead of the hard predictions. Args: y_true (array-like): Ground-truth (correct) target values. @@ -272,7 +272,8 @@ def statistical_parity_difference(*y, prot_attr=None, priv_group=1, pos_label=1, Note: If only y_true is provided, this will return the difference in base - rates (statistical parity difference of the original dataset). + rates (statistical parity difference of the original dataset). If both + y_true and y_pred are provided, only y_pred is used. Args: y_true (pandas.Series): Ground truth (correct) target values. If y_pred @@ -287,6 +288,9 @@ def statistical_parity_difference(*y, prot_attr=None, priv_group=1, pos_label=1, Returns: float: Statistical parity difference. + + See also: + :func:`selection_rate`, :func:`base_rate` """ rate = base_rate if len(y) == 1 or y[1] is None else selection_rate return difference(rate, *y, prot_attr=prot_attr, priv_group=priv_group, @@ -302,7 +306,8 @@ def disparate_impact_ratio(*y, prot_attr=None, priv_group=1, pos_label=1, Note: If only y_true is provided, this will return the ratio of base rates - (disparate impact of the original dataset). + (disparate impact of the original dataset). If both y_true and y_pred + are provided, only y_pred is used. Args: y_true (pandas.Series): Ground truth (correct) target values. If y_pred @@ -317,6 +322,9 @@ def disparate_impact_ratio(*y, prot_attr=None, priv_group=1, pos_label=1, Returns: float: Disparate impact. + + See also: + :func:`selection_rate`, :func:`base_rate` """ rate = base_rate if len(y) == 1 or y[1] is None else selection_rate return ratio(rate, *y, prot_attr=prot_attr, priv_group=priv_group, @@ -340,6 +348,9 @@ def equal_opportunity_difference(y_true, y_pred, prot_attr=None, priv_group=1, Returns: float: Equal opportunity difference. + + See also: + :func:`~sklearn.metrics.recall_score` """ return difference(recall_score, y_true, y_pred, prot_attr=prot_attr, priv_group=priv_group, pos_label=pos_label, @@ -461,6 +472,9 @@ def generalized_entropy_error(y_true, y_pred, alpha=2, pos_label=1): index, and 2 is half the squared coefficient of variation. pos_label (scalar, optional): The label of the positive class. + See also: + :func:`generalized_entropy_index` + References: .. [#speicher18] `T. Speicher, H. Heidari, N. Grgic-Hlaca, K. P. Gummadi, A. Singla, A. Weller, and M. B. Zafar, "A Unified @@ -495,6 +509,9 @@ def between_group_generalized_entropy_error(y_true, y_pred, prot_attr=None, index, and 2 is half the squared coefficient of variation. pos_label (scalar, optional): The label of the positive class. + See also: + :func:`generalized_entropy_index` + References: .. [#speicher18] `T. Speicher, H. Heidari, N. Grgic-Hlaca, K. P. Gummadi, A. Singla, A. Weller, and M. B. Zafar, "A Unified @@ -518,6 +535,9 @@ def theil_index(b): Args: b (array-like): Parameter over which to calculate the entropy index. + + See also: + :func:`generalized_entropy_index` """ return generalized_entropy_index(b, alpha=1) @@ -527,6 +547,9 @@ def coefficient_of_variation(b): Args: b (array-like): Parameter over which to calculate the entropy index. + + See also: + :func:`generalized_entropy_index` """ return 2 * np.sqrt(generalized_entropy_index(b, alpha=2)) diff --git a/aif360/sklearn/postprocessing/__init__.py b/aif360/sklearn/postprocessing/__init__.py index 9af0db10..c45f4e4b 100644 --- a/aif360/sklearn/postprocessing/__init__.py +++ b/aif360/sklearn/postprocessing/__init__.py @@ -33,14 +33,16 @@ class PostProcessingMeta(BaseEstimator, MetaEstimatorMixin): """ def __init__(self, estimator, postprocessor=CalibratedEqualizedOdds(), - needs_proba=None, val_size=0.25, **options): + needs_proba=None, prefit=False, val_size=0.25, **options): """ Args: estimator (sklearn.BaseEstimator): Original estimator. postprocessor: Post-processing algorithm. - needs_proba (bool): Use ``self.estimator_.predict_proba()`` instead of - ``self.estimator_.predict()`` as input to postprocessor. If + needs_proba (bool): Use ``self.estimator_.predict_proba()`` instead + of ``self.estimator_.predict()`` as input to postprocessor. If ``None``, defaults to ``True`` if the postprocessor supports it. + prefit (bool): If ``True``, it is assumed that estimator has been + fitted already and all data is used to train postprocessor. val_size (int or float): Size of validation set used to fit the postprocessor. The estimator fits on the remainder of the training set. @@ -54,6 +56,7 @@ def __init__(self, estimator, postprocessor=CalibratedEqualizedOdds(), self.estimator = estimator self.postprocessor = postprocessor self.needs_proba = needs_proba + self.prefit = prefit self.val_size = val_size self.options = options @@ -79,14 +82,28 @@ def fit(self, X, y, sample_weight=None, **fit_params): Returns: self """ - self.needs_proba_ = (self.needs_proba if self.needs_proba is not None else - isinstance(self.postprocessor, CalibratedEqualizedOdds)) + self.needs_proba_ = (self.needs_proba if self.needs_proba is not None + else isinstance(self.postprocessor, CalibratedEqualizedOdds)) if self.needs_proba_ and not hasattr(self.estimator, 'predict_proba'): raise TypeError("`estimator` (type: {}) does not implement method " "`predict_proba()`.".format(type(self.estimator))) + if self.prefit: + if len(self.options): + warning("Splitting options were passed but prefit is True so " + "these are ignored.") + self.postprocessor_ = clone(self.postprocessor) + y_score = (self.estimator.predict(X) if not self.needs_proba_ else + self.estimator.predict_proba(X)) + fit_params = fit_params.copy() + fit_params.update(labels=self.estimator_.classes_) + self.postprocessor_.fit(y_score, y, sample_weight=sample_weight, + **fit_params) + return self + if 'train_size' in self.options or 'test_size' in self.options: - warning("'train_size' and 'test_size' are ignored in favor of 'val_size'") + warning("'train_size' and 'test_size' are ignored in favor of " + "'val_size'") options_ = self.options.copy() options_['test_size'] = self.val_size if 'train_size' in options_: @@ -103,10 +120,11 @@ def fit(self, X, y, sample_weight=None, **fit_params): X_est, X_post, y_est, y_post = train_test_split(X, y, **options_) self.estimator_.fit(X_est, y_est) - y_pred = (self.estimator_.predict(X_post) if not self.needs_proba_ else + y_score = (self.estimator_.predict(X_post) if not self.needs_proba_ else self.estimator_.predict_proba(X_post)) - # fit_params = fit_params.copy().update(labels=self.estimator_.classes_) - self.postprocessor_.fit(y_pred, y_post, sample_weight=sw_post + fit_params = fit_params.copy() + fit_params.update(labels=self.estimator_.classes_) + self.postprocessor_.fit(y_score, y_post, sample_weight=sw_post if sample_weight is not None else None, **fit_params) return self @@ -116,8 +134,8 @@ def predict(self, X): """Predict class labels for the given samples. First, runs ``self.estimator_.predict()`` (or ``predict_proba()`` if - ``self.needs_proba_`` is ``True``) then returns the post-processed output - from those predictions. + ``self.needs_proba_`` is ``True``) then returns the post-processed + output from those predictions. Args: X (pandas.DataFrame): Test samples. @@ -125,18 +143,18 @@ def predict(self, X): Returns: numpy.ndarray: Predicted class label per sample. """ - y_pred = (self.estimator_.predict(X) if not self.needs_proba_ else - self.estimator_.predict_proba(X)) - y_pred = pd.DataFrame(y_pred, index=X.index).squeeze('columns') - return self.postprocessor_.predict(y_pred) + y_score = (self.estimator_.predict(X) if not self.needs_proba_ else + self.estimator_.predict_proba(X)) + y_score = pd.DataFrame(y_score, index=X.index).squeeze('columns') + return self.postprocessor_.predict(y_score) @if_delegate_has_method('postprocessor_') def predict_proba(self, X): """Probability estimates. First, runs ``self.estimator_.predict()`` (or ``predict_proba()`` if - ``self.needs_proba_`` is ``True``) then returns the post-processed output - from those predictions. + ``self.needs_proba_`` is ``True``) then returns the post-processed + output from those predictions. The returned estimates for all classes are ordered by the label of classes. @@ -149,18 +167,18 @@ def predict_proba(self, X): in the model, where classes are ordered as they are in ``self.classes_``. """ - y_pred = (self.estimator_.predict(X) if not self.needs_proba_ else - self.estimator_.predict_proba(X)) - y_pred = pd.DataFrame(y_pred, index=X.index).squeeze('columns') - return self.postprocessor_.predict_proba(y_pred) + y_score = (self.estimator_.predict(X) if not self.needs_proba_ else + self.estimator_.predict_proba(X)) + y_score = pd.DataFrame(y_score, index=X.index).squeeze('columns') + return self.postprocessor_.predict_proba(y_score) @if_delegate_has_method('postprocessor_') def predict_log_proba(self, X): """Log of probability estimates. First, runs ``self.estimator_.predict()`` (or ``predict_proba()`` if - ``self.needs_proba_`` is ``True``) then returns the post-processed output - from those predictions. + ``self.needs_proba_`` is ``True``) then returns the post-processed + output from those predictions. The returned estimates for all classes are ordered by the label of classes. @@ -173,10 +191,10 @@ def predict_log_proba(self, X): the model, where classes are ordered as they are in ``self.classes_``. """ - y_pred = (self.estimator_.predict(X) if not self.needs_proba_ else - self.estimator_.predict_proba(X)) - y_pred = pd.DataFrame(y_pred, index=X.index).squeeze('columns') - return self.postprocessor_.predict_log_proba(y_pred) + y_score = (self.estimator_.predict(X) if not self.needs_proba_ else + self.estimator_.predict_proba(X)) + y_score = pd.DataFrame(y_score, index=X.index).squeeze('columns') + return self.postprocessor_.predict_log_proba(y_score) @if_delegate_has_method('postprocessor_') def score(self, X, y, sample_weight=None): @@ -195,10 +213,11 @@ def score(self, X, y, sample_weight=None): Returns: float: Score value. """ - y_pred = (self.estimator_.predict(X) if not self.needs_proba_ else - self.estimator_.predict_proba(X)) - y_pred = pd.DataFrame(y_pred, index=X.index).squeeze('columns') - return self.postprocessor_.score(y_pred, y, sample_weight=sample_weight) + y_score = (self.estimator_.predict(X) if not self.needs_proba_ else + self.estimator_.predict_proba(X)) + y_score = pd.DataFrame(y_score, index=X.index).squeeze('columns') + return self.postprocessor_.score(y_score, y, + sample_weight=sample_weight) __all__ = [ diff --git a/aif360/sklearn/postprocessing/calibrated_equalized_odds.py b/aif360/sklearn/postprocessing/calibrated_equalized_odds.py index 94f8d5ef..0b3bdf01 100644 --- a/aif360/sklearn/postprocessing/calibrated_equalized_odds.py +++ b/aif360/sklearn/postprocessing/calibrated_equalized_odds.py @@ -16,15 +16,18 @@ class CalibratedEqualizedOdds(BaseEstimator, ClassifierMixin): change output labels with an equalized odds objective [#pleiss17]_. Note: - This breaks the sckit-learn API by requiring fit params y_true, y_pred, - and pos_label and predict param y_pred. See :class:`PostProcessingMeta` - for a workaround. + A :class:`~sklearn.pipeline.Pipeline` expects a single estimation step + but this class requires an estimator's predictions as input. See + :class:`PostProcessingMeta` for a workaround. + + See also: + :class:`PostProcessingMeta` References: .. [#pleiss17] `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 @@ -58,7 +61,7 @@ def __init__(self, prot_attr=None, cost_constraint='weighted', generalized false negative rate ('fnr'), or a weighted combination of both ('weighted'). random_state (int or numpy.RandomState, optional): Seed of pseudo- - random number generator for shuffling data. + random number generator for sampling from the mix rates. """ self.prot_attr = prot_attr self.cost_constraint = cost_constraint @@ -80,27 +83,26 @@ def _weighted_cost(self, y_true, probas_pred, pos_label=1, raise ValueError("`cost_constraint` must be one of: 'fpr', 'fnr', " "or 'weighted'") - def fit(self, y_pred, y_true, labels=None, pos_label=1, sample_weight=None): + def fit(self, X, y, labels=None, pos_label=1, sample_weight=None): """Compute the mixing rates required to satisfy the cost constraint. Args: - y_pred (array-like): Probability estimates of the targets as - returned by a ``predict_proba()`` call or equivalent. - y_true (pandas.Series): Ground-truth (correct) target values. + X (array-like): Probability estimates of the targets as returned by + a ``predict_proba()`` call or equivalent. + y (pandas.Series): Ground-truth (correct) target values. labels (list, optional): The ordered set of labels values. Must - match the order of columns in y_pred if provided. By default, - all labels in y_true are used in sorted order. + match the order of columns in X if provided. By default, + all labels in y are used in sorted order. pos_label (scalar, optional): The label of the positive class. sample_weight (array-like, optional): Sample weights. Returns: self """ - y_pred, y_true, sample_weight = check_inputs(y_pred, y_true, - sample_weight) - groups, self.prot_attr_ = check_groups(y_true, self.prot_attr, + X, y, sample_weight = check_inputs(X, y, sample_weight) + groups, self.prot_attr_ = check_groups(y, self.prot_attr, ensure_binary=True) - self.classes_ = labels if labels is not None else np.unique(y_true) + self.classes_ = labels if labels is not None else np.unique(y) self.groups_ = np.unique(groups) self.pos_label_ = pos_label @@ -111,14 +113,13 @@ def fit(self, y_pred, y_true, labels=None, pos_label=1, sample_weight=None): raise ValueError('pos_label={} is not in the set of labels. The ' 'valid values are:\n{}'.format(pos_label, self.classes_)) - y_pred = y_pred[:, np.nonzero(self.classes_ == self.pos_label_)[0][0]] + X = X[:, np.nonzero(self.classes_ == self.pos_label_)[0][0]] # local function to return corresponding args for metric evaluation def _args(grp_idx, triv=False): idx = (groups == self.groups_[grp_idx]) - pred = (np.full_like(y_pred, self.base_rates_[grp_idx]) if triv else - y_pred) - return [y_true[idx], pred[idx], pos_label, sample_weight[idx]] + pred = np.full_like(X, self.base_rates_[grp_idx]) if triv else X + return [y[idx], pred[idx], pos_label, sample_weight[idx]] self.base_rates_ = [base_rate(*_args(i)) for i in range(2)] @@ -131,12 +132,12 @@ def _args(grp_idx, triv=False): return self - def predict_proba(self, y_pred): + def predict_proba(self, X): """The returned estimates for all classes are ordered by the label of classes. Args: - y_pred (pandas.DataFrame): Probability estimates of the targets as + X (pandas.DataFrame): Probability estimates of the targets as returned by a ``predict_proba()`` call or equivalent. Note: must include protected attributes in the index. @@ -148,47 +149,47 @@ def predict_proba(self, y_pred): check_is_fitted(self, 'mix_rates_') rng = check_random_state(self.random_state) - groups, _ = check_groups(y_pred, self.prot_attr_) + groups, _ = check_groups(X, self.prot_attr_) if not set(np.unique(groups)) <= set(self.groups_): - raise ValueError('The protected groups from y_pred:\n{}\ndo not ' + raise ValueError('The protected groups from X:\n{}\ndo not ' 'match those from the training set:\n{}'.format( np.unique(groups), self.groups_)) pos_idx = np.nonzero(self.classes_ == self.pos_label_)[0][0] - y_pred = y_pred.iloc[:, pos_idx] + X = X.iloc[:, pos_idx] - yt = np.empty_like(y_pred) + yt = np.empty_like(X) for grp_idx in range(2): i = (groups == self.groups_[grp_idx]) to_replace = (rng.rand(sum(i)) < self.mix_rates_[grp_idx]) - new_preds = y_pred[i].copy() + new_preds = X[i].copy() new_preds[to_replace] = self.base_rates_[grp_idx] yt[i] = new_preds return np.c_[1 - yt, yt] if pos_idx == 1 else np.c_[yt, 1 - yt] - def predict(self, y_pred): + def predict(self, X): """Predict class labels for the given scores. Args: - y_pred (pandas.DataFrame): Probability estimates of the targets as + X (pandas.DataFrame): Probability estimates of the targets as returned by a ``predict_proba()`` call or equivalent. Note: must include protected attributes in the index. Returns: numpy.ndarray: Predicted class label per sample. """ - scores = self.predict_proba(y_pred) + scores = self.predict_proba(X) return self.classes_[scores.argmax(axis=1)] - def score(self, y_pred, y_true, sample_weight=None): + def score(self, X, y, sample_weight=None): """Score the predictions according to the cost constraint specified. Args: - y_pred (pandas.DataFrame): Probability estimates of the targets as + X (pandas.DataFrame): Probability estimates of the targets as returned by a ``predict_proba()`` call or equivalent. Note: must include protected attributes in the index. - y_true (array-like): Ground-truth (correct) target values. + y (array-like): Ground-truth (correct) target values. sample_weight (array-like, optional): Sample weights. Returns: @@ -198,8 +199,8 @@ def score(self, y_pred, y_true, sample_weight=None): """ check_is_fitted(self, ['classes_', 'pos_label_']) pos_idx = np.nonzero(self.classes_ == self.pos_label_)[0][0] - probas_pred = self.predict_proba(y_pred)[:, pos_idx] + probas_pred = self.predict_proba(X)[:, pos_idx] - return abs(difference(self._weighted_cost, y_true, probas_pred, + return abs(difference(self._weighted_cost, y, probas_pred, prot_attr=self.prot_attr_, priv_group=self.groups_[1], pos_label=self.pos_label_, sample_weight=sample_weight)) diff --git a/aif360/sklearn/preprocessing/reweighing.py b/aif360/sklearn/preprocessing/reweighing.py index d4f782b0..f29653ae 100644 --- a/aif360/sklearn/preprocessing/reweighing.py +++ b/aif360/sklearn/preprocessing/reweighing.py @@ -17,6 +17,9 @@ class Reweighing(BaseEstimator): This breaks the scikit-learn API by returning new sample weights from ``fit_transform()``. See :class:`ReweighingMeta` for a workaround. + See also: + :class:`ReweighingMeta` + References: .. [#kamiran12] `F. Kamiran and T. Calders, "Data Preprocessing Techniques for Classification without Discrimination," Knowledge and diff --git a/aif360/sklearn/utils.py b/aif360/sklearn/utils.py index 13ad3820..604b1202 100644 --- a/aif360/sklearn/utils.py +++ b/aif360/sklearn/utils.py @@ -14,9 +14,20 @@ def check_inputs(X, y, sample_weight=None, ensure_2d=True): Args: X (array-like): Input data. y (array-like, shape = (n_samples,)): Target values. - sample_weight (array-like): Sample weights. + sample_weight (array-like, optional): Sample weights. ensure_2d (bool, optional): Whether to raise a ValueError if X is not 2D. + + Returns: + tuple: + + * **X** (`array-like`) -- Validated X. Unchanged. + + * **y** (`array-like`) -- Validated y. Possibly converted to 1D if + not a :class:`pandas.Series`. + * **sample_weight** (`array-like`) -- Validated sample_weight. If no + sample_weight is provided, returns a consistent-length array of + ones. """ if ensure_2d and X.ndim != 2: raise ValueError("Expected X to be 2D, got ndim == {} instead.".format( @@ -39,8 +50,8 @@ def check_groups(arr, prot_attr, ensure_binary=False): provided protected attributes are in the index. Args: - arr (`pandas.Series` or `pandas.DataFrame`): A Pandas object containing - protected attribute information in the index. + arr (:class:`pandas.Series` or :class:`pandas.DataFrame`): A Pandas + object containing protected attribute information in the index. prot_attr (single label or list-like): Protected attribute(s). If ``None``, all protected attributes in arr are used. ensure_binary (bool): Raise an error if the resultant groups are not @@ -49,11 +60,11 @@ def check_groups(arr, prot_attr, ensure_binary=False): Returns: tuple: - * **groups** (`pandas.Index`) -- Label (or tuple of labels) of - protected attribute for each sample in arr. - * **prot_attr** (list-like) -- Modified input. If input is a single - label, returns single-item list. If input is ``None`` returns list - of all protected attributes. + * **groups** (:class:`pandas.Index`) -- Label (or tuple of labels) + of protected attribute for each sample in arr. + * **prot_attr** (`list-like`) -- Modified input. If input is a + single label, returns single-item list. If input is ``None`` + returns list of all protected attributes. """ if not hasattr(arr, 'index'): raise TypeError( diff --git a/examples/sklearn/demo_new_features.ipynb b/examples/sklearn/demo_new_features.ipynb index 026bf790..34a6c087 100644 --- a/examples/sklearn/demo_new_features.ipynb +++ b/examples/sklearn/demo_new_features.ipynb @@ -18,15 +18,20 @@ "import numpy as np\n", "import pandas as pd\n", "import tensorflow as tf\n", + "tf.logging.set_verbosity(tf.logging.ERROR)\n", + "\n", + "from sklearn.compose import make_column_transformer\n", "from sklearn.linear_model import LogisticRegression\n", "from sklearn.metrics import accuracy_score\n", "from sklearn.model_selection import GridSearchCV, train_test_split\n", + "from sklearn.preprocessing import OneHotEncoder\n", "\n", "from aif360.sklearn.preprocessing import ReweighingMeta\n", "from aif360.sklearn.inprocessing import AdversarialDebiasing\n", "from aif360.sklearn.postprocessing import CalibratedEqualizedOdds, PostProcessingMeta\n", "from aif360.sklearn.datasets import fetch_adult\n", - "from aif360.sklearn.metrics import disparate_impact_ratio, average_odds_error, generalized_fpr, generalized_fnr" + "from aif360.sklearn.metrics import disparate_impact_ratio, average_odds_error, generalized_fpr\n", + "from aif360.sklearn.metrics import generalized_fnr, difference" ] }, { @@ -52,188 +57,8 @@ "outputs": [ { "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
ageworkclasseducationeducation-nummarital-statusoccupationrelationshipracesexcapital-gaincapital-losshours-per-weeknative-country
racesex
0Non-whiteMale25.0Private11th7.0Never-marriedMachine-op-inspctOwn-childNon-whiteMale0.00.040.0United-States
1WhiteMale38.0PrivateHS-grad9.0Married-civ-spouseFarming-fishingHusbandWhiteMale0.00.050.0United-States
2WhiteMale28.0Local-govAssoc-acdm12.0Married-civ-spouseProtective-servHusbandWhiteMale0.00.040.0United-States
3Non-whiteMale44.0PrivateSome-college10.0Married-civ-spouseMachine-op-inspctHusbandNon-whiteMale7688.00.040.0United-States
5WhiteMale34.0Private10th6.0Never-marriedOther-serviceNot-in-familyWhiteMale0.00.030.0United-States
\n", - "
" - ], - "text/plain": [ - " age workclass education education-num \\\n", - " race sex \n", - "0 Non-white Male 25.0 Private 11th 7.0 \n", - "1 White Male 38.0 Private HS-grad 9.0 \n", - "2 White Male 28.0 Local-gov Assoc-acdm 12.0 \n", - "3 Non-white Male 44.0 Private Some-college 10.0 \n", - "5 White Male 34.0 Private 10th 6.0 \n", - "\n", - " marital-status occupation relationship \\\n", - " race sex \n", - "0 Non-white Male Never-married Machine-op-inspct Own-child \n", - "1 White Male Married-civ-spouse Farming-fishing Husband \n", - "2 White Male Married-civ-spouse Protective-serv Husband \n", - "3 Non-white Male Married-civ-spouse Machine-op-inspct Husband \n", - "5 White Male Never-married Other-service Not-in-family \n", - "\n", - " race sex capital-gain capital-loss hours-per-week \\\n", - " race sex \n", - "0 Non-white Male Non-white Male 0.0 0.0 40.0 \n", - "1 White Male White Male 0.0 0.0 50.0 \n", - "2 White Male White Male 0.0 0.0 40.0 \n", - "3 Non-white Male Non-white Male 7688.0 0.0 40.0 \n", - "5 White Male White Male 0.0 0.0 30.0 \n", - "\n", - " native-country \n", - " race sex \n", - "0 Non-white Male United-States \n", - "1 White Male United-States \n", - "2 White Male United-States \n", - "3 Non-white Male United-States \n", - "5 White Male United-States " - ] + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
ageworkclasseducationeducation-nummarital-statusoccupationrelationshipracesexcapital-gaincapital-losshours-per-weeknative-country
racesex
0Non-whiteMale25.0Private11th7.0Never-marriedMachine-op-inspctOwn-childNon-whiteMale0.00.040.0United-States
1WhiteMale38.0PrivateHS-grad9.0Married-civ-spouseFarming-fishingHusbandWhiteMale0.00.050.0United-States
2WhiteMale28.0Local-govAssoc-acdm12.0Married-civ-spouseProtective-servHusbandWhiteMale0.00.040.0United-States
3Non-whiteMale44.0PrivateSome-college10.0Married-civ-spouseMachine-op-inspctHusbandNon-whiteMale7688.00.040.0United-States
5WhiteMale34.0Private10th6.0Never-marriedOther-serviceNot-in-familyWhiteMale0.00.030.0United-States
\n
", + "text/plain": " age workclass education education-num \\\n race sex \n0 Non-white Male 25.0 Private 11th 7.0 \n1 White Male 38.0 Private HS-grad 9.0 \n2 White Male 28.0 Local-gov Assoc-acdm 12.0 \n3 Non-white Male 44.0 Private Some-college 10.0 \n5 White Male 34.0 Private 10th 6.0 \n\n marital-status occupation relationship \\\n race sex \n0 Non-white Male Never-married Machine-op-inspct Own-child \n1 White Male Married-civ-spouse Farming-fishing Husband \n2 White Male Married-civ-spouse Protective-serv Husband \n3 Non-white Male Married-civ-spouse Machine-op-inspct Husband \n5 White Male Never-married Other-service Not-in-family \n\n race sex capital-gain capital-loss hours-per-week \\\n race sex \n0 Non-white Male Non-white Male 0.0 0.0 40.0 \n1 White Male White Male 0.0 0.0 50.0 \n2 White Male White Male 0.0 0.0 40.0 \n3 Non-white Male Non-white Male 7688.0 0.0 40.0 \n5 White Male White Male 0.0 0.0 30.0 \n\n native-country \n race sex \n0 Non-white Male United-States \n1 White Male United-States \n2 White Male United-States \n3 Non-white Male United-States \n5 White Male United-States " }, "execution_count": 2, "metadata": {}, @@ -249,150 +74,81 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We can also easily load a version of the dataset which only contains numeric or binary columns and split it with scikit-learn:" + "We can then map the protected attributes to integers," ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, + "outputs": [], + "source": [ + "X.index = pd.MultiIndex.from_arrays(X.index.codes, names=X.index.names)\n", + "y.index = pd.MultiIndex.from_arrays(y.index.codes, names=y.index.names)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "and the target classes to 0/1," + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "y = pd.Series(y.factorize(sort=True)[0], index=y.index)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "split the dataset," + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "(X_train, X_test,\n", + " y_train, y_test) = train_test_split(X, y, train_size=0.7, random_state=1234567)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "and finally, one-hot encode the categorical features:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, "outputs": [ { "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
ageeducation-numracesexcapital-gaincapital-losshours-per-week
racesex
00125.07.0010.00.040.0
11138.09.0110.00.050.0
21128.012.0110.00.040.0
30144.010.0017688.00.040.0
41018.010.0100.00.030.0
\n", - "
" - ], - "text/plain": [ - " age education-num race sex capital-gain capital-loss \\\n", - " race sex \n", - "0 0 1 25.0 7.0 0 1 0.0 0.0 \n", - "1 1 1 38.0 9.0 1 1 0.0 0.0 \n", - "2 1 1 28.0 12.0 1 1 0.0 0.0 \n", - "3 0 1 44.0 10.0 0 1 7688.0 0.0 \n", - "4 1 0 18.0 10.0 1 0 0.0 0.0 \n", - "\n", - " hours-per-week \n", - " race sex \n", - "0 0 1 40.0 \n", - "1 1 1 50.0 \n", - "2 1 1 40.0 \n", - "3 0 1 40.0 \n", - "4 1 0 30.0 " - ] + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
0123456789...90919293949596979899
racesex
30149110.00.00.00.01.00.00.00.00.00.0...0.00.01.00.00.058.011.00.00.042.0
12028100.00.00.00.01.00.00.00.00.00.0...0.00.00.00.00.051.012.00.00.030.0
36374110.00.01.00.00.00.00.00.00.00.0...0.00.01.00.00.026.014.00.01887.040.0
8055110.00.01.00.00.00.00.00.00.00.0...0.00.00.00.00.044.03.00.00.040.0
38108110.00.01.00.00.00.00.01.00.00.0...0.00.01.00.00.033.06.00.00.040.0
\n

5 rows × 100 columns

\n
", + "text/plain": " 0 1 2 3 4 5 6 7 8 9 ... 90 \\\n race sex ... \n30149 1 1 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 \n12028 1 0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 \n36374 1 1 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 \n8055 1 1 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 \n38108 1 1 0.0 0.0 1.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 ... 0.0 \n\n 91 92 93 94 95 96 97 98 99 \n race sex \n30149 1 1 0.0 1.0 0.0 0.0 58.0 11.0 0.0 0.0 42.0 \n12028 1 0 0.0 0.0 0.0 0.0 51.0 12.0 0.0 0.0 30.0 \n36374 1 1 0.0 1.0 0.0 0.0 26.0 14.0 0.0 1887.0 40.0 \n8055 1 1 0.0 0.0 0.0 0.0 44.0 3.0 0.0 0.0 40.0 \n38108 1 1 0.0 1.0 0.0 0.0 33.0 6.0 0.0 0.0 40.0 \n\n[5 rows x 100 columns]" }, - "execution_count": 3, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "X, y, sample_weight = fetch_adult(numeric_only=True)\n", - "(X_train, X_test,\n", - " y_train, y_test) = train_test_split(X, y, train_size=0.7, shuffle=False)\n", + "ohe = make_column_transformer(\n", + " (OneHotEncoder(sparse=False), X_train.dtypes == 'category'),\n", + " remainder='passthrough')\n", + "X_train = pd.DataFrame(ohe.fit_transform(X_train), index=X_train.index)\n", + "X_test = pd.DataFrame(ohe.transform(X_test), index=X_test.index)\n", + "\n", "X_train.head()" ] }, @@ -400,27 +156,47 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "the protected attribute information is replicated in the labels:" + "Note: the column names are lost in this transformation. The same encoding can be done with Pandas, but this cannot be combined with other preprocessing in a Pipeline." ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
ageeducation-numcapital-gaincapital-losshours-per-weekworkclass_Federal-govworkclass_Local-govworkclass_Privateworkclass_Self-emp-incworkclass_Self-emp-not-inc...native-country_Portugalnative-country_Puerto-Riconative-country_Scotlandnative-country_Southnative-country_Taiwannative-country_Thailandnative-country_Trinadad&Tobagonative-country_United-Statesnative-country_Vietnamnative-country_Yugoslavia
racesex
00125.07.00.00.040.000100...0000000100
11138.09.00.00.050.000100...0000000100
21128.012.00.00.040.001000...0000000100
30144.010.07688.00.040.000100...0000000100
51134.06.00.00.030.000100...0000000100
\n

5 rows × 100 columns

\n
", + "text/plain": " age education-num capital-gain capital-loss hours-per-week \\\n race sex \n0 0 1 25.0 7.0 0.0 0.0 40.0 \n1 1 1 38.0 9.0 0.0 0.0 50.0 \n2 1 1 28.0 12.0 0.0 0.0 40.0 \n3 0 1 44.0 10.0 7688.0 0.0 40.0 \n5 1 1 34.0 6.0 0.0 0.0 30.0 \n\n workclass_Federal-gov workclass_Local-gov workclass_Private \\\n race sex \n0 0 1 0 0 1 \n1 1 1 0 0 1 \n2 1 1 0 1 0 \n3 0 1 0 0 1 \n5 1 1 0 0 1 \n\n workclass_Self-emp-inc workclass_Self-emp-not-inc ... \\\n race sex ... \n0 0 1 0 0 ... \n1 1 1 0 0 ... \n2 1 1 0 0 ... \n3 0 1 0 0 ... \n5 1 1 0 0 ... \n\n native-country_Portugal native-country_Puerto-Rico \\\n race sex \n0 0 1 0 0 \n1 1 1 0 0 \n2 1 1 0 0 \n3 0 1 0 0 \n5 1 1 0 0 \n\n native-country_Scotland native-country_South \\\n race sex \n0 0 1 0 0 \n1 1 1 0 0 \n2 1 1 0 0 \n3 0 1 0 0 \n5 1 1 0 0 \n\n native-country_Taiwan native-country_Thailand \\\n race sex \n0 0 1 0 0 \n1 1 1 0 0 \n2 1 1 0 0 \n3 0 1 0 0 \n5 1 1 0 0 \n\n native-country_Trinadad&Tobago native-country_United-States \\\n race sex \n0 0 1 0 1 \n1 1 1 0 1 \n2 1 1 0 1 \n3 0 1 0 1 \n5 1 1 0 1 \n\n native-country_Vietnam native-country_Yugoslavia \n race sex \n0 0 1 0 0 \n1 1 1 0 0 \n2 1 1 0 0 \n3 0 1 0 0 \n5 1 1 0 0 \n\n[5 rows x 100 columns]" + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# there is one unused category ('Never-worked') that was dropped during dropna\n", + "X.workclass.cat.remove_unused_categories(inplace=True)\n", + "pd.get_dummies(X).head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The protected attribute information is also replicated in the labels:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - " race sex\n", - "0 0 1 0\n", - "1 1 1 0\n", - "2 1 1 1\n", - "3 0 1 1\n", - "4 1 0 0\n", - "Name: annual-income, dtype: int64" - ] + "text/plain": " race sex\n30149 1 1 0\n12028 1 0 1\n36374 1 1 1\n8055 1 1 0\n38108 1 1 0\ndtype: int64" }, - "execution_count": 4, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } @@ -445,22 +221,20 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 9, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "0.823858595509452" - ] + "text/plain": "0.8375469890174688" }, - "execution_count": 5, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "y_pred = LogisticRegression(solver='liblinear').fit(X_train, y_train).predict(X_test)\n", + "y_pred = LogisticRegression(solver='lbfgs').fit(X_train, y_train).predict(X_test)\n", "accuracy_score(y_test, y_pred)" ] }, @@ -473,16 +247,14 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 10, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "0.19826239080897468" - ] + "text/plain": "0.2905425926727236" }, - "execution_count": 6, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" } @@ -499,22 +271,19 @@ "\n", "`average_odds_error()` computes the (unweighted) average of the absolute values of the true positive rate (TPR) difference and false positive rate (FPR) difference, i.e.:\n", "\n", - "$\\tfrac{1}{2}\\left(|FPR_{D = \\text{unprivileged}} - FPR_{D = \\text{privileged}}|\n", - " + |TPR_{D = \\text{unprivileged}} - TPR_{D = \\text{privileged}}|\\right)$" + "$$ \\tfrac{1}{2}\\left(|FPR_{D = \\text{unprivileged}} - FPR_{D = \\text{privileged}}| + |TPR_{D = \\text{unprivileged}} - TPR_{D = \\text{privileged}}|\\right) $$" ] }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 11, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "0.12427040384779571" - ] + "text/plain": "0.09372170954260936" }, - "execution_count": 7, + "execution_count": 11, "metadata": {}, "output_type": "execute_result" } @@ -539,22 +308,17 @@ }, { "cell_type": "code", - "execution_count": 8, - "metadata": { - "scrolled": false - }, + "execution_count": 12, + "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", - "text": [ - "0.8147819559134648\n", - "{'estimator__C': 10, 'reweigher__prot_attr': 'sex'}\n" - ] + "text": "0.8279649148669566\n{'estimator__C': 10, 'reweigher__prot_attr': 'sex'}\n" } ], "source": [ - "rew = ReweighingMeta(estimator=LogisticRegression(solver='liblinear'))\n", + "rew = ReweighingMeta(estimator=LogisticRegression(solver='lbfgs'))\n", "\n", "params = {'estimator__C': [1, 10], 'reweigher__prot_attr': ['sex']}\n", "\n", @@ -566,16 +330,14 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 13, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "0.639237550613212" - ] + "text/plain": "0.5676803237673037" }, - "execution_count": 9, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" } @@ -593,47 +355,34 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 14, "metadata": {}, "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "WARNING:tensorflow:From /anaconda/envs/aif360/lib/python3.5/site-packages/tensorflow/python/framework/op_def_library.py:263: colocate_with (from tensorflow.python.framework.ops) is deprecated and will be removed in a future version.\n", - "Instructions for updating:\n", - "Colocations handled automatically by placer.\n" - ] - }, { "data": { - "text/plain": [ - "0.8218794786050638" - ] + "text/plain": "0.8399056534237488" }, - "execution_count": 10, + "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "adv_deb = AdversarialDebiasing(prot_attr='sex')\n", + "adv_deb = AdversarialDebiasing(prot_attr='sex', random_state=1234567)\n", "adv_deb.fit(X_train, y_train)\n", "adv_deb.score(X_test, y_test)" ] }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 15, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "0.022611763594614448" - ] + "text/plain": "0.060623189820735834" }, - "execution_count": 11, + "execution_count": 15, "metadata": {}, "output_type": "execute_result" } @@ -651,7 +400,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 16, "metadata": {}, "outputs": [], "source": [ @@ -669,24 +418,22 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 17, "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "0.7676926226711254" - ] + "text/plain": "0.8163190093609494" }, - "execution_count": 13, + "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "cal_eq_odds = CalibratedEqualizedOdds('sex', cost_constraint='fnr')\n", - "log_reg = LogisticRegression(solver='liblinear')\n", - "postproc = PostProcessingMeta(estimator=log_reg, postprocessor=cal_eq_odds)\n", + "cal_eq_odds = CalibratedEqualizedOdds('sex', cost_constraint='fnr', random_state=1234567)\n", + "log_reg = LogisticRegression(solver='lbfgs')\n", + "postproc = PostProcessingMeta(estimator=log_reg, postprocessor=cal_eq_odds, random_state=1234567)\n", "\n", "postproc.fit(X_train, y_train)\n", "accuracy_score(y_test, postproc.predict(X_test))" @@ -694,15 +441,14 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 18, "metadata": {}, "outputs": [ { "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] + "image/png": "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\n", + "image/svg+xml": "\n\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n", + "text/plain": "
" }, "metadata": { "needs_background": "light" @@ -714,7 +460,7 @@ "y_pred = postproc.predict_proba(X_test)[:, 1]\n", "y_lr = postproc.estimator_.predict_proba(X_test)[:, 1]\n", "br = postproc.postprocessor_.base_rates_\n", - "i = X_test.sex == 1\n", + "i = X_test.index.get_level_values('sex') == 1\n", "\n", "plt.plot([0, br[0]], [0, 1-br[0]], '-b', label='All calibrated classifiers (Females)')\n", "plt.plot([0, br[1]], [0, 1-br[1]], '-r', label='All calibrated classifiers (Males)')\n", @@ -736,8 +482,8 @@ "plt.plot([0, 1], [generalized_fnr(y_test, y_pred)]*2, '--', c='0.5')\n", "\n", "plt.axis('square')\n", - "plt.xlim([0, 0.4])\n", - "plt.ylim([0.4, 0.8])\n", + "plt.xlim([0.0, 0.4])\n", + "plt.ylim([0.3, 0.7])\n", "plt.xlabel('generalized fpr');\n", "plt.ylabel('generalized fnr');\n", "plt.legend(bbox_to_anchor=(1.04,1), loc='upper left');" @@ -747,15 +493,28 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We can see the generalized false negative rate is approximately equalized and the classifiers remain close to the calibration lines." + "We can see the generalized false negative rate is approximately equalized and the classifiers remain close to the calibration lines.\n", + "\n", + "We can quanitify the discrepancy between protected groups using the `difference` operator:" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 19, "metadata": {}, - "outputs": [], - "source": [] + "outputs": [ + { + "data": { + "text/plain": "0.0027891187222710556" + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "difference(generalized_fnr, y_test, y_pred, prot_attr='sex')" + ] } ], "metadata": { @@ -774,9 +533,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.5.6" + "version": "3.6.9-final" } }, "nbformat": 4, "nbformat_minor": 2 -} +} \ No newline at end of file