Measuring Bias in classification#

This notebook is a tutorial on auditing bias within a binary classification task. We will use the holisticai library both in the data exploration and measure bias sections, introducing some of the functions we have created to help study algorithmic bias.

The sections are organised as follows :

  1. Load the data : we load the law school dataset as a pandas DataFrame

  2. Data Exploration : some preliminary analysis of the data

  3. Train a Model : we train a simple logistic regression model (sklearn)

  4. Measure Bias : we compute a few bias metrics, and comment on their meaning

Load the data#

First of all, we need to import the required packages to perform our bias analysis and mitigation. You will need to have the holisticai package installed on your system, remember that you can install it by running:

!pip install holisticai[all]
[1]:
# Imports
import warnings
warnings.filterwarnings("ignore")

We host a few example datasets on the holisticai library for quick loading and experimentation. Here we load and use the Law School dataset. The goal of this dataset is the prediction of the binary attribute ‘bar’ (whether a student passes the law school bar). The protected attributes are race and gender. We pay special attention to race in this case, because preliminary exploration hints there is strong inequality on that sensitive attribute.

[2]:
from holisticai.datasets import load_dataset

dataset = load_dataset('law_school', protected_attribute='race')
dataset
[2]:
[Dataset]
Instances: 20800
Features: X , y , p_attrs , group_a , group_b
Metadata: race: {'group_a': 'white', 'group_b': 'non-white'}

Data Exploration#

We import some of the holisticai plotters for quick exploration of the data.

[3]:
from holisticai.bias.plots import group_pie_plot, frequency_plot
import matplotlib.pyplot as plt

group_a = dataset['group_a']
group_a = dataset['group_b']

fig,axs = plt.subplots(1, 2, figsize=(15,5))
group_pie_plot(group_a, ax=axs[0], title='Group Pie Plot')
frequency_plot(group_a, dataset['y'], ax=axs[1], title='Frequency Plot')
[3]:
<Axes: title={'center': 'Frequency Plot'}, xlabel='Group', ylabel='Frequency'>
../../../../_images/gallery_tutorials_bias_measuring_bias_measuring_bias_classification_9_1.png

The proportion of white people in law school is very high, allready we observe there is a big representation issue.

We also observe that the white group has a much higher pass rate (within the dataset) than the non-white group.

Train a model#

Here we train a Logistic Regression classifier.

[4]:
dataset_split = dataset.train_test_split(test_size=0.3)
dataset_split
[4]:
[DatasetDict]
train [Dataset]
Instances: 14560
Features: X , y , p_attrs , group_a , group_b
Metadata: race: {'group_a': 'white', 'group_b': 'non-white'}
test [Dataset]
Instances: 6240
Features: X , y , p_attrs , group_a , group_b
Metadata: race: {'group_a': 'white', 'group_b': 'non-white'}
[5]:
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler

# train a model, do not forget to standard scale data
train = dataset_split['train']
test = dataset_split['test']

scaler = StandardScaler()
X_train_t = scaler.fit_transform(train['X'])
model = LogisticRegression(random_state=42, max_iter=500)
model.fit(X_train_t, train['y'])
X_test_t = scaler.transform(test['X'])
y_pred = model.predict(X_test_t)

Measure bias#

The holisticai.bias.metrics module contains a range of metrics useful in evaluating the fairness of algorithmic decisions. In this case we use only a few of the metrics relevant to a classification task.

[6]:
# import some bias metrics
from holisticai.bias.metrics import statistical_parity
from holisticai.bias.metrics import disparate_impact
from holisticai.bias.metrics import accuracy_diff
[7]:
# set up groups, prediction array and true (aka target/label) array.
y_pred  = model.predict(X_test_t)          # prediction vector
y_true  = test['y']                        # true vector
group_a_test = test['group_a']        # group A
group_b_test = test['group_b']        # group B
[8]:
frequency_plot(group_a_test, y_pred)
[8]:
<Axes: title={'center': 'Frequency Plot (Class 1)'}, xlabel='Group', ylabel='Frequency'>
../../../../_images/gallery_tutorials_bias_measuring_bias_measuring_bias_classification_20_1.png
[9]:
# compute statistical parity
statistical_parity(group_a_test, group_b_test, y_pred)
[9]:
np.float64(0.1799772568475383)

The statistical parity indicates the difference in success rate between non-white and white groups. In this case it is outside of ranges considered fair (-0.1, 0.1).

[10]:
# compute disparate impact
disparate_impact(group_a_test, group_b_test, y_pred)
[10]:
np.float64(1.223714135789832)

The disparate impact on the other hand is within the fair range (0.8, 1.2). This shows the importance of considering many different metrics to get a holistic picture of the situation.

[11]:
# compute accuracy difference
accuracy_diff(group_a_test, group_b_test, y_pred, y_true)
[11]:
0.13295694146757975

The above metric is different from the first two in that it also uses the target values, this is an equality of opportunity metric. A value of -0.12 shows that the classifier we trained is less accurate on non-white group than white group. This is expected because of the data imbalance.

[12]:
from holisticai.bias.plots import accuracy_bar_plot

accuracy_bar_plot(group_a_test, y_pred, y_true)
[12]:
<Axes: title={'center': 'Accuracy Bar Plot'}, xlabel='Group', ylabel='Accuracy'>
../../../../_images/gallery_tutorials_bias_measuring_bias_measuring_bias_classification_27_1.png

The above shows the same result as accuracy_diff metric in plot form.

Equality of outcome metrics (batch computation)

[13]:
# import function for batch computation
from holisticai.bias.metrics import classification_bias_metrics
[14]:
classification_bias_metrics(group_a_test, group_b_test, y_pred, y_true, metric_type='equal_outcome')
[14]:
Value Reference
Metric
Statistical Parity 0.179977 0
Disparate Impact 1.223714 1
Four Fifths Rule 0.817184 1
Cohen D 0.916665 0
2SD Rule 25.381560 0

Equality of opportunity metrics (batch computation)

[15]:
classification_bias_metrics(group_a_test, group_b_test, y_pred, y_true, metric_type='equal_opportunity')
[15]:
Value Reference
Metric
Equality of Opportunity Difference 0.079170 0
False Positive Rate Difference 0.379250 0
Average Odds Difference 0.229210 0
Accuracy Difference 0.132957 0

For instance the false positive rate difference of 0.34 hints that white people are more likely to be missclassified as passing the bar than non-whites.

We can show all group bias metrics by setting ‘metric_types’ as ‘group’:

[16]:
classification_bias_metrics(group_a=group_a_test, group_b=group_b_test, y_pred=y_pred, y_true=y_true, metric_type='group')
[16]:
Value Reference
Metric
Statistical Parity 0.179977 0
Disparate Impact 1.223714 1
Four Fifths Rule 0.817184 1
Cohen D 0.916665 0
2SD Rule 25.381560 0
Equality of Opportunity Difference 0.079170 0
False Positive Rate Difference 0.379250 0
Average Odds Difference 0.229210 0
Accuracy Difference 0.132957 0

We can show all individual bias metrics by setting ‘metric_types’ as ‘individual’.

[17]:
classification_bias_metrics(y_pred=y_pred, y_true=y_true, X=X_test_t, metric_type='individual')
[17]:
Value Reference
Metric
Theil Index 0.044640 0
Generalized Entropy Index 0.042503 0
Coefficient of Variation 0.412326 0
Consistency Score 0.977179 1

You can pass specific parameters for individual fairness metrics:

[18]:
individual_kargs = {
    'generalized_entropy_index__alpha': 2,
    'consistency_score__n_neighbors' : 5
}
classification_bias_metrics(y_pred=y_pred, y_true=y_true, X=X_test_t, metric_type='individual', **individual_kargs)
[18]:
Value Reference
Metric
Theil Index 0.044640 0
Generalized Entropy Index 0.042503 0
Coefficient of Variation 0.412326 0
Consistency Score 0.977179 1