APERTURE_AUDIT / generate_synthetic_data.py
Darkweb007's picture
Initial commit: model-agnostic SHAP/LIME explainability audit dashboard
41dfe78
Raw
History Blame Contribute Delete
2.73 kB
"""
Generates SYNTHETIC credit-application data with a KNOWN ground-truth
relationship between features and outcome. This is deliberate: because we
know the true generating weights, we can later verify that the model
recovers a sensible approximation of them, and that the explanation layer
(SHAP-style + LIME-style) correctly attributes the decision to the features
that actually drove it -- a property you can't verify against messy real
data. No real applicant, credit bureau, or financial data is used.
"""
import csv
import os
import random
random.seed(7)
N = 600
FEATURES = [
"annual_income_k", "debt_to_income_pct", "credit_history_years",
"num_late_payments_2y", "employment_years", "loan_amount_k", "num_open_accounts",
]
# True (synthetic) generating weights -- unknown to the model at training time,
# used only to label the data and later sanity-check what the model/explainer recovers.
TRUE_WEIGHTS = {
"annual_income_k": 0.028,
"debt_to_income_pct": -0.055,
"credit_history_years": 0.09,
"num_late_payments_2y": -0.65,
"employment_years": 0.05,
"loan_amount_k": -0.012,
"num_open_accounts": -0.04,
}
TRUE_INTERCEPT = -1.0
def sigmoid(x):
import math
return 1 / (1 + math.exp(-x))
def make_row():
income = max(18, random.gauss(65, 28))
dti = max(2, min(70, random.gauss(28, 12)))
history = max(0, random.gauss(9, 6))
late = max(0, int(random.gauss(1.1, 1.4)))
employment = max(0, random.gauss(6, 5))
loan = max(3, random.gauss(18, 12))
open_accts = max(0, int(random.gauss(5, 2.5)))
features = {
"annual_income_k": round(income, 1),
"debt_to_income_pct": round(dti, 1),
"credit_history_years": round(history, 1),
"num_late_payments_2y": late,
"employment_years": round(employment, 1),
"loan_amount_k": round(loan, 1),
"num_open_accounts": open_accts,
}
logit = TRUE_INTERCEPT + sum(TRUE_WEIGHTS[k] * v for k, v in features.items())
logit += random.gauss(0, 0.4) # irreducible noise
p_approve = sigmoid(logit)
approved = 1 if random.random() < p_approve else 0
features["approved"] = approved
return features
def main():
rows = [make_row() for _ in range(N)]
out_path = os.path.join(os.path.dirname(__file__), "data", "credit_applications.csv")
with open(out_path, "w", newline="") as f:
writer = csv.DictWriter(f, fieldnames=FEATURES + ["approved"])
writer.writeheader()
writer.writerows(rows)
approve_rate = sum(r["approved"] for r in rows) / len(rows)
print(f"Wrote {len(rows)} synthetic applications to {out_path} (approve rate: {approve_rate:.1%})")
if __name__ == "__main__":
main()