63  AI Ethics and Responsible AI

Note📋 Learning Objectives
  • Identify the primary ethical risks in AI-powered analytics: bias, fairness, privacy, and accountability
  • Apply quantitative fairness metrics to evaluate disparate impact across demographic groups
  • Understand explainability requirements and how to communicate model decisions to non-technical stakeholders
  • Navigate the emerging regulatory landscape, including the EU AI Act and African data protection frameworks
  • Implement responsible AI governance practices in your organisation

63.1 Why Ethics Matters in Analytics

The techniques in this book are powerful. A credit-scoring model trained on historical data can process thousands of loan applications per day at far lower cost than a team of human analysts. A churn-prediction model can identify at-risk customers before any human reviewer notices a pattern. A fraud-detection system can flag suspicious transactions in milliseconds.

But power without accountability is dangerous. Historical data encodes historical inequities. A credit model trained on data from a society where women were systematically excluded from formal employment will, if not carefully examined, learn to penalise women. An automated hiring system trained on decades of resumes from a male-dominated industry will, if not corrected, discriminate against women — even if gender is not an explicit input.

This is not a hypothetical risk. Multiple real-world AI systems have caused documented harm:

  • Amazon scrapped its AI hiring tool in 2018 after discovering it penalised resumes containing the word “women’s” (as in “women’s chess club”)
  • Compas, a US criminal recidivism prediction tool, was found to score Black defendants as higher risk than white defendants with similar profiles
  • In healthcare, an algorithm used by hundreds of US hospitals was found to recommend less care for Black patients with identical illness severity to white patients

These are not failures of mathematics. They are failures of oversight, governance, and ethical reasoning. As analytics professionals, you have both the technical capability and the professional responsibility to prevent such outcomes.

63.2 Core Concepts in AI Ethics

63.2.1 Bias: Sources and Types

Bias in AI systems arises from multiple sources:

Bias Type Definition Example
Historical bias Training data reflects past discrimination Loan data where minorities were denied credit
Representation bias Some groups are underrepresented in training data Medical AI trained mostly on Western patients
Measurement bias Proxy variables indirectly encode protected attributes Postcode as a proxy for race
Aggregation bias One model applied to groups with different distributions Diabetes risk model ignoring gender differences
Deployment bias Model is used in a context different from training Model trained on urban data deployed rurally
Feedback loop bias Model predictions influence future training data Predictive policing that over-scrutinises certain areas

63.2.2 Fairness: Multiple Definitions

There is no single definition of “fairness.” Different stakeholders may prefer different criteria, and — critically — it is mathematically impossible to satisfy all of them simultaneously.

Note📘 Theory: Fairness Metrics

Let \(\hat{Y}\) be the model’s prediction, \(Y\) be the true label, and \(A \in \{0, 1\}\) be a sensitive attribute (e.g., gender, ethnicity).

Demographic Parity:
\(P(\hat{Y} = 1 \mid A = 0) = P(\hat{Y} = 1 \mid A = 1)\)
Equal approval (or positive prediction) rates across groups.

Equal Opportunity:
\(P(\hat{Y} = 1 \mid Y = 1, A = 0) = P(\hat{Y} = 1 \mid Y = 1, A = 1)\)
Equal true positive rates — qualified candidates from both groups are equally likely to be approved.

Predictive Parity:
\(P(Y = 1 \mid \hat{Y} = 1, A = 0) = P(Y = 1 \mid \hat{Y} = 1, A = 1)\)
Equal precision — a positive prediction means the same thing regardless of group.

Individual Fairness:
Similar individuals should receive similar predictions:
\(\|\hat{Y}(x_i) - \hat{Y}(x_j)\| \leq L \cdot d(x_i, x_j)\)

The impossibility theorem (Chouldechova, 2017): When base rates differ between groups, demographic parity, equal opportunity, and predictive parity cannot all be satisfied simultaneously.

Show code
library(tidyverse)

set.seed(5193)

# Simulate a loan approval dataset with demographic disparities
n <- 2000

loan_data <- tibble(
  applicant_id = 1:n,
  # Protected attribute: zone (North/South as a rough proxy)
  zone         = sample(c("North", "South"), n, replace = TRUE, prob = c(0.45, 0.55)),
  # Credit-relevant features
  credit_score = rnorm(n, mean = if_else(zone == "South", 620, 590), sd = 80),
  income_k     = rnorm(n, mean = if_else(zone == "South", 180, 150), sd = 60),
  years_employed = rnorm(n, mean = if_else(zone == "South", 6, 4), sd = 3)
) |>
  mutate(
    credit_score    = pmax(300, pmin(850, credit_score)),
    income_k        = pmax(50, income_k),
    years_employed  = pmax(0, years_employed),
    # True default probability (depends on credit features, NOT zone)
    default_prob    = plogis(-2 + 0.015 * (credit_score - 600) +
                             0.003 * income_k + 0.05 * years_employed),
    actual_default  = rbinom(n, 1, default_prob),
    # Model 1: Fair (uses only credit features)
    score_fair      = credit_score * 0.5 + income_k * 0.3 + years_employed * 20,
    approved_fair   = as.integer(score_fair > quantile(score_fair, 0.40)),
    # Model 2: Biased (implicitly disadvantages North via correlated feature)
    score_biased    = score_fair - if_else(zone == "North", rnorm(n, 15, 5), 0),
    approved_biased = as.integer(score_biased > quantile(score_biased, 0.40))
  )

# ── Compute fairness metrics ────────────────────────────────────────────────────
compute_fairness <- function(data, approved_col) {
  data |>
    group_by(zone) |>
    summarise(
      n                  = n(),
      approval_rate      = mean(.data[[approved_col]]),
      true_positive_rate = sum(.data[[approved_col]] == 1 & actual_default == 0) /
                           sum(actual_default == 0),
      false_positive_rate = sum(.data[[approved_col]] == 1 & actual_default == 1) /
                            sum(actual_default == 1),
      .groups = "drop"
    ) |>
    mutate(
      dem_parity_ratio = approval_rate / approval_rate[1]
    )
}

cat("=== Model 1: Fair (Credit Features Only) ===\n")
#> === Model 1: Fair (Credit Features Only) ===
fair_metrics <- compute_fairness(loan_data, "approved_fair")
print(fair_metrics |> mutate(across(where(is.numeric), round, 3)))
#> # A tibble: 2 × 6
#>   zone      n approval_rate true_positive_rate false_positive_rate
#>   <chr> <dbl>         <dbl>              <dbl>               <dbl>
#> 1 North   875         0.4                0.336               0.615
#> 2 South  1125         0.756              0.699               0.877
#> # ℹ 1 more variable: dem_parity_ratio <dbl>

cat("\n=== Model 2: Biased (Implicit Zone Penalty) ===\n")
#> 
#> === Model 2: Biased (Implicit Zone Penalty) ===
biased_metrics <- compute_fairness(loan_data, "approved_biased")
print(biased_metrics |> mutate(across(where(is.numeric), round, 3)))
#> # A tibble: 2 × 6
#>   zone      n approval_rate true_positive_rate false_positive_rate
#>   <chr> <dbl>         <dbl>              <dbl>               <dbl>
#> 1 North   875         0.365              0.302               0.575
#> 2 South  1125         0.783              0.725               0.908
#> # ℹ 1 more variable: dem_parity_ratio <dbl>

cat("\n=== Disparate Impact Analysis ===\n")
#> 
#> === Disparate Impact Analysis ===
cat("Fair model — North/South approval ratio:",
    round(fair_metrics$approval_rate[1] / fair_metrics$approval_rate[2], 3), "\n")
#> Fair model — North/South approval ratio: 0.529
cat("Biased model — North/South approval ratio:",
    round(biased_metrics$approval_rate[1] / biased_metrics$approval_rate[2], 3), "\n")
#> Biased model — North/South approval ratio: 0.466
cat("\n80% rule (US EEOC guideline): ratio < 0.8 suggests disparate impact.\n")
#> 
#> 80% rule (US EEOC guideline): ratio < 0.8 suggests disparate impact.
Show code
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from scipy.special import expit

np.random.seed(5193)
n = 2000

zone = np.random.choice(['North', 'South'], n, p=[0.45, 0.55])
credit_score = np.where(zone == 'South',
                        np.random.normal(620, 80, n),
                        np.random.normal(590, 80, n)).clip(300, 850)
income_k = np.where(zone == 'South',
                    np.random.normal(180, 60, n),
                    np.random.normal(150, 60, n)).clip(50)
years_emp = np.where(zone == 'South',
                     np.random.normal(6, 3, n),
                     np.random.normal(4, 3, n)).clip(0)

default_prob = expit(-2 + 0.015*(credit_score - 600) + 0.003*income_k + 0.05*years_emp)
actual_default = np.random.binomial(1, default_prob)

score_fair = credit_score * 0.5 + income_k * 0.3 + years_emp * 20
approved_fair = (score_fair > np.percentile(score_fair, 40)).astype(int)

score_biased = score_fair - np.where(zone == 'North', np.random.normal(15, 5, n), 0)
approved_biased = (score_biased > np.percentile(score_biased, 40)).astype(int)

data = pd.DataFrame({
    'zone': zone, 'credit_score': credit_score,
    'actual_default': actual_default,
    'approved_fair': approved_fair, 'approved_biased': approved_biased
})

def fairness_report(df, col):
    res = df.groupby('zone').apply(lambda g: pd.Series({
        'n': len(g),
        'approval_rate': g[col].mean(),
        'tpr': ((g[col]==1) & (g['actual_default']==0)).sum() / (g['actual_default']==0).sum(),
        'fpr': ((g[col]==1) & (g['actual_default']==1)).sum() / (g['actual_default']==1).sum(),
    })).reset_index()
    res['disparate_impact'] = res['approval_rate'] / res['approval_rate'].max()
    return res.round(3)

print("=== Fair Model ===")
#> === Fair Model ===
print(fairness_report(data, 'approved_fair').to_string(index=False))
#>  zone      n  approval_rate   tpr   fpr  disparate_impact
#> North  859.0          0.405 0.349 0.589             0.543
#> South 1141.0          0.747 0.682 0.874             1.000

print("\n=== Biased Model ===")
#> 
#> === Biased Model ===
print(fairness_report(data, 'approved_biased').to_string(index=False))
#>  zone      n  approval_rate   tpr   fpr  disparate_impact
#> North  859.0          0.361 0.301 0.554             0.463
#> South 1141.0          0.780 0.717 0.906             1.000

# Visualise
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
for ax, col, title in [(ax1, 'approved_fair', 'Fair Model'),
                        (ax2, 'approved_biased', 'Biased Model')]:
    rates = data.groupby('zone')[col].mean()
    bars = ax.bar(rates.index, rates.values, color=['#2ca02c', '#d62728'], alpha=0.8)
    ax.axhline(0.40, color='grey', linestyle='--', linewidth=1, label='Overall rate')
    ax.set_ylabel('Approval Rate')
    ax.set_title(f'Approval Rate by Zone: {title}')
    ax.set_ylim(0, 0.7)
    ax.legend()
    ax.grid(axis='y', alpha=0.3)
    for bar, rate in zip(bars, rates.values):
        ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.01,
                f'{rate:.1%}', ha='center', fontweight='bold')

plt.tight_layout()
plt.savefig('ch58_fairness_comparison.png', dpi=150, bbox_inches='tight')
plt.show()

63.3 Explainability and Transparency

Regulators, customers, and courts increasingly require that AI decisions be explainable. In Nigeria, the CBN requires that credit decisions be accompanied by reasons. The EU AI Act (2024) mandates transparency for high-risk AI systems. GDPR grants individuals the right to an explanation of automated decisions.

We covered SHAP and LIME in Chapter 16. Here we focus on how to communicate these explanations to non-technical stakeholders.

Show code
library(tidyverse)

# Construct a clear explanation letter from SHAP-like feature contributions
generate_explanation_letter <- function(applicant, contributions, decision) {
  declined_factors <- contributions |>
    filter(direction == "negative") |>
    arrange(abs_contribution) |>
    head(3)

  supporting_factors <- contributions |>
    filter(direction == "positive") |>
    arrange(desc(abs_contribution)) |>
    head(2)

  cat("─────────────────────────────────────────────────────\n")
  cat("LOAN APPLICATION DECISION NOTICE\n")
  cat("─────────────────────────────────────────────────────\n\n")
  cat("Dear ", applicant$name, ",\n\n", sep = "")
  cat("Thank you for your loan application of ₦", format(applicant$amount, big.mark = ","),
      ".\n\n", sep = "")

  if (decision == "Declined") {
    cat("After a thorough assessment, we are unable to approve your\n")
    cat("application at this time. The key factors in this decision were:\n\n")
    for (i in 1:nrow(declined_factors)) {
      cat("  •  ", declined_factors$factor[i], ": ",
          declined_factors$explanation[i], "\n", sep = "")
    }
  } else {
    cat("We are pleased to approve your application. Key strengths:\n\n")
    for (i in 1:nrow(supporting_factors)) {
      cat("  •  ", supporting_factors$factor[i], ": ",
          supporting_factors$explanation[i], "\n", sep = "")
    }
  }

  cat("\nYou have the right to request further information about this\n")
  cat("decision and to lodge a complaint with our Credit Review Unit.\n\n")
  cat("Yours sincerely,\nCredit Decisions Team\n")
  cat("─────────────────────────────────────────────────────\n")
}

# Example application
applicant <- list(name = "Mr Emeka Obi", amount = 2500000)

contributions <- tribble(
  ~factor,              ~abs_contribution, ~direction, ~explanation,
  "Credit Score",       0.35,             "negative", "Credit score of 520 is below our minimum threshold of 650",
  "Loan-to-Income",     0.28,             "negative", "Requested loan is 4.2× annual income (maximum: 3.5×)",
  "Employment Tenure",  0.18,             "negative", "Current employment of 8 months is below the 12-month requirement",
  "Repayment History",  0.12,             "positive", "No previous defaults on record",
  "Account Relationship", 0.07,           "positive", "Active account with the bank for 3+ years"
)

generate_explanation_letter(applicant, contributions, "Declined")
#> ─────────────────────────────────────────────────────
#> LOAN APPLICATION DECISION NOTICE
#> ─────────────────────────────────────────────────────
#> 
#> Dear Mr Emeka Obi,
#> 
#> Thank you for your loan application of ₦2,500,000.
#> 
#> After a thorough assessment, we are unable to approve your
#> application at this time. The key factors in this decision were:
#> 
#>   •  Employment Tenure: Current employment of 8 months is below the 12-month requirement
#>   •  Loan-to-Income: Requested loan is 4.2× annual income (maximum: 3.5×)
#>   •  Credit Score: Credit score of 520 is below our minimum threshold of 650
#> 
#> You have the right to request further information about this
#> decision and to lodge a complaint with our Credit Review Unit.
#> 
#> Yours sincerely,
#> Credit Decisions Team
#> ─────────────────────────────────────────────────────
Show code
import pandas as pd
import numpy as np

# Simulate SHAP values for a credit decision
applicant = {
    'name': 'Ms Amaka Nwosu',
    'loan_amount': 1_500_000,
    'decision': 'Approved'
}

shap_values = pd.DataFrame({
    'feature': ['Credit Score', 'Income', 'Years Employed', 'Debt-to-Income', 'Previous Defaults'],
    'value': [710, 220_000, 7.5, 0.28, 0],
    'shap': [0.42, 0.31, 0.18, -0.08, 0.15],
    'baseline': [0.35, 0.35, 0.35, 0.35, 0.35]
})

shap_values['direction'] = shap_values['shap'].apply(lambda x: 'Positive' if x > 0 else 'Negative')
shap_values['cumulative'] = shap_values['baseline'] + shap_values['shap'].cumsum()

print("=== SHAP-Based Credit Decision Explanation ===\n")
#> === SHAP-Based Credit Decision Explanation ===
print(f"Applicant: {applicant['name']}")
#> Applicant: Ms Amaka Nwosu
print(f"Loan Amount: ₦{applicant['loan_amount']:,.0f}")
#> Loan Amount: ₦1,500,000
print(f"Decision: {applicant['decision']}\n")
#> Decision: Approved

print("Feature Contributions:")
#> Feature Contributions:
for _, row in shap_values.iterrows():
    arrow = "↑ +" if row['shap'] > 0 else "↓  "
    print(f"  {row['feature']:<22} value: {str(row['value']):<10} "
          f"SHAP: {arrow}{row['shap']:.3f}")
#>   Credit Score           value: 710.0      SHAP: ↑ +0.420
#>   Income                 value: 220000.0   SHAP: ↑ +0.310
#>   Years Employed         value: 7.5        SHAP: ↑ +0.180
#>   Debt-to-Income         value: 0.28       SHAP: ↓  -0.080
#>   Previous Defaults      value: 0.0        SHAP: ↑ +0.150

base_rate  = float(shap_values['baseline'].iloc[0])
final_prob = base_rate + float(shap_values['shap'].sum())
print(f"\nBase rate (average approval):  {base_rate:.1%}")
#> 
#> Base rate (average approval):  35.0%
print(f"Final approval probability:    {final_prob:.1%}")
#> Final approval probability:    133.0%
print(f"\nDecision: {applicant['decision']}")
#> 
#> Decision: Approved
print("\nRegulatory note: This explanation has been logged for audit purposes.")
#> 
#> Regulatory note: This explanation has been logged for audit purposes.

# Waterfall chart
fig, ax = plt.subplots(figsize=(10, 5))
features = shap_values['feature'].tolist()
shapvals = shap_values['shap'].tolist()
base = shap_values['baseline'].iloc[0]

cumul = [base]
for s in shapvals:
    cumul.append(cumul[-1] + s)

colors = ['steelblue'] + ['#2ca02c' if s > 0 else '#d62728' for s in shapvals]
bars = ax.barh(['Base Rate'] + features,
               [base] + shapvals,
               left=[0] + cumul[:-1],
               color=colors, alpha=0.85, edgecolor='white', linewidth=0.5)

ax.axvline(cumul[-1], color='black', linestyle='--', linewidth=1.5, label=f'Final: {cumul[-1]:.1%}')
#> <matplotlib.lines.Line2D object at 0x000002016BDEAE40>
ax.set_xlabel('Approval Probability')
#> Text(0.5, 0, 'Approval Probability')
ax.set_title(f'SHAP Waterfall: {applicant["name"]}{applicant["decision"]}')
#> Text(0.5, 1.0, 'SHAP Waterfall: Ms Amaka Nwosu — Approved')
ax.legend()
#> <matplotlib.legend.Legend object at 0x000002016BDEAF90>
ax.grid(axis='x', alpha=0.3)
plt.tight_layout()
plt.savefig('ch58_shap_waterfall.png', dpi=150, bbox_inches='tight')
plt.show()

63.4 Privacy and Data Protection

63.4.1 African Data Protection Landscape

Country/Region Framework Key Provisions
Nigeria Nigeria Data Protection Act (NDPA, 2023) Data subject rights, consent, cross-border transfer restrictions
South Africa POPIA (Protection of Personal Information Act, 2020) 8 lawful processing conditions, Information Regulator
Kenya Data Protection Act (2019) Registration of data controllers, impact assessments
Ghana Data Protection Act (2012) Data Protection Commission oversight
ECOWAS Supplementary Act on Personal Data (2010) Framework for the region
EU (GDPR) General Data Protection Regulation (2018) Applies to any organisation processing EU residents’ data

63.4.2 Privacy-Preserving Analytics Techniques

Show code
library(tidyverse)

set.seed(8834)

# ── Technique 1: Data Anonymisation via k-Anonymity ──────────────────────────
# A dataset is k-anonymous if every record is indistinguishable from
# at least k-1 others on the quasi-identifiers.

raw_data <- tibble(
  name         = c("Aisha Bello", "Chukwu Eze", "Fatima Umar", "James Ade",
                    "Ngozi Obi", "Sani Danladi"),
  age          = c(28, 35, 28, 42, 35, 42),
  postcode     = c("100001", "100001", "100001", "102001", "102001", "102001"),
  salary_k     = c(180, 220, 175, 350, 210, 380),
  has_diabetes = c(0, 1, 0, 1, 0, 1)
)

# Generalise quasi-identifiers to achieve 3-anonymity
anonymised <- raw_data |>
  mutate(
    name     = "REDACTED",                                    # Direct identifier removed
    age_band = cut(age, breaks = c(20, 35, 50),              # Age generalised
                   labels = c("20–35", "35–50")),
    postcode_trunc = str_sub(postcode, 1, 4) |> paste0("XX")  # Postcode truncated to 4 digits
  ) |>
  select(-name, -age, -postcode)

cat("=== Original Data (Privacy Risk) ===\n")
#> === Original Data (Privacy Risk) ===
print(raw_data |> select(-name))
#> # A tibble: 6 × 4
#>     age postcode salary_k has_diabetes
#>   <dbl> <chr>       <dbl>        <dbl>
#> 1    28 100001        180            0
#> 2    35 100001        220            1
#> 3    28 100001        175            0
#> 4    42 102001        350            1
#> 5    35 102001        210            0
#> 6    42 102001        380            1

cat("\n=== 3-Anonymised Data (k=3 per quasi-identifier combination) ===\n")
#> 
#> === 3-Anonymised Data (k=3 per quasi-identifier combination) ===
print(anonymised)
#> # A tibble: 6 × 4
#>   salary_k has_diabetes age_band postcode_trunc
#>      <dbl>        <dbl> <fct>    <chr>         
#> 1      180            0 20–35    1000XX        
#> 2      220            1 20–35    1000XX        
#> 3      175            0 20–35    1000XX        
#> 4      350            1 35–50    1020XX        
#> 5      210            0 20–35    1020XX        
#> 6      380            1 35–50    1020XX

cat("\nVerification: Each (age_band, postcode) group has k =",
    min(anonymised |> count(age_band, postcode_trunc) |> pull(n)), "records.\n")
#> 
#> Verification: Each (age_band, postcode) group has k = 1 records.

# ── Technique 2: Differential Privacy (noise addition) ─────────────────────────
cat("\n=== Differential Privacy Example ===\n")
#> 
#> === Differential Privacy Example ===

true_avg_salary  <- mean(raw_data$salary_k)
epsilon          <- 1.0      # Privacy budget (smaller = more private)
sensitivity      <- 330      # Max salary range / n
noise            <- rlaplace <- function(n, mu, b) mu + (rexp(n, 1/b) - rexp(n, 1/b))

dp_avg_salary <- true_avg_salary + noise(1, 0, sensitivity / epsilon)

cat("True average salary:              ₦", round(true_avg_salary, 0), "k\n")
#> True average salary:              ₦ 252 k
cat("DP-noised average salary:         ₦", round(dp_avg_salary, 0), "k\n")
#> DP-noised average salary:         ₦ 177 k
cat("Privacy budget (epsilon):         ", epsilon, "\n")
#> Privacy budget (epsilon):          1
cat("The noised value protects individuals while preserving the aggregate signal.\n")
#> The noised value protects individuals while preserving the aggregate signal.
Show code
import pandas as pd
import numpy as np

np.random.seed(8834)

# Demonstrate k-anonymity and differential privacy

raw = pd.DataFrame({
    'name': ['Aisha Bello', 'Chukwu Eze', 'Fatima Umar', 'James Ade', 'Ngozi Obi', 'Sani Danladi'],
    'age': [28, 35, 28, 42, 35, 42],
    'postcode': ['100001', '100001', '100001', '102001', '102001', '102001'],
    'salary_k': [180, 220, 175, 350, 210, 380],
    'has_diabetes': [0, 1, 0, 1, 0, 1]
})

print("=== Original Data ===")
#> === Original Data ===
print(raw.drop(columns=['name']).to_string(index=False))
#>  age postcode  salary_k  has_diabetes
#>   28   100001       180             0
#>   35   100001       220             1
#>   28   100001       175             0
#>   42   102001       350             1
#>   35   102001       210             0
#>   42   102001       380             1

# k-Anonymisation
anon = raw.copy()
anon['name'] = 'REDACTED'
anon['age_band'] = pd.cut(anon['age'], bins=[20, 35, 50], labels=['20-35', '35-50'])
anon['postcode_trunc'] = anon['postcode'].str[:4] + 'XX'
anon = anon.drop(columns=['name', 'age', 'postcode'])

print("\n=== 3-Anonymised Data ===")
#> 
#> === 3-Anonymised Data ===
print(anon.to_string(index=False))
#>  salary_k  has_diabetes age_band postcode_trunc
#>       180             0    20-35         1000XX
#>       220             1    20-35         1000XX
#>       175             0    20-35         1000XX
#>       350             1    35-50         1020XX
#>       210             0    20-35         1020XX
#>       380             1    35-50         1020XX

k = anon.groupby(['age_band', 'postcode_trunc']).size().min()
print(f"\nMinimum k achieved: {k} (all quasi-identifier groups have at least {k} records)")
#> 
#> Minimum k achieved: 0 (all quasi-identifier groups have at least 0 records)

# Differential Privacy
print("\n=== Differential Privacy: Salary Average ===")
#> 
#> === Differential Privacy: Salary Average ===
true_mean = raw['salary_k'].mean()
sensitivity = 330  # (max - min) salary range
epsilon = 1.0

laplace_noise = np.random.laplace(0, sensitivity / epsilon)
dp_mean = true_mean + laplace_noise

print(f"True mean salary:   ₦{true_mean:.0f}k")
#> True mean salary:   ₦252k
print(f"DP noised mean:     ₦{dp_mean:.0f}k")
#> DP noised mean:     ₦438k
print(f"Privacy budget ε:   {epsilon}  (lower = stronger privacy)")
#> Privacy budget ε:   1.0  (lower = stronger privacy)
print("The noise hides individual values while preserving population statistics.")
#> The noise hides individual values while preserving population statistics.

63.5 Accountability and Governance

Responsible AI is not just a technical problem — it is an organisational one. The following governance framework applies whether you are building a model in a startup, a multinational, or a government agency.

library(tidyverse)

governance_checklist <- tribble(
  ~Phase, ~Practice, ~Tool_or_Output,
  "Design",     "Define the problem and success criteria explicitly",         "Problem statement doc",
  "Design",     "Identify affected stakeholders and potential harms",         "Stakeholder impact map",
  "Design",     "Check data sources for historical bias",                     "Data provenance audit",
  "Development","Evaluate model on fairness metrics across demographic groups","Fairness audit report",
  "Development","Document model assumptions and limitations",                 "Model card",
  "Development","Implement explainability (SHAP, LIME) for high-stakes models","Explanation framework",
  "Deployment", "Establish human review thresholds for borderline decisions", "Escalation protocol",
  "Deployment", "Monitor for distribution shift and performance degradation",  "Model monitoring dashboard",
  "Deployment", "Log all model decisions for audit",                          "Decision audit trail",
  "Lifecycle",  "Set model expiration dates and retraining triggers",         "Model refresh schedule",
  "Lifecycle",  "Provide appeals process for automated decisions",            "Complaints procedure",
  "Lifecycle",  "Annual ethics review by independent panel",                  "Ethics review report"
)

cat("=== Responsible AI Governance Checklist ===\n\n")
#> === Responsible AI Governance Checklist ===
for (phase in unique(governance_checklist$Phase)) {
  cat("▶", phase, "\n")
  items <- governance_checklist |> filter(Phase == phase)
  for (i in 1:nrow(items)) {
    cat("  □ ", items$Practice[i], "\n", sep = "")
    cat("      → ", items$Tool_or_Output[i], "\n", sep = "")
  }
  cat("\n")
}
#> ▶ Design 
#>   □ Define the problem and success criteria explicitly
#>       → Problem statement doc
#>   □ Identify affected stakeholders and potential harms
#>       → Stakeholder impact map
#>   □ Check data sources for historical bias
#>       → Data provenance audit
#> 
#> ▶ Development 
#>   □ Evaluate model on fairness metrics across demographic groups
#>       → Fairness audit report
#>   □ Document model assumptions and limitations
#>       → Model card
#>   □ Implement explainability (SHAP, LIME) for high-stakes models
#>       → Explanation framework
#> 
#> ▶ Deployment 
#>   □ Establish human review thresholds for borderline decisions
#>       → Escalation protocol
#>   □ Monitor for distribution shift and performance degradation
#>       → Model monitoring dashboard
#>   □ Log all model decisions for audit
#>       → Decision audit trail
#> 
#> ▶ Lifecycle 
#>   □ Set model expiration dates and retraining triggers
#>       → Model refresh schedule
#>   □ Provide appeals process for automated decisions
#>       → Complaints procedure
#>   □ Annual ethics review by independent panel
#>       → Ethics review report

63.6 The Regulatory Landscape

63.6.1 EU AI Act (2024)

The EU AI Act classifies AI systems by risk level:

Risk Level Examples Requirements
Unacceptable Social scoring, real-time biometric surveillance in public Prohibited
High Credit scoring, hiring, CV screening, essential services Transparency, human oversight, conformity assessment, registration
Limited Chatbots, deepfakes Disclosure requirements
Minimal Spam filters, AI in video games No specific requirements

For Nigerian and African organisations that export to the EU, or process data of EU residents, high-risk provisions effectively apply.

63.6.2 Nigeria: NDPA 2023 and CBN AI Guidelines

The Nigeria Data Protection Act (2023) gives individuals the right to: - Know what data is collected about them - Access, correct, and delete their data - Opt out of automated decision-making with legal effects

The Central Bank of Nigeria (CBN) has issued guidance requiring banks to: - Document AI models used in credit decisions - Provide reasons for declined applications - Maintain audit logs for five years - Report AI-related incidents to the CBN within 72 hours

Caution📝 Chapter Review Questions
  1. A credit-scoring model has an overall AUC of 0.82 but shows a disparate impact ratio of 0.68 for female applicants. What does this mean, and what are your options for addressing it?

  2. Define the “impossibility theorem” of fairness in your own words. What practical implication does this have for a bank deploying an automated loan system?

  3. Your organisation wants to share anonymised customer data with an academic research partner. What steps would you take to ensure the data cannot be re-identified?

  4. A model’s SHAP explanation shows that “postcode” is the most important feature in a credit risk model. Should this concern you? What would you investigate?

  5. Under the Nigeria Data Protection Act, a customer disputes a loan decline made by an AI model. What must your organisation provide to the customer?

63.7 Chapter Exercises

Chapter 58 Exercises

Exercise 58.1: Identifying Bias in AI Systems

  1. A bank’s loan approval model was trained on 10 years of historical data during which women were statistically less likely to receive loans (due to discriminatory practices at the time). The model learns to predict “loan granted” based on historical outcomes. Explain, step by step, how this creates a feedback loop that perpetuates discrimination even if gender is not explicitly included as a feature.

  2. A hospital in Abuja deploys an AI system to predict which patients are at risk of developing complications after surgery. The system was trained mostly on data from European hospitals. A Nigerian doctor notices it performs well for middle-class urban patients but poorly for rural patients. What type of bias is this, and why does it occur?

  3. A company’s AI-powered CV screening tool rejects candidates whose resumes mention “women’s leadership” or “female entrepreneur networks.” This was not intentional — the model learned from historical hiring decisions. Explain why removing the word “gender” from the input data would not solve this problem. What would actually solve it?

  4. Measurement bias occurs when the data used to train a model measures the wrong thing. A predictive policing algorithm uses “past arrests” as a proxy for “criminal behaviour.” Explain why this proxy is flawed and how the flawed measurement introduces bias against communities that have historically been over-policed.

  5. You are auditing a credit scoring model for bias. You compare approval rates across three groups: Salaried employees (72%), Self-employed (48%), Unemployed (15%). Is this necessarily evidence of bias? What additional analysis would you need to determine whether these differences are legitimate or discriminatory?


Exercise 58.2: Fairness Metrics — Calculations and Trade-offs

A recruitment AI tool screens candidates for a management training programme. In a test on 1,000 candidates (500 male, 500 female), the tool produces these outcomes:

Males Recommended Males Not Recommended Females Recommended Females Not Recommended
Truly Qualified 180 20 120 80
Not Truly Qualified 80 220 40 260
  1. Calculate the overall recommendation rate (% recommended) for males and females. Calculate the disparate impact ratio (female rate ÷ male rate).

  2. Calculate the True Positive Rate (Recall / Equal Opportunity) for each group: of all truly qualified candidates, what fraction were recommended? Is the model equally good at identifying qualified candidates from both groups?

  3. Calculate the Precision for each group: of those recommended, what fraction are truly qualified? Is a “pass” from the tool equally meaningful for both groups?

  4. You are told that the base rate (proportion of truly qualified candidates) differs: 40% of male candidates are truly qualified vs. 40% of female candidates. (Both groups have the same base rate in this dataset.) The model still shows disparate impact. What does this tell you about the source of the discrimination?

  5. The hiring manager says: “Fix it so that the same percentage of male and female candidates are recommended (demographic parity).” The data scientist says: “But if we do that, we’ll need to lower the threshold for females, which means we’ll recommend less-qualified female candidates.” Explain the fundamental tension here, using the impossibility theorem as your framework.


Exercise 58.3: Explainability in Practice

A Nigerian telecoms company uses an AI model to predict whether a customer will file a regulatory complaint in the next 30 days. The model flags 500 high-risk customers each week for priority service.

  1. A customer calls to say: “I was told I’m on a ‘high-risk’ list and I want to know why.” Under the Nigeria Data Protection Act (NDPA 2023), what rights does this customer have? What must the company provide?

  2. The model uses 47 features including: days since last call to customer service, average call waiting time, number of dropped calls, bill amount, subscription type. SHAP analysis shows the top 3 features for this specific customer are: dropped calls (+0.42), call waiting time (+0.31), and subscription type: prepaid (−0.18). Translate these three SHAP values into a plain-English explanation suitable for a customer service agent to read to the customer.

  3. The telecoms regulator (NCC) audits the company and asks: “Show us that your model does not discriminate based on geography (which, in Nigeria, correlates with ethnicity).” Describe the specific fairness analysis you would conduct to answer this question.

  4. The company’s legal team says: “We should not tell customers why they are flagged — this could be used to game the system.” The customer experience team says: “We must explain — the customer deserves to know.” Who is right? How would you design a system that balances transparency with gaming-resistance?

  5. Propose a model governance policy of 5 rules for this telecoms company. Each rule should address a specific ethical risk and describe what the company must do (not just what it should avoid).


Exercise 58.4: Data Privacy in Analytics

A hospital network in Nigeria wants to use patient data to build predictive models for disease prevention. They have records for 2 million patients covering: demographics, diagnosis history, medication records, and appointment attendance.

  1. Under the Nigeria Data Protection Act 2023, what legal basis would the hospital need to process this data for research purposes? What must patients be told?

  2. The hospital wants to share the dataset with a university research team. Before sharing, they remove patient names and ID numbers. Explain why this anonymisation may be insufficient, and describe a specific re-identification attack that could use the remaining data.

  3. The research team proposes using k-anonymity to protect the dataset. Explain what k-anonymity means (without using technical jargon) and describe one weakness of k-anonymity that a more advanced technique like l-diversity addresses.

  4. A data scientist proposes training the model using federated learning — training on data that never leaves the hospital’s servers. Explain how federated learning works and why it addresses the privacy concern better than sharing raw data.

  5. After the model is deployed, a journalist discovers it and writes a story: “Hospital AI predicts your risk of diabetes — without your consent.” Assess the ethical issue raised and how the hospital should have handled the consent process from the start.


Exercise 58.5: Capstone — Responsible AI Governance Policy

You are the Chief Data Officer of a major Nigerian commercial bank. The Board has asked you to develop a Responsible AI Policy for the bank.

  1. The bank uses AI in five areas: credit scoring, fraud detection, customer service chatbots, employee performance evaluation, and marketing personalisation. Rank these five applications from highest to lowest ethical risk. Justify your ranking using specific harms that could occur in each application.

  2. For the highest-risk application (your #1 from above), design a human-in-the-loop protocol. Specifically: what decisions can the AI make autonomously, which require human review, and which must always be made by a qualified human?

  3. Draft a Model Risk Assessment Checklist with 8 specific items that the bank’s data science team must complete before deploying any new AI model. For each item, write: (i) the question the team must answer; (ii) what evidence they must provide.

  4. The bank’s Board asks: “How do we know our AI systems are behaving ethically on an ongoing basis?” Design a quarterly AI Ethics Review process: who attends, what is reviewed, what triggers an alert, and what actions can the Board take.

  5. Write a 300-word Responsible AI Statement for the bank’s public website. This statement should: explain the bank’s commitments to customers; name specific rights customers have regarding AI decisions; describe the bank’s oversight process. Write in plain language that a non-technical customer can understand.

63.8 Further Reading

Barocas, S., Hardt, M., & Narayanan, A. (2023). Fairness and Machine Learning: Limitations and Opportunities. fairmlbook.org (open access).

Chouldechova, A. (2017). Fair prediction with disparate impact: A study of bias in recidivism prediction instruments. Big Data, 5(2), 153–163.

Dwork, C., & Roth, A. (2014). The Algorithmic Foundations of Differential Privacy. Foundations and Trends in Theoretical Computer Science, 9(3–4).

European Parliament. (2024). Artificial Intelligence Act. EUR-Lex.

Nigeria Data Protection Commission. (2023). Nigeria Data Protection Act 2023. ndpc.gov.ng.