Where Bias Comes From
A lending dataset whose truth we know by construction
The remaining weeks are about the consequences of deploying the things the first ten built. This is not an appendix. Week 6's embedding learned who does which job, and week 10's model learned a stuck pixel, and both were doing exactly what they were built to do.
A lending dataset with a known history
import pandas as pd
rng = np.random.RandomState(0)
n = 6000
# Two groups, equally creditworthy on average. Group B has historically
# had less access to credit, so its members have shorter credit files
# and lower recorded balances. Nothing here is about ability to repay.
group = rng.choice(['A', 'B'], size=n, p=[0.7, 0.3])
is_b = group == 'B'
# the thing we would like to predict, generated the same way for both
repays = rng.rand(n) < 0.75
# observable features. credit history is systematically shorter for B
history = np.where(is_b, rng.normal(3, 1.6, n), rng.normal(7, 2.2, n))
history = np.clip(history, 0, None)
income = rng.normal(30, 8, n) + repays * 4
balance = np.where(is_b, rng.normal(900, 400, n),
rng.normal(2100, 700, n))
# the genuinely predictive signal, identical in both groups
missed = rng.poisson(np.where(repays, 0.4, 2.4))
df = pd.DataFrame({'history': history, 'income': income,
'balance': balance, 'missed': missed,
'group': group, 'repays': repays.astype(int)})
FEATURES = ['history', 'income', 'balance', 'missed']
from sklearn.model_selection import train_test_split
train, test = train_test_split(df, test_size=0.3, random_state=0,
stratify=df['repays'])
print(df.groupby('group')[FEATURES + ['repays']].mean().round(2))
print()
print('group sizes: %s' % df['group'].value_counts().to_dict())
print('the two groups repay at almost the same rate,')
print('and differ substantially on history and balance')
group
A 7.03 33.03 2087.57 0.89 0.76
B 3.04 32.93 897.57 0.90 0.75
group sizes: {'A': 4217, 'B': 1783}
the two groups repay at almost the same rate,
and differ substantially on history and balance
This data was constructed so that repayment is generated identically for both groups. Any difference in outcome the model produces is therefore not about who repays. It comes from the features, which record a history of unequal access rather than unequal reliability.
Why a synthetic dataset here
Because we get to know the truth. On real data you cannot separate the model is unfair from the groups genuinely differ, and that ambiguity is where most arguments about fairness stall. Here the answer is known by construction, so the measurements can be interpreted without argument.