Source code · Python

run.py

96 lines · Part of Credit risk lab · All code · Read only; the files run as published

"""Run the credit risk lab end to end and write the figures the website shows.     SQL     data_layer.py + sql/   load, check and segment the 30,000 accounts    Python  pd_model.py            WoE scorecard and validation    Python  portfolio_risk.py      expected loss, IRB capital, Monte Carlo, stress    R       crosscheck.R           independent refit and capital check Usage: python3 run.pyWrites app/lib/creditRisk.json in the website project."""import jsonimport subprocessfrom pathlib import Path import data_layerimport pd_modelimport portfolio_risk HERE = Path(__file__).resolve().parentOUT = HERE / "output"SITE_JSON = HERE.parents[1] / "app" / "lib" / "creditRisk.json"  def check(condition: bool, message: str) -> None:    if not condition:        raise SystemExit(f"check failed: {message}")  def rounded(value, digits=8):    """Round to significant figures, so tiny cross-check gaps stay visible."""    if isinstance(value, float):        return float(f"{value:.{digits}g}")    if isinstance(value, dict):        return {k: rounded(v, digits) for k, v in value.items()}    if isinstance(value, list):        return [rounded(v, digits) for v in value]    return value  def main() -> None:    OUT.mkdir(exist_ok=True)    db = OUT / "credit.db"    db.unlink(missing_ok=True)     con, quality, segments, features = data_layer.load(db)    check(len(features) == 30_000 and int(features["default_next_month"].sum()) == 6_636,          "row and default counts match the UCI documentation")     model = pd_model.run(features)    portfolio = portfolio_risk.run(features, model["pd"])     # Hand the same inputs to R and compare its answers with Python's.    model["model_data"].to_csv(OUT / "model_data.csv", index=False)    portfolio["account_level"].to_csv(OUT / "portfolio_accounts.csv", index=False)    subprocess.run(["Rscript", str(HERE / "crosscheck.R"), str(OUT)], check=True, capture_output=True)    r = json.loads((OUT / "r_crosscheck.json").read_text())     py_coefs = {c["term"]: c["coefficient"] for c in model["result"]["coefficients"]}    coef_gap = max(abs(py_coefs[t] - r["coefficients"][t]) for t in py_coefs)    auc_gap = abs(model["result"]["validation"]["test"]["auc"] - r["test_auc"])    el_gap = abs(portfolio["base"]["expected_loss"] - r["expected_loss"]) / r["expected_loss"]    capital_gap = abs(portfolio["base"]["irb_capital"] - r["irb_capital"]) / r["irb_capital"]    check(coef_gap < 1e-4 and auc_gap < 1e-6 and el_gap < 1e-9 and capital_gap < 1e-9,          "Python and R agree on coefficients, AUC, expected loss and capital")     portfolio.pop("account_level")    site = {        "source": {            "dataset": "Default of Credit Card Clients, UCI Machine Learning Repository (id 350)",            "citation": "Yeh, I. C., & Lien, C. H. (2009). Expert Systems with Applications, 36(2), 2473-2480.",            "doi": "https://doi.org/10.24432/C55S3H",            "license": "CC BY 4.0",            "period": "Repayment history April to September 2005; default measured in October 2005",            "region": "Taiwan",        },        "quality": quality.to_dict(orient="records"),        "segments": segments.to_dict(orient="records"),        "model": model["result"],        "portfolio": portfolio,        "r_crosscheck": {            "r_version": r["r_version"],            "max_coefficient_gap": coef_gap,            "test_auc_gap": auc_gap,            "max_pd_gap": r["max_pd_gap"],            "expected_loss_gap": el_gap,            "irb_capital_gap": capital_gap,        },    }    SITE_JSON.write_text(json.dumps(rounded(site), indent=1) + "\n")    print(f"wrote {SITE_JSON}")    print(f"test AUC {model['result']['validation']['test']['auc']:.4f} | R gap {auc_gap:.2e} | "          f"coefficient gap {coef_gap:.2e} | capital gap {capital_gap:.2e}")  if __name__ == "__main__":    main()