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.

  • credit risk
  • fintech
  • PD LGD EAD
  • Basel
  • risk modeling
0100200Jan 2022Jul 2022Jan 2023Jul 2023Jan 2024Jul 2024Jan 2025Jul 2025Nov 2025
Rows per month in loans.origination_date, counted from the file.

Explore

Every table, profiled

Each column's type, spread, empties and most common values, measured from the CSVs in the download. Switch to the first rows to see the data exactly as it sits in the file.

loans.csv

4,200 rows · 14 columns · 1 foreign keys

loan_idprimary key
unique on every row
4,200 distinctno empties
borrower_idforeign key
points to borrowers.borrower_id
2,255 distinctno empties
senioritycategory
  • senior_unsecured
    83%
  • subordinated
    17%
2 valuesno empties
commitment_typecategory
  • term_loan
    46%
  • long_term_revolving
    21%
  • short_term_revolving
    19%
  • unconditionally_cancellable
    14%
4 valuesno empties
drawn_amountnumber
50,00013.6M
mean 1.32Mmedian 714,61050,000 to 25Mno empties
undrawn_commitmentnumber
01.13M
mean 72,327median 9,507.20 to 3.57Mno empties
origination_datedate
Jan 2022Nov 2025
2022-01-01 to 2025-11-30no empties
lgdnumber
0.40.75
mean 0.46median 0.40.4 to 0.75no empties
ccfnumber
00.5
mean 0.14median 00 to 0.5no empties
eadnumber
50,72613.6M
mean 1.34Mmedian 736,80550,000 to 25Mno empties
pdnumber
00.27
mean 0.023median 0.00030 to 0.27no empties
defaultedyes / no
true · 2.4%false · 98%
no empties
expected_lossnumber
0428,018
mean 14,849median 219.40 to 4.05Mno empties
realized_lossnumber
0788,898
mean 18,064median 00 to 15Mno empties

Keys

Every join resolves

1 foreign key, 4,200 references checked against the table each one points at. None points at a row that does not exist.

loans

  • borrower_idborrowers.borrower_id0 orphans

Checks

What we checked

  • 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

The same checks ship in the zip as INTEGRITY.txt, so you can rerun them in any SQL engine. How we verify

Questions

Worth asking it

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

Use it

Take it, or make one shaped like yours

Rebuild it byte for byte

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)"

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.

Your own tables, your own numbers

Describe the data you need in Studio and get connected tables with the totals you state, profiled like this and checked before you download.

Premium datasets

All premium
  • Weekly sales, at regular price and on promotion

    Grocery chain, 10 stores, fiscal 2024 and 2025

    Two fiscal years of a grocery chain, from the shelf to the receipt

    Answer key

    • baseline_units
    • price_effect
    • promo_lift
    • +7 more
    Rows
    2,790,852
    Tables
    14
    Checks
    117 of 117
    Free previewProfile and preview
  • An X-bar chart with the truth underneath: flatness, characteristic 139

    3 plants, 36 CNC machines, six months

    Six months of control charts with the ground truth underneath

    Answer key

    • active_cause_id
    • true_cause_shift_sigma
    • true_tool_wear_sigma
    • +6 more
    Rows
    515,442
    Tables
    15
    Checks
    101 of 101
    Free previewProfile and preview
  • Vibration through a life that ends in failure

    240 machines at 4 plants, two years

    Two years of sensor readings, failures, repairs and costs for 240 machines

    Answer key

    • failure_mode
    • true_life_days
    • true_failure_at
    • +6 more
    Rows
    179,038
    Tables
    6
    Checks
    67 of 67
    Free previewProfile and preview

Related free datasets

All 12

Fully synthetic. No real person, company or transaction is represented, and no production data was read to make it. CC0: use it anywhere, no attribution needed.