Credit risk portfolio

3,000 borrowers and 4,200 commercial loans, where a credit rating actually predicts whether the loan defaults, at the rate S&P's own data says it should.

A credit_score column and a defaulted column with no statistical relationship between them is worse than useless for validating a risk model. Here, PD comes from S&P Global Ratings' own published annual global corporate default rate (averaged across the 2019-2024 studies), LGD comes from the Basel Foundation IRB supervisory value (a number banks are required to use, not one they estimate), and EAD is drawn plus a credit-conversion-factor on undrawn commitment, the real Basel formula. Whether a loan actually defaults is a Bernoulli draw at its borrower's own PD, so AAA, AA, and A show exactly zero defaults, and CCC defaults at close to 28%, both measured directly from these files.

7,200 rows across 2 tables, 136 KB zipped. Public domain (CC0), no signup, no attribution required.

What is in it

borrowers3,000 rows

borrower_id, credit_rating, industry, pd

S&P letter grades, not a fictional 1-10 score

loans4,200 rows

loan_id, borrower_id, seniority, commitment_type, drawn_amount, undrawn_commitment, origination_date, lgd, ccf, ead, pd, defaulted, expected_loss, realized_loss

PD, LGD, EAD, and expected loss, all recomputable from the raw columns

What holds, and how it was checked

Each line was measured against these exact files rather than asserted. The same checks ship inside the zip as INTEGRITY.txt, so you can re-run them yourself.

  • AAA, AA, and A: 0 of 1,067 loans defaulted (S&P published PD: 0.00% for all three)
  • BBB: 2 of 1,261 defaulted, 0.16%, against a cited 0.03% PD
  • CCC: 88 of 314 defaulted, 28.03%, against a cited 26.92% PD
  • senior_unsecured LGD is exactly 40% on all 3,480 such loans, subordinated exactly 75% on all 720
  • ead equals drawn_amount + ccf x undrawn_commitment on all 4,200 loans
  • expected_loss equals pd x lgd x ead on all 4,200 loans
  • realized_loss is 0 on every non-defaulted loan, and equals lgd x ead on every defaulted one
  • 0 orphaned foreign keys joining loans to borrowers

Questions it can answer

  • Does realized default rate actually track PD, by rating grade?
  • What is the portfolio's total expected loss as a percentage of exposure?
  • How does expected loss concentrate across seniority and commitment type?
  • Which industries carry the riskiest rating mix?

Generate a custom version

This dataset is fixed. Different borrower counts, a different rating mix, or a different seed all come from the same underlying schema, so start on the canvas and shape the loan and borrower tables to what you need.

This dataset adds a reconcile step on top of the declared schema (PD/LGD/EAD lookups, a real Bernoulli default draw per loan), so it ships as a runnable script rather than a schema.yaml a bare misata generate could regenerate honestly.

pip install misata
curl -O https://raw.githubusercontent.com/rasinmuhammed/misata/main/examples/credit_risk_portfolio.py
python -c "from credit_risk_portfolio import build, verify; t = build(n_borrowers=3000, seed=11); verify(t)"
Read the docs

Other datasets

  • Retail star schema

    A dimensional model you can actually practise joins on: one fact table, four dimensions, 63,170 rows, and every join resolving.

  • Machine degradation

    100 machines monitored from commissioning to failure, with an exact remaining-useful-life label on every one of 23,118 readings.

  • Ecommerce storefront

    A year of orders across five joined tables, with a real Q4 peak and totals that reconcile to the cent.

  • B2B SaaS subscription analytics

    Accounts, seats, MRR, churn and support load, where company size actually drives the plan.

  • Network intrusion / netflow

    500 hosts and 20,000 labeled flows, where a port scan, a DDoS flood, and a brute-force attempt each carry the real statistical signature their MITRE ATT&CK technique actually leaves in traffic.

  • Contact center queueing (Erlang C)

    4 queues, 2,400 half-hour intervals over a full business month, where agents_staffed is what Erlang C (1917) says is actually required to clear each queue's own SLA target, not a plausible-looking number.

  • Manufacturing SPC (Cpk / Xbar-R)

    5 processes, 450 subgroups, 2,250 measurements, where process capability and out-of-control detection come from a real Xbar-R control chart -- including one process with an engineered tool-wear drift that actually trips Western Electric rules where it happens.