diff --git a/README.md b/README.md index 8ebcad16..7662c3e3 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ Get in touch with us on [Slack](https://aif360.slack.com) (invitation * Comprehensive set of sample distortion metrics * Generalized Entropy Index ([Speicher et al., 2018](https://doi.org/10.1145/3219819.3220046)) * Differential Fairness and Bias Amplification ([Foulds et al., 2018](https://arxiv.org/pdf/1807.08362)) -* Bias Scan with Multi-Dimensional Subset Scan ([Zhang et al., 2017](https://arxiv.org/abs/1611.08292)) +* Bias Scan with Multi-Dimensional Subset Scan ([Zhang, Neill, 2017](https://arxiv.org/abs/1611.08292)) ## Setup diff --git a/aif360/metrics/mdss/MDSS.py b/aif360/metrics/mdss/MDSS.py index a90365bd..1c4dd64a 100644 --- a/aif360/metrics/mdss/MDSS.py +++ b/aif360/metrics/mdss/MDSS.py @@ -196,12 +196,12 @@ def score_current_subset(self, coordinates: pd.DataFrame, probs: pd.Series, outc penalized_score = scoring_function.score(observed_sum, probs, total_penalty, current_q_mle) return penalized_score - def scan(self, coordinates: pd.DataFrame, outcomes: pd.Series, probs: pd.Series, penalty: float, + def scan(self, coordinates: pd.DataFrame, probs: pd.Series, outcomes: pd.Series, penalty: float, num_iters: int, verbose: bool = False, seed: int = 0): """ :param coordinates: data frame containing having as columns the covariates/features - :param outcomes: data series containing the outcomes/observed outcomes :param probs: data series containing the probabilities/expected outcomes + :param outcomes: data series containing the outcomes/observed outcomes :param penalty: penalty coefficient :param num_iters: number of iteration :param verbose: logging flag diff --git a/aif360/metrics/mdss_classification_metric.py b/aif360/metrics/mdss_classification_metric.py index 5e49f6b0..e44701cb 100644 --- a/aif360/metrics/mdss_classification_metric.py +++ b/aif360/metrics/mdss_classification_metric.py @@ -2,7 +2,7 @@ from aif360.datasets import BinaryLabelDataset from aif360.metrics import ClassificationMetric -from aif360.metrics.mdss.ScoringFunctions import Bernoulli +from aif360.metrics.mdss.ScoringFunctions import Bernoulli, ScoringFunction from aif360.metrics.mdss.MDSS import MDSS import pandas as pd @@ -15,15 +15,15 @@ class MDSSClassificationMetric(ClassificationMetric): .. [1] Zhang, Z., & Neill, D. B. (2016). Identifying significant predictive bias in classifiers. arXiv preprint arXiv:1611.08292. """ def __init__(self, dataset: BinaryLabelDataset, classified_dataset: BinaryLabelDataset, - scoring_function: Bernoulli, unprivileged_groups: dict = None, privileged_groups:dict = None): + scoring_function: ScoringFunction = Bernoulli(direction='positive'), unprivileged_groups: dict = None, privileged_groups:dict = None): super(MDSSClassificationMetric, self).__init__(dataset, classified_dataset, unprivileged_groups=unprivileged_groups, privileged_groups=privileged_groups) - + self.scanner = MDSS(scoring_function) - def score_groups(self, privileged=True, penalty = 0.0): + def score_groups(self, privileged=True, penalty = 1e-17): """ compute the bias score for a prespecified group of records. @@ -36,14 +36,17 @@ def score_groups(self, privileged=True, penalty = 0.0): :returns: the score for the group """ groups = self.privileged_groups if privileged else self.unprivileged_groups - subset = defaultdict(list) + subset = dict() xor_op = privileged ^ bool(self.classified_dataset.favorable_label) - direction = 'negative' if xor_op else 'positive' + direction = 'positive' if xor_op else 'negative' for g in groups: for k, v in g.items(): - subset[k].append(v) + if k in subset.keys(): + subset[k].append(v) + else: + subset[k] = [v] coordinates = pd.DataFrame(self.dataset.features, columns=self.dataset.feature_names) expected = pd.Series(self.classified_dataset.scores.flatten()) @@ -52,7 +55,7 @@ def score_groups(self, privileged=True, penalty = 0.0): self.scanner.scoring_function.kwargs['direction'] = direction return self.scanner.score_current_subset(coordinates, expected, outcomes, dict(subset), penalty) - def bias_scan(self, privileged=True, num_iters = 10, penalty = 0.0): + def bias_scan(self, privileged=True, num_iters = 10, penalty = 1e-17): """ scan to find the highest scoring subset of records @@ -67,12 +70,12 @@ def bias_scan(self, privileged=True, num_iters = 10, penalty = 0.0): """ xor_op = privileged ^ bool(self.classified_dataset.favorable_label) - direction = 'negative' if xor_op else 'positive' + direction = 'positive' if xor_op else 'negative' self.scanner.scoring_function.kwargs['direction'] = direction coordinates = pd.DataFrame(self.classified_dataset.features, columns=self.classified_dataset.feature_names) expected = pd.Series(self.classified_dataset.scores.flatten()) outcomes = pd.Series(self.dataset.labels.flatten()) - - return self.scanner.scan(coordinates, outcomes, expected, penalty, num_iters) \ No newline at end of file + + return self.scanner.scan(coordinates, expected, outcomes, penalty, num_iters) \ No newline at end of file diff --git a/aif360/sklearn/metrics/metrics.py b/aif360/sklearn/metrics/metrics.py index a8bad91c..4b0c3ab3 100644 --- a/aif360/sklearn/metrics/metrics.py +++ b/aif360/sklearn/metrics/metrics.py @@ -431,7 +431,7 @@ def mdss_bias_score(y_true, y_pred, pos_label=1, privileged=True, num_iters = 10 :param num_iters (scalar, optional): number of iterations """ xor_op = privileged ^ bool(pos_label) - direction = 'negative' if xor_op else 'positive' + direction = 'positive' if xor_op else 'negative' dummy_subset = dict({'index': range(len(y_true))}) expected = pd.Series(y_pred) @@ -458,7 +458,7 @@ def mdss_bias_scan(y_true, y_pred, dataset=None, pos_label=1, privileged=True, n """ xor_op = privileged ^ bool(pos_label) - direction = 'negative' if xor_op else 'positive' + direction = 'positive' if xor_op else 'negative' expected = pd.Series(y_pred) outcomes = pd.Series(y_true) @@ -473,7 +473,7 @@ def mdss_bias_scan(y_true, y_pred, dataset=None, pos_label=1, privileged=True, n scoring_function = Bernoulli(direction=direction) scanner = MDSS(scoring_function) - return scanner.scan(coordinates, outcomes, expected, penalty, num_iters) + return scanner.scan(coordinates, expected, outcomes, penalty, num_iters) # ========================== INDIVIDUAL FAIRNESS =============================== diff --git a/examples/demo_mdss_classifier_metric.ipynb b/examples/demo_mdss_classifier_metric.ipynb index 12da2f35..c338bfc5 100644 --- a/examples/demo_mdss_classifier_metric.ipynb +++ b/examples/demo_mdss_classifier_metric.ipynb @@ -92,8 +92,8 @@ "\n", "dataset_orig = load_preproc_data_compas()\n", "\n", - "privileged_groups = [{'sex': 1}]\n", - "unprivileged_groups = [{'sex': 0}]" + "female_group = [{'sex': 1}]\n", + "male_group = [{'sex': 0}]" ] }, { @@ -381,6 +381,13 @@ "print(dataset_orig_train.feature_names)\n" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, { "cell_type": "code", "execution_count": 11, @@ -397,16 +404,30 @@ ], "source": [ "metric_train = BinaryLabelDatasetMetric(dataset_orig_train, \n", - " unprivileged_groups=unprivileged_groups,\n", - " privileged_groups=privileged_groups)\n", + " unprivileged_groups=male_group,\n", + " privileged_groups=female_group)\n", "\n", "print(\"Train set: Difference in mean outcomes between unprivileged and privileged groups = %f\" % metric_train.mean_difference())\n", "metric_test = BinaryLabelDatasetMetric(dataset_orig_test, \n", - " unprivileged_groups=unprivileged_groups,\n", - " privileged_groups=privileged_groups)\n", + " unprivileged_groups=male_group,\n", + " privileged_groups=female_group)\n", "print(\"Test set: Difference in mean outcomes between unprivileged and privileged groups = %f\" % metric_test.mean_difference())\n" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It shows that overall Females in the dataset have a lower observed recidivism them Males." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If we train a classifier, the model is likely to pick up this bias in the dataset" + ] + }, { "cell_type": "code", "execution_count": 12, @@ -473,19 +494,39 @@ "source": [ "### bias scoring\n", "\n", - "We'll create an instance of the MDSS Classification Metric and specifiy the apriori defined priviledged and unpriviledged groups." + "We'll create an instance of the MDSS Classification Metric and assess the apriori defined privileged and unprivileged groups; females and males respectively." ] }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 16, "metadata": {}, "outputs": [], "source": [ - "scoring_function = Bernoulli(direction='positive')\n", - "mdss_classified = MDSSClassificationMetric(dataset_orig_test, dataset_bias_test, scoring_function,\n", - " unprivileged_groups=unprivileged_groups,\n", - " privileged_groups=privileged_groups)" + "mdss_classified = MDSSClassificationMetric(dataset_orig_test, dataset_bias_test,\n", + " unprivileged_groups=male_group,\n", + " privileged_groups=female_group)" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "-1e-17" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "female_privileged_score = mdss_classified.score_groups(privileged=True)\n", + "female_privileged_score" ] }, { @@ -496,7 +537,7 @@ { "data": { "text/plain": [ - "1.17706135776708" + "-1e-17" ] }, "execution_count": 18, @@ -505,14 +546,32 @@ } ], "source": [ - "privileged_score = mdss_classified.score_groups(privileged=True)\n", - "privileged_score" + "male_unprivileged_score = mdss_classified.score_groups(privileged=False)\n", + "male_unprivileged_score" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It appears there is no multiplicative increase in the odds for females thus no bias towards females and the bias score is negligible. Similarly there is no multiplicative decrease in the odds for males. We can alternate our assumptions of priviledge and unprivileged groups to see if there is some bias." ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, + "outputs": [], + "source": [ + "mdss_classified = MDSSClassificationMetric(dataset_orig_test, dataset_bias_test,\n", + " unprivileged_groups=female_group,\n", + " privileged_groups=male_group)" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, "outputs": [ { "data": { @@ -520,24 +579,42 @@ "0.630108034329993" ] }, - "execution_count": 19, + "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "unprivileged_score = mdss_classified.score_groups(privileged=False)\n", - "unprivileged_score" + "male_privileged_score = mdss_classified.score_groups(privileged=True)\n", + "male_privileged_score" ] }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 21, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "1.17706135776708" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "assert privileged_score > 0\n", - "assert unprivileged_score > 0" + "female_unprivileged_score = mdss_classified.score_groups(privileged=False)\n", + "female_unprivileged_score" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It appears there is some multiplicative increase in the odds of recidivism for male and a multiplicative decrease in the odds for females." ] }, { @@ -551,7 +628,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 22, "metadata": {}, "outputs": [], "source": [ @@ -561,15 +638,15 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 23, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "({'sex': [1.0], 'race': [0.0]}, 3.303741512514005)\n", - "({'race': [0.0], 'age_cat': [0.0], 'sex': [0.0]}, 3.153105510860506)\n" + "({'race': [0.0], 'age_cat': [0.0], 'sex': [0.0]}, 3.153105510860506)\n", + "({'sex': [1.0], 'race': [0.0]}, 3.303741512514005)\n" ] } ], @@ -580,7 +657,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 25, "metadata": {}, "outputs": [], "source": [ @@ -592,9 +669,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We can observe that the bias score is higher than the score of the prior groups. These subgroups are guaranteed to be the highest scoring subgroup among the exponentially many subgroups according the LTSS property. \n", + "We can observe that the bias score is higher than the score of the prior groups. These subgroups are guaranteed to be the highest scoring subgroup among the exponentially many subgroups.\n", "\n", - "For the purposes of this example, the logistic regression model systematically under estimates the recidivism risk of individuals belonging to the `Female`, and `Caucasian` whereas individuals belonging to the `Not Caucasian`, `Male`, and `age_cat=Less than 25` are assigned a higher risk that is actually observed. We refer to these subgroups as the `detected privileged group` and `detected unprivileged group` respectively." + "For the purposes of this example, the logistic regression model systematically under estimates the recidivism risk of individuals in the `Non-caucasian`, `less than 25`, `Male` subgroup whereas individuals belonging to the `Causasian`, `Female` are assigned a higher risk than is actually observed. We refer to these subgroups as the `detected privileged group` and `detected unprivileged group` respectively." ] }, { @@ -606,7 +683,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 26, "metadata": {}, "outputs": [], "source": [ @@ -622,7 +699,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 27, "metadata": {}, "outputs": [ { @@ -737,7 +814,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 28, "metadata": {}, "outputs": [], "source": [ @@ -759,14 +836,14 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 29, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Test set: Difference in mean outcomes between unprivileged and privileged groups = -0.275836\n" + "Test set: Difference in mean outcomes between unprivileged and privileged groups = 0.275836\n" ] } ], @@ -779,6 +856,13 @@ " % metric_bias_test.mean_difference())" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It appears the detected privileged group have a higher risk of recidivism than the unpriviledged group." + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -789,7 +873,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 30, "metadata": {}, "outputs": [], "source": [ @@ -799,37 +883,37 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 31, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "'Our detected priviledged group has a size of 169, we observe 0.33136094674556216 as the mean outcome, but our model predicts 0.43652313575727764'" + "'Our detected priviledged group has a size of 192, we observe 0.6770833333333334 as the average risk of recidivism, but our model predicts 0.5730004938240804'" ] }, - "execution_count": 30, + "execution_count": 31, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "\"Our detected priviledged group has a size of {}, we observe {} as the mean outcome, but our model predicts {}\"\\\n", + "\"Our detected priviledged group has a size of {}, we observe {} as the average risk of recidivism, but our model predicts {}\"\\\n", ".format(len(temp_df), temp_df['observed'].mean(), temp_df['probabilities'].mean())" ] }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 32, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "'This is a multiplicative decrease in the odds by 0.6397030278261826'" + "'This is a multiplicative increase in the odds by 1.56251443909305'" ] }, - "execution_count": 31, + "execution_count": 32, "metadata": {}, "output_type": "execute_result" } @@ -839,22 +923,22 @@ "group_prob = temp_df['probabilities'].mean()\n", "\n", "odds_mul = (group_obs / (1 - group_obs)) / (group_prob /(1 - group_prob))\n", - "\"This is a multiplicative decrease in the odds by {}\"\\\n", + "\"This is a multiplicative increase in the odds by {}\"\\\n", ".format(odds_mul)" ] }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 33, "metadata": {}, "outputs": [], "source": [ - "assert odds_mul < 1" + "assert odds_mul > 1" ] }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 34, "metadata": {}, "outputs": [], "source": [ @@ -864,37 +948,37 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 35, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "'Our detected unpriviledged group has a size of 192, we observe 0.6770833333333334 as the mean outcome, but our model predicts 0.5730004938240804'" + "'Our detected unpriviledged group has a size of 169, we observe 0.33136094674556216 as the average risk of recidivism, but our model predicts 0.43652313575727764'" ] }, - "execution_count": 34, + "execution_count": 35, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "\"Our detected unpriviledged group has a size of {}, we observe {} as the mean outcome, but our model predicts {}\"\\\n", + "\"Our detected unpriviledged group has a size of {}, we observe {} as the average risk of recidivism, but our model predicts {}\"\\\n", ".format(len(temp_df), temp_df['observed'].mean(), temp_df['probabilities'].mean())" ] }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 36, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "'This is a multiplicative increase in the odds by 1.56251443909305'" + "'This is a multiplicative decrease in the odds by 0.6397030278261826'" ] }, - "execution_count": 35, + "execution_count": 36, "metadata": {}, "output_type": "execute_result" } @@ -904,17 +988,17 @@ "group_prob = temp_df['probabilities'].mean()\n", "\n", "odds_mul = (group_obs / (1 - group_obs)) / (group_prob /(1 - group_prob))\n", - "\"This is a multiplicative increase in the odds by {}\"\\\n", + "\"This is a multiplicative decrease in the odds by {}\"\\\n", ".format(odds_mul)" ] }, { "cell_type": "code", - "execution_count": 36, + "execution_count": 37, "metadata": {}, "outputs": [], "source": [ - "assert odds_mul > 1" + "assert odds_mul < 1" ] }, { diff --git a/examples/sklearn/demo_mdss_classifier_metric_sklearn.ipynb b/examples/sklearn/demo_mdss_classifier_metric_sklearn.ipynb index 806f1133..2e3e7595 100644 --- a/examples/sklearn/demo_mdss_classifier_metric_sklearn.ipynb +++ b/examples/sklearn/demo_mdss_classifier_metric_sklearn.ipynb @@ -101,7 +101,7 @@ "metadata": {}, "source": [ "### training\n", - "We'll split the dataset and then train a simple classifier to predict the probability of the outcome" + "We'll split the dataset and then train a simple classifier to predict the probability of the outcome; (0: Survived, 1: Recidivated)" ] }, { @@ -265,6 +265,13 @@ "dff.head()" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this example, we assume that the model makes systematic under or over estimatations of the recidivism risk for certain subgroups and our aim is to identify these subgroups" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -276,66 +283,63 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 9, "metadata": {}, "outputs": [], "source": [ - "privileged_group = dff[dff['sex'] == 1]\n", - "unprivileged_group = dff[dff['sex'] == 0]" + "females = dff[dff['sex'] == 1]\n", + "males = dff[dff['sex'] == 0]" ] }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 10, "metadata": {}, "outputs": [ { - "data": { - "text/plain": [ - "2.363262497629335" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" + "name": "stdout", + "output_type": "stream", + "text": [ + "-1.512e-14\n", + "2.363262497629335\n" + ] } ], "source": [ - "privileged_score = mdss_bias_score(privileged_group['observed'], privileged_group['probabilities'], \\\n", - " pos_label=0, privileged=True)\n", - "privileged_score" + "# get the bias score of females assuming they are privileged\n", + "print(mdss_bias_score(females['observed'], females['probabilities'], pos_label=0, privileged=True))\n", + "\n", + "# get the bias score of females assuming they are unprivileged\n", + "print(mdss_bias_score(females['observed'], females['probabilities'], pos_label=0, privileged=False))" ] }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 11, "metadata": {}, "outputs": [ { - "data": { - "text/plain": [ - "0.003755523381276868" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" + "name": "stdout", + "output_type": "stream", + "text": [ + "0.003755523381276868\n", + "-3.4000000000000004e-15\n" + ] } ], "source": [ - "unprivileged_score = mdss_bias_score(unprivileged_group['observed'], unprivileged_group['probabilities'], \\\n", - " pos_label=0, privileged=False)\n", - "unprivileged_score" + "# get the bias score of males assuming they are privileged\n", + "print(mdss_bias_score(males['observed'], males['probabilities'], pos_label=0, privileged=True))\n", + "\n", + "# get the bias score of males assuming they are unprivileged\n", + "print(mdss_bias_score(males['observed'], males['probabilities'], pos_label=0, privileged=False))" ] }, { - "cell_type": "code", - "execution_count": 11, + "cell_type": "markdown", "metadata": {}, - "outputs": [], "source": [ - "assert privileged_score > 0\n", - "assert unprivileged_score > 0" + "If we assume correctly, then our bias score is going to be higher; thus whichever of the assumptions results in a higher bias score has the most evidence of being true. This means females are likley unprivileged whereas males are likely priviledged by our classifier. Note that the default penalty term added is what results in a negative bias score." ] }, { @@ -349,7 +353,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 29, "metadata": {}, "outputs": [], "source": [ @@ -361,15 +365,15 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 30, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "({'age_cat': [1.0]}, 30.149019994560646)\n", - "({'sex': [1.0], 'age_cat': [2.0]}, 4.710934850314047)\n" + "({'sex': [1.0], 'age_cat': [2.0]}, 4.710934850314047)\n", + "({'age_cat': [1.0]}, 30.149019994560646)\n" ] } ], @@ -380,7 +384,32 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 31, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[array(['Female', 'Male'], dtype=object),\n", + " array(['African-American', 'Asian', 'Caucasian', 'Hispanic',\n", + " 'Native American', 'Other'], dtype=object),\n", + " array(['25 - 45', 'Greater than 45', 'Less than 25'], dtype=object),\n", + " array(['0', '1 to 3', 'More than 3'], dtype=object),\n", + " array(['F', 'M'], dtype=object)]" + ] + }, + "execution_count": 31, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "enc.categories_" + ] + }, + { + "cell_type": "code", + "execution_count": 32, "metadata": {}, "outputs": [], "source": [ @@ -392,9 +421,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We can observe that the bias score is higher than the score of the prior groups. These subgroups are guaranteed to be the highest scoring subgroup among the exponentially many subgroups according the LTSS property. \n", + "We can observe that the bias score is higher than the score of the prior groups. These subgroups are guaranteed to be the highest scoring subgroup among the exponentially many subgroups.\n", "\n", - "For the purposes of this example, the logistic regression model systematically under estimates the recidivism risk of individuals belonging to the `Female` group whereas individuals belonging to the `Male`, and `age_cat=Less than 25` are assigned a higher risk that is actually observed. We refer to these subgroups as the `detected privileged group` and `detected unprivileged group` respectively." + "For the purposes of this example, the logistic regression model systematically under estimates the recidivism risk of individuals belonging to the `Female` and `Less than 25` group. Whereas individuals belonging to the `Greater than 45` age group are assigned a higher risk than is actually observed. We refer to these subgroups as the `detected privileged group` and `detected unprivileged group` respectively." ] }, { @@ -407,7 +436,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 33, "metadata": {}, "outputs": [], "source": [ @@ -417,7 +446,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 34, "metadata": { "scrolled": true }, @@ -425,31 +454,31 @@ { "data": { "text/plain": [ - "'Our detected priviledged group has a size of 403, we observe 0.2878411910669975 as the mean outcome, but our model predicts 0.4661469627631023'" + "'Our detected priviledged group has a size of 340, our model predicts 0.5271796434466836 probability of recidivism but we observe 0.6147058823529412 as the mean outcome'" ] }, - "execution_count": 16, + "execution_count": 34, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "\"Our detected priviledged group has a size of {}, we observe {} as the mean outcome, but our model predicts {}\"\\\n", - ".format(len(temp_df), temp_df['observed'].mean(), temp_df['probabilities'].mean())" + "\"Our detected priviledged group has a size of {}, our model predicts {} probability of recidivism but we observe {} as the mean outcome\"\\\n", + ".format(len(temp_df), temp_df['probabilities'].mean(), temp_df['observed'].mean())" ] }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 35, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "'This is a multiplicative decrease in the odds by 0.4628869654122457'" + "'This is a multiplicative increase in the odds by 1.430910678064278'" ] }, - "execution_count": 17, + "execution_count": 35, "metadata": {}, "output_type": "execute_result" } @@ -459,22 +488,22 @@ "group_prob = temp_df['probabilities'].mean()\n", "\n", "odds_mul = (group_obs / (1 - group_obs)) / (group_prob /(1 - group_prob))\n", - "\"This is a multiplicative decrease in the odds by {}\"\\\n", + "\"This is a multiplicative increase in the odds by {}\"\\\n", ".format(odds_mul)" ] }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 36, "metadata": {}, "outputs": [], "source": [ - "assert odds_mul < 1" + "assert odds_mul > 1" ] }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 37, "metadata": {}, "outputs": [], "source": [ @@ -484,37 +513,37 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 38, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "'Our detected unpriviledged group has a size of 340, we observe 0.6147058823529412 as the mean outcome, but our model predicts 0.5271796434466836'" + "'Our detected unpriviledged group has a size of 403, our model predicts 0.4661469627631023 probability of recidivism but we observe 0.2878411910669975 as the mean outcome'" ] }, - "execution_count": 20, + "execution_count": 38, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "\"Our detected unpriviledged group has a size of {}, we observe {} as the mean outcome, but our model predicts {}\"\\\n", - ".format(len(temp_df), temp_df['observed'].mean(), temp_df['probabilities'].mean())" + "\"Our detected unpriviledged group has a size of {}, our model predicts {} probability of recidivism but we observe {} as the mean outcome\"\\\n", + ".format(len(temp_df), temp_df['probabilities'].mean(), temp_df['observed'].mean())" ] }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 39, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "'This is a multiplicative increase in the odds by 1.430910678064278'" + "'This is a multiplicative decrease in the odds by 0.4628869654122457'" ] }, - "execution_count": 21, + "execution_count": 39, "metadata": {}, "output_type": "execute_result" } @@ -524,17 +553,17 @@ "group_prob = temp_df['probabilities'].mean()\n", "\n", "odds_mul = (group_obs / (1 - group_obs)) / (group_prob /(1 - group_prob))\n", - "\"This is a multiplicative increase in the odds by {}\"\\\n", + "\"This is a multiplicative decrease in the odds by {}\"\\\n", ".format(odds_mul)" ] }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 40, "metadata": {}, "outputs": [], "source": [ - "assert odds_mul > 1" + "assert odds_mul < 1" ] }, {