Analyse whether a government’s debt is on a sustainable path, using the same frameworks that the IMF and European Commission use in their country assessments.
# install.packages("devtools")
devtools::install_github("charlescoverdale/debtkit")library(debtkit)
# Project debt forward 10 years
proj <- dk_project(debt = 0.90, interest_rate = 0.04,
gdp_growth = 0.03, primary_balance = 0.01, horizon = 10)
proj
#> -- Debt Sustainability Projection --
#> * Horizon: 10 years
#> * Initial debt/GDP: 90%
#> * Terminal debt/GDP: 82.6%
#> * Debt-stabilising primary balance: 0.8%Every government borrows money. The central question in public finance is whether a government can keep servicing that debt, or whether the debt ratio will spiral upward until something breaks. Debt sustainability analysis (DSA) is the standard framework for answering this question. The IMF, the European Commission, finance ministries, and central banks all use it.
The core idea is simple. Next year’s debt-to-GDP ratio depends on four things: this year’s debt, the interest rate on that debt, how fast the economy is growing, and whether the government is running a surplus or deficit (excluding interest payments). When interest rates exceed GDP growth, debt snowballs. When growth exceeds interest rates, debt stabilises more easily.
In practice, a full DSA combines several analyses: deterministic
projections under baseline assumptions, historical decompositions of
what drove debt changes in the past, stress tests under adverse
scenarios, stochastic simulations that generate thousands of possible
paths, tests of whether the government historically responds to rising
debt by tightening policy, and calculations of how much fiscal
adjustment is needed to hit a target. All of these are well-established
methods, but tedious to code from scratch. debtkit packages
them into clean R functions with plotting methods, so you can go from
raw fiscal data to a complete assessment in a few lines of code.
Project debt forward under constant assumptions. The output tells you the terminal debt ratio and the primary balance that would stabilise debt at its current level.
library(debtkit)
proj <- dk_project(debt = 0.90, interest_rate = 0.04,
gdp_growth = 0.03, primary_balance = 0.01, horizon = 10)
proj
#> -- Debt Sustainability Projection --
#> * Horizon: 10 years
#> * Initial debt/GDP: 90%
#> * Terminal debt/GDP: 82.6%
#> * Debt-stabilising primary balance: 0.8%
plot(proj)Decompose historical debt movements into four components: interest costs pushing debt up, GDP growth pulling it down, the primary balance, and a residual (stock-flow adjustment).
d <- dk_sample_data()
decomp <- dk_decompose(d$debt, d$interest_rate, d$gdp_growth,
d$primary_balance, years = d$years)
decomp
#> -- Debt Decomposition --
#> * Periods: 19 (2005-2023)
#> * Cumulative change: 24 pp
#> * Interest effect: 52.5 pp
#> * Growth effect: -58.2 pp
#> * Primary balance: 14.4 pp
#> * Stock-flow adj.: 15.3 pp
plot(decomp) # Stacked bar chart of contributionsEstimate the joint distribution of macro shocks from historical data, then simulate 1,000 debt paths. The fan chart shows the 10th to 90th percentile bands.
d <- dk_sample_data()
shocks <- dk_estimate_shocks(d$gdp_growth, d$interest_rate, d$primary_balance)
fan <- dk_fan_chart(debt = 0.90, interest_rate = 0.035, gdp_growth = 0.03,
primary_balance = -0.01, shocks = shocks,
n_sim = 1000, horizon = 10, seed = 42)
fan
#> -- Stochastic Debt Fan Chart --
#> * Simulations: 1000
#> * Horizon: 10 years
#> * Initial debt: 90% of GDP
#> * Baseline terminal debt: 103.2% of GDP
plot(fan) # Fan chart with percentile bandsRun six standardised IMF stress-test scenarios: growth shock, interest rate shock, exchange rate shock, primary balance shock, a combined shock, and contingent liabilities materialising.
stress <- dk_stress_test(debt = 0.90, interest_rate = 0.04,
gdp_growth = 0.03, primary_balance = 0.01,
fx_share = 0.20)
stress
#> -- IMF Stress Test Scenarios --
#> * Horizon: 5 years
#> * Initial debt/GDP: 90%
#>
#> Terminal debt/GDP by scenario:
#> Baseline 85.5%
#> Growth shock 87.3%
#> Interest rate shock 93.9%
#> Exchange rate shock 88.2%
#> Primary balance shock 87.3%
#> Combined shock 89.8%
#> Contingent liabilities 94.8%
plot(stress)Estimate Bohn’s (1998) fiscal reaction function. A positive, statistically significant coefficient means the government systematically raises the primary surplus when debt rises, satisfying a sufficient condition for sustainability.
d <- dk_sample_data()
bohn <- dk_bohn_test(d$primary_balance, d$debt, robust_se = TRUE)
bohn
#> -- Bohn Fiscal Reaction Function --
#> * Method: ols (HAC)
#> * Observations: 20
#> * rho = 0.15 (SE = 0.06, p = 0.02)
#> v Sustainable: rho > 0 and significant at 5% level.Compute the European Commission’s S1 and S2 sustainability gap indicators. S1 measures the adjustment needed to reach 60% debt in 20 years. S2 measures the adjustment needed to stabilise debt over an infinite horizon, accounting for ageing costs.
dk_sustainability_gap(
debt = 0.90, structural_balance = -0.01,
gdp_growth = 0.015, interest_rate = 0.025, ageing_costs = 0.02
)
#> -- Sustainability Gap Indicators --
#> * Current debt/GDP: 90%
#> * Current structural PB: -1%
#>
#> -- S1 Indicator --
#> * Required PB adjustment: 2.8 pp
#> * Target debt/GDP: 60% in 20 years
#>
#> -- S2 Indicator --
#> * Required PB adjustment: 1.9 ppEvery function in debtkit takes the same four inputs,
all as decimals (0.90 = 90% of GDP):
| Input | What it is |
|---|---|
debt |
Debt-to-GDP ratio (0.90 = 90%) |
interest_rate |
Interest rate on government debt (0.04 = 4%) |
gdp_growth |
Nominal GDP growth (0.03 = 3%) |
primary_balance |
Revenue minus non-interest spending (0.01 = 1% surplus, negative = deficit) |
These are standard fiscal variables published by every major data provider. You can type them in directly, use the built-in sample data, or pull them from a data source.
If you know the numbers (from a budget document, a news article, or a textbook exercise), just type them:
library(debtkit)
# Italy-like scenario: high debt, low growth, small deficit
dk_project(debt = 1.40, interest_rate = 0.035, gdp_growth = 0.02,
primary_balance = 0.015, horizon = 10)The package includes sample datasets so you can try everything immediately:
d <- dk_sample_data()
str(d)
#> List of 5
#> $ years : int [1:20] 2004 2005 2006 ... 2023
#> $ debt : num [1:20] 0.45 0.44 0.42 ... 0.69
#> $ interest_rate : num [1:20] 0.045 0.043 0.042 ... 0.038
#> $ gdp_growth : num [1:20] 0.055 0.050 0.060 ... 0.040
#> $ primary_balance: num [1:20] 0.010 0.012 0.015 ... -0.005Here is a complete example using OECD data for any of 38 member countries:
# 1. Install the data package (one time)
install.packages("readoecd")
# 2. Download fiscal data for France
library(readoecd)
debt <- oecd_series("GGDEBT", "FRA") # General government debt (% of GDP)
growth <- oecd_series("NGDP_RPCH", "FRA") # Real GDP growth (%)
balance <- oecd_series("GGXONLB_NGDP", "FRA") # Primary balance (% of GDP)
# 3. Convert from percent to decimal and feed into debtkit
library(debtkit)
dk_project(
debt = tail(debt$value, 1) / 100,
interest_rate = 0.03, # Use latest effective rate estimate
gdp_growth = tail(growth$value, 1) / 100,
primary_balance = tail(balance$value, 1) / 100,
horizon = 10
)| Source | Coverage | Series you need | How to get into R |
|---|---|---|---|
| OECD | 38 countries | GGDEBT, NGDP_RPCH, GGXONLB_NGDP | readoecd |
| IMF WEO | 190+ countries | GGXWDG_NGDP, NGDP_RPCH, GGXONLB_NGDP | Download CSV from imf.org |
| FRED (US) | United States | GFDEGDQ188S, GDPC1, FYFSGDA188S | fred |
| Eurostat | EU members | gov_10dd_edpt1, nama_10_gdp | eurostat package |
| World Bank | 200+ countries | GC.DOD.TOTL.GD.ZS, NY.GDP.MKTP.KD.ZG | WDI package |
| Function | Description |
|---|---|
dk_project() |
Project debt-to-GDP paths forward |
dk_decompose() |
Decompose historical debt changes into interest, growth, primary balance, and stock-flow effects |
dk_rg() |
Interest rate-growth differential and debt-stabilising primary balance |
dk_bohn_test() |
Bohn fiscal reaction function (OLS, rolling, quadratic; optional HAC standard errors) |
dk_estimate_shocks() |
Estimate joint shock distributions (VAR, bootstrap, normal) |
dk_fan_chart() |
Stochastic debt fan charts via Monte Carlo simulation |
dk_stress_test() |
Six IMF standardised stress tests (fixed or data-driven calibration) |
dk_heat_map() |
IMF-style risk heat map with colour-coded ratings |
dk_gfn() |
Gross financing needs projection |
dk_sustainability_gap() |
European Commission S1/S2 sustainability gap indicators |
dk_compare() |
Side-by-side comparison of multiple projection scenarios |
dk_sample_data() |
Built-in sample fiscal datasets |
All objects returned by dk_project(),
dk_decompose(), dk_fan_chart(),
dk_stress_test(), dk_bohn_test(), and
dk_compare() have print(),
summary(), and plot() methods.
| Package | Description |
|---|---|
| yieldcurves | Yield curve fitting and analysis |
| inflationkit | Inflation analysis and core measures |
| fred | Federal Reserve Economic Data |
| readoecd | OECD data access |
Found a bug or have a feature request? Please open an issue on GitHub.
r, r-package, debt-sustainability, fiscal-policy, public-debt, macroeconomics, imf, sovereign-risk, fan-chart, stress-test