---
title: "Fairness Visualizations"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Fairness Visualizations}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>"
)
```

```{r setup}
library(mlr3)
library(mlr3fairness)
library(mlr3learners)
```

# Why we need fairness visualizations:

Through fairness visualizations allow for first investigations into possible fairness problems in a dataset.
In this vignette we will showcase some of the pre-built fairness visualization functions.
All the methods showcased below can be used together with objects of type `BenchmarkResult`, `ResampleResult` and `Prediction`.

# The scenario

For this example, we use the `adult_train` dataset.
Keep in mind all the datasets from `mlr3fairness` package already set protected attribute via the `col_role` "pta", here the "sex" column.

```{r}
t = tsk("adult_train")
t$col_roles$pta
```
We choose a random forest as well as a decision tree model in order to showcase differences in performances.

```{r}
task = tsk("adult_train")$filter(1:5000)
learner = lrn("classif.ranger", predict_type = "prob")
learner$train(task)
predictions = learner$predict(tsk("adult_test")$filter(1:5000))
```
Note, that it is important to evaluate predictions on held-out data in order to obtain unbiased estimates of fairness and performance metrics.
By inspecting the confusion matrix, we can get some first insights.

```{r}
predictions$confusion
```

We furthermore design a small experiment allowing us to compare a random forest (`ranger`) and a decision tree (`rpart`). The result, `bmr` is a `BenchmarkResult` that contains the trained models on each cross-validation split.


```{r}
design = benchmark_grid(
  tasks = tsk("adult_train")$filter(1:5000),
  learners = lrns(c("classif.ranger", "classif.rpart"),
                  predict_type = "prob"),
  resamplings = rsmps("cv", folds = 3)
)

bmr = benchmark(design)
```

# Fairness Prediction Density Plot

By inspecting the prediction density plot we can see the predicted probability for a given class split by the protected attribute, in this case `"sex"`. Large differences in densities might hint at strong differences in the target between groups, either directly in the data or as a consequence of the modeling process. Note, that plotting densities for a `Prediction`
requires a `Task` since information about protected attributes is not contained in the `Prediction`.

We can either plot the density with a `Prediction`
```{r}
fairness_prediction_density(predictions, task)
```

or use it with a `BenchmarkResult` / `ResampleResult`:

```{r}
fairness_prediction_density(bmr)
```

# Fairness Accuracy Tradeoff Plot

In practice, we are most often interested in a trade-off between fairness metrics and a measure of utility such as accuracy.
We showcase individual scores obtained in each cross-validation fold as well as the aggregate (`mean`) in order to additionally provide an indication in the variance of the performance estimates.

```{r}
fairness_accuracy_tradeoff(bmr, msr("fairness.fpr"))
```

# Fairness Comparison Plot

An additional comparison can be obtained using `compare_metrics`. It allows comparing `Learner`s with respect to multiple metrics.
Again, we can use it with a `Prediction`:

```{r}
compare_metrics(predictions, msrs(c("fairness.fpr", "fairness.tpr")), task)
```

or use it with a `BenchmarkResult` / `ResampleResult`:

```{r}
compare_metrics(bmr, msrs(c("classif.ce", "fairness.fpr", "fairness.tpr")))
```

# Custom visualizations

The required metrics to create custom visualizations can also be easily computed using the `$score()` method.

```{r}
bmr$score(msr("fairness.tpr"))
```


# Interpretability

Fairness metrics, in combination with tools from interpretable machine learning can help pinpointing sources of bias. 
In the following example, we try to figure out which variables have a high feature importance for the difference in `classif.eod`, the equalized odds difference. 
In the following example


```{r}
set.seed(432L)
library("iml")
library("mlr3fairness")

learner = lrn("classif.rpart", predict_type = "prob")
task = tsk("adult_train")
# Make the task smaller:
task$filter(sample(task$row_ids, 2000))
task$select(c("sex", "relationship", "race", "capital_loss", "age", "education"))
target = task$target_names
learner$train(task)

model = Predictor$new(model = learner,
                      data = task$data()[,.SD, .SDcols = !target],
                      y = task$data()[, ..target])

custom_metric = function(actual, predicted) {
  compute_metrics(
    data = task$data(),
    target = task$target_names,
    protected_attribute = task$col_roles$pta,
    prediction = predicted,
    metrics = msr("fairness.eod")
  )
}

imp <- FeatureImp$new(model, loss = custom_metric, n.repetitions = 5L)
plot(imp)
```

We can now investigate this variable a little deeper by looking at the distribution of labels in each of the two groups.

```{r}
data = task$data()
data[, setNames(as.list(summary(relationship)/.N),levels(data$relationship)), by = "sex"]
```

We can see, that the different levels are skewed across groups, e.g. 25\% of females in our data are unmarried in contrast to 3\% of males are unmarried.
