Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
kozodoi committed Dec 18, 2018
0 parents commit b38bb96
Show file tree
Hide file tree
Showing 17 changed files with 572 additions and 0 deletions.
Binary file added .DS_Store
Binary file not shown.
2 changes: 2 additions & 0 deletions .Rbuildignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
^fairness\.Rproj$
^\.Rproj\.user$
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.Rproj.user
10 changes: 10 additions & 0 deletions DESCRIPTION
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Package: fairness
Title: Algorithmic Fairness Measures
Version: 0.0.0.1
Authors@R: person("Nikita", "Kozodoi", email = "[email protected]", role = c("aut", "cre"))
Description: This package computes different measures of algorithmic fairness based on a confusion matrix.
Depends: R (>= 3.5.1)
License: What license is it under?
Encoding: UTF-8
LazyData: true
RoxygenNote: 6.1.1
9 changes: 9 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Generated by roxygen2: do not edit by hand

export(acc_parity)
export(dem_parity)
export(dis_impact)
export(fnr_parity)
export(fpr_parity)
export(npv_parity)
export(ppv_parity)
346 changes: 346 additions & 0 deletions R/measures.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,346 @@
#' Disparate Impact
#'
#' This function computes the Disparate Impact metric (Feldman et al. 2015; Zafar et al. 2017)
#'
#'
#' @param predicted Vector of predicted target values
#' @param group Sensitive group (binary or factor)
#' @param cutoff Cutoff for rounding the probabilities
#' @return DI metric
#' @export
#' @examples
#' df = fairness::compas
#' dis_impact(df$label_value, df$score, df$race, "Caucasian")
dis_impact <- function(predicted, group, cutoff = 0.5, base = NULL) {

# check lengths
if (length(predicted) != length(group)) {
stop("Predictions and groups must be of the same length")
}

# convert types
group <- as.factor(group)
predicted <- as.numeric(predicted >= cutoff)

# relevel group
if (is.null(base)) {base <- levels(group)[1]}
group <- relevel(group, base)

# placeholder
val <- rep(NA, length(levels(group)))
names(val) <- levels(group)

# compute value for group 1
cv1 <- mean(predicted[group == levels(group)[1]])

# compute value for other groups
for (i in 1:length(levels(group))) {
val[i] <- mean(predicted[group == levels(group)[i]]) / cv1
}

return(val)
}



#' Demographic Parity
#'
#' This function computes the Demographic Parity metric (Calders and Verwer 2010)
#'
#'
#' @param predicted Vector of predicted target values
#' @param group Sensitive group (binary or factor)
#' @param cutoff Cutoff for rounding the probabilities
#' @return DP metric
#' @export
#' @examples
#' df = fairness::compas
#' dem_parity(df$score, df$race, "Caucasian")
dem_parity <- function(predicted, group, cutoff = 0.5, base = NULL) {

# check lengths
if (length(predicted) != length(group)) {
stop("Predictions and groups must be of the same length")
}

# convert types
group <- as.factor(group)
predicted <- as.numeric(predicted >= cutoff)

# relevel group
if (is.null(base)) {base <- levels(group)[1]}
group <- relevel(group, base)

# placeholder
val <- rep(NA, length(levels(group)))
names(val) <- levels(group)

# compute value for gorup 1
cv1 <- mean(predicted[group == levels(group)[1]])

# compute value for other groups
for (i in 1:length(levels(group))) {
val[i] <- 1 - (mean(predicted[group == levels(group)[i]]) - cv1)
}

return(val)
}



#' FPR Parity
#'
#' This function computes the False Positive Rate Parity metric (Chouldechova 2017)
#'
#'
#' @param actuals Vector of actual target values
#' @param predicted Vector of predicted target values
#' @param group Sensitive group (binary or factor)
#' @param cutoff Cutoff for rounding the probabilities
#' @return FPR Parity metric
#' @examples
#' df = fairness::compas
#' fpr_parity(df$label_value, df$score, df$race, "Caucasian")
#' @export
fpr_parity <- function(actuals, predicted, group, cutoff = 0.5, base = NULL) {

# check lengths
if ((length(actuals) != length(predicted)) | (length(actuals) != length(group))) {
stop("Actuals, predictions and groups must be of the same length")
}

# convert types
group <- as.factor(group)
actuals <- as.numeric(actuals)
predicted <- as.numeric(predicted >= cutoff)

# relevel group
if (is.null(base)) {base <- levels(group)[1]}
group <- relevel(group, base)

# placeholder
val <- rep(NA, length(levels(group)))
names(val) <- levels(group)

# compute value for group 1
fpr1 <- sum(actuals == 0 & predicted == 1 & group == levels(group)[1]) /
sum(actuals ==0 & group == levels(group)[1])

# compute value for other groups
for (i in 1:length(levels(group))) {
fpr1 <- sum(actuals == 0 & predicted == 1 & group == levels(group)[1]) /
sum(actuals == 0 & group == levels(group)[1])
fpri <- sum(actuals == 0 & predicted == 1 & group == levels(group)[i]) /
sum(actuals == 0 & group == levels(group)[i])
val[i] <- fpri / fpr1
}

return(val)
}



#' FNR Parity
#'
#' This function computes the False Negative Rate Parity metric (Chouldechova 2017)
#'
#'
#' @param actuals Vector of actual target values
#' @param predicted Vector of predicted target values
#' @param group Sensitive group (binary or factor)
#' @param cutoff Cutoff for rounding the probabilities
#' @return FNR Parity metric
#' @examples
#' df = fairness::compas
#' fnr_parity(df$label_value, df$score, df$race, "Caucasian")
#' @export
fnr_parity <- function(actuals, predicted, group, cutoff = 0.5, base = NULL) {

# check lengths
if ((length(actuals) != length(predicted)) | (length(actuals) != length(group))) {
stop("Actuals, predictions and groups must be of the same length")
}

# convert types
group <- as.factor(group)
actuals <- as.numeric(actuals)
predicted <- as.numeric(predicted >= cutoff)

# relevel group
if (is.null(base)) {base <- levels(group)[1]}
group <- relevel(group, base)

# placeholder
val <- rep(NA, length(levels(group)))
names(val) <- levels(group)

# compute value for gorup 1
fnr1 <- sum(actuals == 1 & predicted == 0 & group == levels(group)[1]) /
sum(actuals == 1 & group == levels(group)[1])

# compute value for other groups
for (i in 1:length(levels(group))) {
fnr1 <- sum(actuals == 1 & predicted == 0 & group == levels(group)[1]) /
sum(actuals == 1 & group == levels(group)[1])
fnri <- sum(actuals == 1 & predicted == 0 & group == levels(group)[i]) /
sum(actuals == 1 & group == levels(group)[i])
val[i] <- fnri / fnr1
}

return(val)
}



#' PPV Parity
#'
#' This function computes the Positive Predicted Value Parity metric (see Aeuquitas bias audit toolkit)
#'
#'
#' @param actuals Vector of actual target values
#' @param predicted Vector of predicted target values
#' @param group Sensitive group (binary or factor)
#' @param cutoff Cutoff for rounding the probabilities
#' @return FNR Parity metric
#' @examples
#' df = fairness::compas
#' ppv_pariy(df$label_value, df$score, df$race, "Caucasian")
#' @export
ppv_parity <- function(actuals, predicted, group, cutoff = 0.5, base = NULL) {

# check lengths
if ((length(actuals) != length(predicted)) | (length(actuals) != length(group))) {
stop("Actuals, predictions and groups must be of the same length")
}

# convert types
group <- as.factor(group)
actuals <- as.numeric(actuals)
predicted <- as.numeric(predicted >= cutoff)

# relevel group
if (is.null(base)) {base <- levels(group)[1]}
group <- relevel(group, base)

# placeholder
val <- rep(NA, length(levels(group)))
names(val) <- levels(group)

# compute value for group 1
ppv1 <- sum(actuals == 1 & predicted == 1 & group == levels(group)[1]) /
sum(predicted == 1 & group == levels(group)[1])

# compute value for other groups
for (i in 1:length(levels(group))) {

ppv1 <- sum(actuals == 1 & predicted == 1 & group == levels(group)[1]) /
sum(predicted == 1 & group == levels(group)[1])
ppvi <- sum(actuals == 1 & predicted == 1 & group == levels(group)[i]) /
sum(predicted == 1 & group == levels(group)[i])
val[i] <- ppvi / ppv1
}

return(val)
}



#' NPV Parity
#'
#' This function computes the Negative Positive Value Parity metric (see Aeuquitas bias audit toolkit)
#'
#'
#' @param actuals Vector of actual target values
#' @param predicted Vector of predicted target values
#' @param group Sensitive group (binary or factor)
#' @param cutoff Cutoff for rounding the probabilities
#' @return FNR Parity metric
#' @examples
#' df = fairness::compas
#' npv_parity(df$label_value, df$score, df$race, "Caucasian")
#' @export
npv_parity <- function(actuals, predicted, group, cutoff = 0.5, base = NULL) {

# check lengths
if ((length(actuals) != length(predicted)) | (length(actuals) != length(group))) {
stop("Actuals, predictions and groups must be of the same length")
}

# convert types
group <- as.factor(group)
actuals <- as.numeric(actuals)
predicted <- as.numeric(predicted >= cutoff)

# relevel group
if (is.null(base)) {base <- levels(group)[1]}
group <- relevel(group, base)

# placeholder
val <- rep(NA, length(levels(group)))
names(val) <- levels(group)

# compute value for group 1
npv1 <- sum(actuals == 0 & predicted == 0 & group == levels(group)[1]) /
sum(predicted == 0 & group == levels(group)[1])

# compute value for other groups
for (i in 1:length(levels(group))) {
npvi <- sum(actuals == 0 & predicted == 0 & group == levels(group)[i]) /
sum(predicted == 0 & group == levels(group)[i])
val[i] <- npvi / npv1
}

return(val)
}



#' Accuracy Parity
#'
#' This function computes the Accuracy Parity metric (Friedler et al. 2018)
#'
#'
#' @param actuals Vector of actual target values
#' @param predicted Vector of predicted target values
#' @param group Sensitive group (binary or factor)
#' @param cutoff Cutoff for rounding the probabilities
#' @return Accuracy Parity metric
#' @examples
#' df = fairness::compas
#' acc_parity(df$label_value, df$score, df$race, "Caucasian")
#' @export
acc_parity <- function(actuals, predicted, group, cutoff = 0.5, base = NULL) {

# check lengths
if ((length(actuals) != length(predicted)) | (length(actuals) != length(group))) {
stop("Actuals, predictions and groups must be of the same length")
}

# convert types
group <- as.factor(group)
actuals <- as.numeric(actuals)
predicted <- as.numeric(predicted >= cutoff)

# relevel group
if (is.null(base)) {base <- levels(group)[1]}
group <- relevel(group, base)

# placeholder
val <- rep(NA, length(levels(group)))
names(val) <- levels(group)

# compute value for group 1
ac1 <- sum(actuals[group == levels(group)[1]] == predicted[group == levels(group)[1]]) /
sum(group == levels(group)[1])

# compute value for other groups
for (i in 1:length(levels(group))) {
ac1 <- sum(actuals[group == levels(group)[1]] == predicted[group == levels(group)[1]]) /
sum(group == levels(group)[1])
aci <- sum(actuals[group == levels(group)[i]] == predicted[group == levels(group)[i]]) /
sum(group == levels(group)[i])
val[i] <- aci / ac1
}

return(val)
}
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Fairness
Binary file added data/compas.rda
Binary file not shown.
16 changes: 16 additions & 0 deletions fairness.Rproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
Version: 1.0

RestoreWorkspace: No
SaveWorkspace: No
AlwaysSaveHistory: Default

EnableCodeIndexing: Yes
Encoding: UTF-8

AutoAppendNewline: Yes
StripTrailingWhitespace: Yes

BuildType: Package
PackageUseDevtools: Yes
PackageInstallArgs: --no-multiarch --with-keep.source
PackageRoxygenize: rd,collate,namespace
Loading

0 comments on commit b38bb96

Please sign in to comment.