L’objectif de cette note est d’analyser la dynamique de la dette publique (TDP/PIB) en France sur 1963–2023 et d’évaluer comment elle réagit à (i) la position budgétaire (solde primaire, SP) et (ii) l’écart entre taux d’intérêt et croissance \((r-g)\). La démarche combine : - une modélisation économétrique de correction d’erreur (ECM) centrée sur la dette/PIB, le SP et \((r-g)\) ; - des simulations de scénarios (chocs permanents ou temporaires sur SP et \((r-g)\)) ; - des lectures graphiques (ajusté vs observé, écarts au baseline, éventail d’incertitude, règle budgétaire).
L’enjeu est double : (a) quantifier les multiplicateurs de court et de long terme associés à des ajustements budgétaires ; (b) apprécier la sensibilité de la trajectoire de dette à la conjoncture financière (taux) et réelle (croissance).
knitr::opts_chunk$set(
echo = TRUE, message = FALSE, warning = FALSE,
fig.width = 7, fig.height = 4.5, fig.align = "center"
)
pkgs <- c(
"readxl","dplyr","stringr","tibble","ggplot2","tidyr",
"modelsummary","broom","zoo","rlang","lmtest","sandwich",
"systemfit","strucchange"
)
inst <- pkgs[!pkgs %in% installed.packages()[,1]]
if (length(inst)) install.packages(inst, dep = TRUE)
invisible(lapply(pkgs, require, character.only = TRUE))
# Helpers
L <- function(x,k=1) dplyr::lag(x,k)
D1 <- function(x) x - L(x)
safe_log <- function(x, eps = .Machine$double.eps) log(pmax(x, eps))
pick <- function(pats, nms) {
for (p in pats) {
h <- grep(p, nms, value=TRUE, ignore.case=TRUE)
if (length(h)) return(h[1])
}
NA_character_
}
# Essaie automatiquement plusieurs chemins; sinon file.choose()
candidates <- c(
"C:/Users/33753/Downloads/DATA1963-2023 (1).xls",
"C:/Users/33753/Downloads/DATA1963-2023 (1).xlsx",
"C:/Users/33753/Downloads/DATA1963-2023.xls",
"C:/Users/33753/Downloads/DATA1963-2023.xlsx",
"C:/Users/33753/OneDrive/Téléchargements/DATA1963-2023 (1).xls",
"C:/Users/33753/OneDrive/Téléchargements/DATA1963-2023 (1).xlsx"
)
candidates <- candidates[file.exists(candidates)]
if (!length(candidates)) {
message("Sélectionne le fichier Excel…")
data_path <- file.choose()
} else data_path <- candidates[1]
sheets <- readxl::excel_sheets(data_path)
df_raw <- readxl::read_excel(data_path, sheet = sheets[1])
cat("Fichier:", normalizePath(data_path), "\nFeuille:", sheets[1], "\n")
## Fichier: C:\Users\33753\Downloads\DATA1963-2023 (1).xlsx
## Feuille: DATATRIMESTRIELLE
Lecture : un coefficient négatif sur \(L(\text{debt})\) indique un mécanisme de rappel vers une trajectoire d’équilibre (stabilité). Un coefficient négatif sur \(SP\) signifie qu’un effort budgétaire réduit la dette/PIB.
nms <- names(df_raw)
pick <- function(pats, nms) {
for (p in pats) {
h <- grep(p, nms, value = TRUE, ignore.case = TRUE)
if (length(h)) return(h[1])
}
NA_character_
}
col_py <- pick(c("^py$"), nms)
col_pc <- pick(c("^pc$"), nms)
col_pinv <- pick(c("^pinv$"), nms)
col_pm <- pick(c("^pm$"), nms)
col_px <- pick(c("^px$"), nms)
col_ypy <- pick(c("^ypy$"), nms)
col_cpc <- pick(c("^cpc$"), nms)
col_invp <- pick(c("^invp$"), nms)
col_mpm <- pick(c("^mpm$"), nms)
col_xpx <- pick(c("^xpx$"), nms)
col_w <- pick(c("^w$"), nms)
col_h <- pick(c("^h$"), nms)
col_l <- pick(c("^l$"), nms)
col_gpg <- pick(c("^gpg$|^g$"), nms)
col_pg <- pick(c("^pg$"), nms)
col_pdi <- pick(c("^pdi$"), nms)
col_rd <- pick(c("^rd$"), nms)
col_tcot <- pick(c("^tcot$"), nms)
col_tpres <- pick(c("^tpres$"), nms)
col_k <- pick(c("^k$"), nms)
col_tr <- pick(c("^tr$"), nms)
col_tinfl <- pick(c("^tinfl$"), nms)
col_qepqe <- pick(c("^qepqe$"), nms)
col_pqe <- pick(c("^pqe$"), nms)
# >>>>> NOUVEAU : dette publique (TDP) et solde primaire (si présent)
col_debt <- pick(c("^tdp$","^tdp\\b","dette.*publique","\\bdebt\\b"), nms) # <- TDP
col_sp <- pick(c("^sp$","solde.*primaire","^s_?prim","^pb$"), nms) # optionnel
need <- c(col_py,col_pc,col_pinv,col_pm,col_px,col_ypy,col_cpc,col_invp,col_mpm,
col_xpx,col_w,col_h,col_l,col_gpg,col_pg,col_pdi,col_rd,col_tcot,
col_tpres,col_k,col_tr,col_tinfl,col_qepqe,col_pqe)
if (any(is.na(need))) {
stop("Colonnes manquantes. Ajuste les patterns si besoin.")
}
# Vecteurs pré-calculés pour injection sans !!sym()
debt_vec <- if (!is.na(col_debt)) as.numeric(df_raw[[col_debt]]) else rep(NA_real_, nrow(df_raw))
sp_vec <- if (!is.na(col_sp)) as.numeric(df_raw[[col_sp]]) else rep(NA_real_, nrow(df_raw))
safe_log <- function(x, eps = .Machine$double.eps) log(pmax(as.numeric(x), eps))
D <- df_raw |>
dplyr::transmute(
py = !!rlang::sym(col_py),
pc = !!rlang::sym(col_pc),
pinv = !!rlang::sym(col_pinv),
pm = !!rlang::sym(col_pm),
px = !!rlang::sym(col_px),
ypy = !!rlang::sym(col_ypy),
cpc = !!rlang::sym(col_cpc),
invp = !!rlang::sym(col_invp),
mpm = !!rlang::sym(col_mpm),
xpx = !!rlang::sym(col_xpx),
w = !!rlang::sym(col_w),
h = !!rlang::sym(col_h),
l = !!rlang::sym(col_l),
gpg = !!rlang::sym(col_gpg),
pg = !!rlang::sym(col_pg),
pdi = !!rlang::sym(col_pdi),
rd = !!rlang::sym(col_rd),
tcot = !!rlang::sym(col_tcot),
tpres= !!rlang::sym(col_tpres),
k = !!rlang::sym(col_k),
tr = !!rlang::sym(col_tr),
tinfl= !!rlang::sym(col_tinfl),
qepqe= !!rlang::sym(col_qepqe),
pqe = !!rlang::sym(col_pqe),
# >>>>> ICI on injecte les vecteurs pré-calculés
debt = debt_vec, # TDP (% du PIB) — INSEE
sp = sp_vec # solde primaire (% du PIB), si présent
) |>
dplyr::mutate(
# logs de base
lpy = safe_log(py), lpc= safe_log(pc), lpin = safe_log(pinv),
lpm = safe_log(pm), lpx= safe_log(px), lypy = safe_log(ypy),
lcpc = safe_log(cpc), linvp = safe_log(invp), lmpm = safe_log(mpm),
lxpx = safe_log(xpx), lw = safe_log(w), lh = safe_log(h),
ln = safe_log(l), lgpg = safe_log(gpg), lpg = safe_log(pg),
lpdi = safe_log(pdi), lrd = safe_log(rd), ltcot = safe_log(tcot),
lupres = log(pmax(1 - tpres/1000, .Machine$double.eps)),
lucot = log(pmax(1 - tcot/1000, .Machine$double.eps)),
# composés
lcsu = lw + ln + lh - lypy - lucot,
lpypm = lpy - lpm,
lpinpy = lpin - lpy,
bc = px * xpx - pm * mpm,
lnk = ln - safe_log(k),
a = ypy / pmax(l, .Machine$double.eps),
la = safe_log(a),
lac = la - ltcot,
lwpc = lw - lpc,
lwtpy = lw + ltcot - lpy,
lwta = lw + ltcot - la,
lpxpqe = lpx - safe_log(pqe)
)
# ----- Fonctions lag/diff si pas déjà dans le doc
L <- function(x, k=1) dplyr::lag(x, k)
D1 <- function(x) x - L(x)
has_debt <- any(is.finite(D$debt))
if (has_debt) {
message("Module Dette: colonne TDP détectée -> estimation ECM.")
# On suppose TDP déjà en % du PIB (niveau 0–100). Sinon, adapte.
D$debt_gdp <- as.numeric(D$debt)
# inflation attendue (pp) et coût réel approximatif r - g
# r nominal prox: tr/100 ; g réel prox: Δlog(YPY)
D <- D |>
mutate(
dlpy = c(NA, diff(lpy)),
pp = 0.2*dlpy + 0.4*L(dlpy,1) + 0.2*L(dlpy,2) + 0.1*L(dlpy,3) + 0.1*L(dlpy,4),
r_real = (tr/100) - pmax(pp, 0),
g = pmax(c(NA, diff(lypy)), 0),
d_debt = D1(debt_gdp),
r_minus_g = r_real - g
)
debt_df <- D |>
select(debt_gdp, d_debt, r_minus_g, sp) |>
filter(row_number() > 8)
# ECM minimal (avec solde primaire s si dispo)
if (any(is.finite(debt_df$sp))) {
ecm_debt <- lm(d_debt ~ L(debt_gdp) + r_minus_g + sp, data = debt_df)
} else {
ecm_debt <- lm(d_debt ~ L(debt_gdp) + r_minus_g, data = debt_df)
}
print(summary(ecm_debt))
# Observé vs ajusté (Δdette)
mf <- model.frame(ecm_debt)
aug <- broom::augment(ecm_debt)
y <- as.numeric(model.response(mf))
ggplot(tibble::tibble(t=seq_len(nrow(mf)), y=y, yhat=aug$.fitted), aes(t)) +
geom_line(aes(y=y), linewidth=.5) +
geom_line(aes(y=yhat), linetype="dashed") +
labs(title="Δ Dette/PIB — observé vs ajusté (ECM)", x=NULL, y=NULL)
# Scénarios stylisés sur 20 trimestres
simulate_debt <- function(d0, T=20, r_m_g=0.01, s=0){
d <- numeric(T+1); d[1] <- d0
for(t in 2:(T+1)){
d[t] <- d[t-1] + r_m_g*d[t-1] - s
}
tibble::tibble(t=0:T, debt=d)
}
d0 <- tail(na.omit(D$debt_gdp),1)
s0 <- if (any(is.finite(D$sp))) mean(tail(na.omit(D$sp),12)) else 0
rmg0 <- mean(tail(na.omit(D$r_minus_g),12))
simD_base <- simulate_debt(d0, T=20, r_m_g=rmg0, s=s0) |> dplyr::mutate(scn="Base")
simD_hi <- simulate_debt(d0, T=20, r_m_g=rmg0+0.01, s=s0) |> dplyr::mutate(scn="r-g +1 pt")
simD_lo <- simulate_debt(d0, T=20, r_m_g=max(rmg0-0.01,0), s=s0) |> dplyr::mutate(scn="r-g –1 pt")
simD_pb <- simulate_debt(d0, T=20, r_m_g=rmg0, s=s0+0.01) |> dplyr::mutate(scn="s +1 pt PIB")
dplyr::bind_rows(simD_base, simD_hi, simD_lo, simD_pb) |>
ggplot2::ggplot(ggplot2::aes(t, debt, color=scn)) +
ggplot2::geom_line() +
ggplot2::labs(title="Dette/PIB — scénarios stylisés (20 trimestres)",
x="Horizon (t)", y="Dette / PIB", color=NULL)
} else {
message("Module Dette: TDP non trouvé — vérifie le nom exact de la colonne dans Excel.")
}
##
## Call:
## lm(formula = d_debt ~ L(debt_gdp) + r_minus_g + sp, data = debt_df)
##
## Residuals:
## Min 1Q Median 3Q Max
## -5.2508 -0.5208 -0.0517 0.4936 8.9674
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 0.378449 0.384996 0.983 0.327
## L(debt_gdp) -0.025085 0.006195 -4.049 7.05e-05 ***
## r_minus_g 2.591337 3.503694 0.740 0.460
## sp -0.026005 0.003197 -8.135 2.65e-14 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 1.182 on 228 degrees of freedom
## (1 observation effacée parce que manquante)
## Multiple R-squared: 0.24, Adjusted R-squared: 0.23
## F-statistic: 24 on 3 and 228 DF, p-value: 1.557e-13
Le traçage « observé vs ajusté » montre que l’ECM capte correctement les grandes phases de montée et de stabilisation de la dette. Les écarts résiduels signalent des chocs non modélisés (ex. mesures exceptionnelles, opérations de capital, révisions statistiques), mais la tendance et l’amplitude des mouvements sont bien reproduites.
D <- D |>
mutate(
time = row_number(),
dlpy = c(NA, diff(lpy)),
pp = 0.2*dlpy + 0.4*L(dlpy,1) + 0.2*L(dlpy,2) + 0.1*L(dlpy,3) + 0.1*L(dlpy,4),
dt = 0.02 + 0.1*(0.2*lpypm + 0.4*L(lpypm,1) + 0.2*L(lpypm,2) + 0.1*L(lpypm,3) + 0.1*L(lpypm,4)),
# coût d’usage
lrpd = safe_log(tr - 100*pp + 100*dt),
lvc = lpinpy + lrpd,
lwlvc= lw + lh - lucot - lvc - lpy,
ik = invp / L(k,1),
lik = safe_log(ik),
# variations récurrentes
dlypy = safe_log(ypy) - safe_log(L(ypy)),
ddlpy = dlypy - L(dlypy),
dlpinpy = D1(lpinpy),
dlrpd = D1(lrpd),
dlcpc = safe_log(cpc) - safe_log(L(cpc)),
dlrd = D1(lrd),
lqepqe = safe_log(qepqe),
dlqepqe = D1(lqepqe),
dlpxpqe = D1(lpxpqe)
)
# ----- Fonctions lag/diff si pas déjà dans le doc
L <- function(x, k=1) dplyr::lag(x, k)
D1 <- function(x) x - L(x)
has_debt <- any(is.finite(D$debt))
if (has_debt) {
message("Module Dette: colonne TDP détectée -> estimation ECM.")
# On suppose TDP déjà en % du PIB (niveau 0–100). Sinon, adapte.
D$debt_gdp <- as.numeric(D$debt)
# inflation attendue (pp) et coût réel approximatif r - g
# r nominal prox: tr/100 ; g réel prox: Δlog(YPY)
D <- D |>
mutate(
dlpy = c(NA, diff(lpy)),
pp = 0.2*dlpy + 0.4*L(dlpy,1) + 0.2*L(dlpy,2) + 0.1*L(dlpy,3) + 0.1*L(dlpy,4),
r_real = (tr/100) - pmax(pp, 0),
g = pmax(c(NA, diff(lypy)), 0),
d_debt = D1(debt_gdp),
r_minus_g = r_real - g
)
debt_df <- D |>
select(debt_gdp, d_debt, r_minus_g, sp) |>
filter(row_number() > 8)
# ECM minimal (avec solde primaire s si dispo)
if (any(is.finite(debt_df$sp))) {
ecm_debt <- lm(d_debt ~ L(debt_gdp) + r_minus_g + sp, data = debt_df)
} else {
ecm_debt <- lm(d_debt ~ L(debt_gdp) + r_minus_g, data = debt_df)
}
print(summary(ecm_debt))
# Observé vs ajusté (Δdette)
mf <- model.frame(ecm_debt)
aug <- broom::augment(ecm_debt)
y <- as.numeric(model.response(mf))
ggplot(tibble::tibble(t=seq_len(nrow(mf)), y=y, yhat=aug$.fitted), aes(t)) +
geom_line(aes(y=y), linewidth=.5) +
geom_line(aes(y=yhat), linetype="dashed") +
labs(title="Δ Dette/PIB — observé vs ajusté (ECM)", x=NULL, y=NULL)
# Scénarios stylisés sur 20 trimestres
simulate_debt <- function(d0, T=20, r_m_g=0.01, s=0){
d <- numeric(T+1); d[1] <- d0
for(t in 2:(T+1)){
d[t] <- d[t-1] + r_m_g*d[t-1] - s
}
tibble::tibble(t=0:T, debt=d)
}
d0 <- tail(na.omit(D$debt_gdp),1)
s0 <- if (any(is.finite(D$sp))) mean(tail(na.omit(D$sp),12)) else 0
rmg0 <- mean(tail(na.omit(D$r_minus_g),12))
simD_base <- simulate_debt(d0, T=20, r_m_g=rmg0, s=s0) |> dplyr::mutate(scn="Base")
simD_hi <- simulate_debt(d0, T=20, r_m_g=rmg0+0.01, s=s0) |> dplyr::mutate(scn="r-g +1 pt")
simD_lo <- simulate_debt(d0, T=20, r_m_g=max(rmg0-0.01,0), s=s0) |> dplyr::mutate(scn="r-g –1 pt")
simD_pb <- simulate_debt(d0, T=20, r_m_g=rmg0, s=s0+0.01) |> dplyr::mutate(scn="s +1 pt PIB")
dplyr::bind_rows(simD_base, simD_hi, simD_lo, simD_pb) |>
ggplot2::ggplot(ggplot2::aes(t, debt, color=scn)) +
ggplot2::geom_line() +
ggplot2::labs(title="Dette/PIB — scénarios stylisés (20 trimestres)",
x="Horizon (t)", y="Dette / PIB", color=NULL)
} else {
message("Module Dette: TDP non trouvé — vérifie le nom exact de la colonne dans Excel.")
}
##
## Call:
## lm(formula = d_debt ~ L(debt_gdp) + r_minus_g + sp, data = debt_df)
##
## Residuals:
## Min 1Q Median 3Q Max
## -5.2508 -0.5208 -0.0517 0.4936 8.9674
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 0.378449 0.384996 0.983 0.327
## L(debt_gdp) -0.025085 0.006195 -4.049 7.05e-05 ***
## r_minus_g 2.591337 3.503694 0.740 0.460
## sp -0.026005 0.003197 -8.135 2.65e-14 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 1.182 on 228 degrees of freedom
## (1 observation effacée parce que manquante)
## Multiple R-squared: 0.24, Adjusted R-squared: 0.23
## F-statistic: 24 on 3 and 228 DF, p-value: 1.557e-13
Court terme (CT).
- Un choc unitaire de SP (ex. +1 point de PIB) réduit
immédiatement la variation de dette/PIB (coefficient
sur \(\Delta SP_t\) négatif).
- Un choc de \((r-g)\)
(ex. +1 pp/an) augmente immédiatement la variation de
dette/PIB (coefficient sur \(\Delta
(r-g)_t\) positif).
Long terme (LT).
- Le ratio dette/PIB réagit de manière amplifiée à un
ajustement budgétaire durable : la
semi-élasticité de LT (issue du ratio \(-\beta_{SP}/\lambda\)) indique de combien
de points la dette se modifie pour 1 point de PIB
d’ajustement permanent.
- À l’inverse, une augmentation durable de \((r-g)\) accroît la dette/PIB selon la
semi-élasticité \(-\beta_{r-g}/\lambda\).
- Le signe négatif du coefficient d’ajustement sur \(L(\text{debt})\) confirme un retour
vers l’équilibre (processus de correction d’erreur).
Lecture synthétique.
- Le modèle met en évidence une forte inertie de la
dette, mais des effets cumulatifs substantiels des
politiques primaires.
- Les conditions financières et de croissance, via \((r-g)\), demeurent un déterminant
majeur de la soutenabilité.
# ===== Simulateur robuste de trajectoires de dette (ECM) =====
stopifnot(exists("ecm_debt"), exists("debt_df"), exists("D"))
# Helpers robustes
`%||%` <- function(x, y) if (is.null(x) || length(x)==0 || all(is.na(x))) y else x
coef_get <- function(cf, keys, default = 0){
# 1) clé exacte
for(k in keys) if (k %in% names(cf)) return(unname(cf[[k]]))
# 2) regex
for(k in keys){
hit <- grep(k, names(cf))
if (length(hit)) return(unname(cf[[ hit[1] ]]))
}
default
}
# Détecte quelle métrique de (r−g) le modèle utilise
uses_rmg_pp <- function(cf_names){
any(grepl("^Lrmg_pp_a$", cf_names)) || any(grepl("^drmg_pp_a$", cf_names))
}
# -------- Simulateur (choisit automatiquement l’échelle de r−g) --------
simulate_debt_paths <- function(T=20,
shock = list(
type = "none", # "none" | "permanent" | "temp"
d_sp = 0.00, # SP en points de PIB : +1.0 = +1 pt
d_rmg = 0.00, # si modèle en pp/an -> +1.0 = +1 pp/an
# sinon en décimal trimestriel -> +0.0025 ≈ +1 pp/an
start = 1,
len = 4
)) {
cf <- coef(ecm_debt)
# Quelle échelle pour r-g ?
rmg_is_pp <- uses_rmg_pp(names(cf))
rmg_base <- if (rmg_is_pp) D$rmg_pp_a else D$r_minus_g
stopifnot(any(is.finite(rmg_base)))
# États initiaux
d0 <- tail(na.omit(D$debt), 1)
r0 <- tail(na.omit(rmg_base), 1)
sp0 <- if (any(is.finite(D$sp))) tail(na.omit(D$sp), 1) else 0
# Séries exogènes projetées
r_path <- rep(r0, T)
sp_path<- rep(sp0, T)
stype <- shock$type %||% "none"
if (stype == "permanent") {
r_path <- r_path + (shock$d_rmg %||% 0)
sp_path<- sp_path + (shock$d_sp %||% 0)
} else if (stype == "temp") {
s <- shock$start %||% 1
L <- shock$len %||% 4
if (L > 0) {
idx <- seq.int(s, min(T, s+L-1))
r_path[idx] <- r_path[idx] + (shock$d_rmg %||% 0)
sp_path[idx]<- sp_path[idx] + (shock$d_sp %||% 0)
}
}
# Coefficients (robustes aux noms)
c0 <- coef_get(cf, c("(Intercept)"))
cLdebt <- coef_get(cf, c("^Ldebt$", "L\\(debt\\)$", "L\\(debt, ?1\\)$",
"^Ldebt2$", "L\\(debt, ?2\\)$"))
cLrmg <- if (rmg_is_pp) coef_get(cf, c("^Lrmg_pp_a$")) else coef_get(cf, c("^Lrmg$"))
cdrmg <- if (rmg_is_pp) coef_get(cf, c("^drmg_pp_a$")) else coef_get(cf, c("^drmg$"))
cddebt1 <- coef_get(cf, c("^d_debt_1$", "L\\(d_debt_1\\)$"))
cLsp <- coef_get(cf, c("^Lsp$"))
cdsp <- coef_get(cf, c("^dsp$"))
# Lags initiaux
d_debt_lag <- tail(na.omit(debt_df$d_debt), 1) %||% 0
r_lag <- r0
sp_lag <- sp0
# Boucle
debt <- numeric(T+1); debt[1] <- d0
for (t in 1:T) {
dr_t <- if (t==1) (r_path[1]-r_lag) else (r_path[t]-r_path[t-1])
dsp_t <- if (t==1) (sp_path[1]-sp_lag) else (sp_path[t]-sp_path[t-1])
d_debt_t <- c0 +
cLdebt * debt[t] +
cLrmg * (if (t==1) r_lag else r_path[t-1]) +
cdrmg * dr_t +
cddebt1 * d_debt_lag +
cLsp * (if (t==1) sp_lag else sp_path[t-1]) +
cdsp * dsp_t
debt[t+1] <- debt[t] + d_debt_t
d_debt_lag <- d_debt_t
}
tibble::tibble(t=0:T, debt=debt)
}
# -------- Scénarios (20 trimestres) --------
Tsim <- 20
sim_baseD <- simulate_debt_paths(T=Tsim, shock=list(type="none"))
# Si ton ECM est en pp/an (cf noms Lrmg_pp_a/drmg_pp_a), alors d_rmg=+1.0 = +1 pp/an.
# Sinon (décimal trimestriel), utilise d_rmg=+0.0025 pour ≈ +1 pp/an.
rmg_bump <- if (uses_rmg_pp(names(coef(ecm_debt)))) +1.0 else +0.0025
sim_spP1 <- simulate_debt_paths(T=Tsim, shock=list(type="permanent", d_sp=+1.0))
sim_spM1 <- simulate_debt_paths(T=Tsim, shock=list(type="permanent", d_sp=-1.0))
sim_rmgP1 <- simulate_debt_paths(T=Tsim, shock=list(type="permanent", d_rmg=rmg_bump))
sim_spTemp <- simulate_debt_paths(T=Tsim, shock=list(type="temp", d_sp=+1.0, start=1, len=4))
# Agrégat + tracés
libra <- dplyr::bind_rows(
sim_baseD |> dplyr::mutate(scn="Baseline"),
sim_spP1 |> dplyr::mutate(scn="SP +1 pt (perm)"),
sim_spM1 |> dplyr::mutate(scn="SP −1 pt (perm)"),
sim_rmgP1 |> dplyr::mutate(scn="(r−g) +1 (selon échelle du modèle)"),
sim_spTemp |> dplyr::mutate(scn="SP +1 pt (4 tr)")
)
# Trajectoires
ggplot(libra, aes(t, debt, color=scn)) +
geom_line() +
labs(title="Dette/PIB — trajectoires simulées (ECM, 20 trimestres)",
x="Horizon (t)", y="Dette / PIB", color=NULL)
# Écarts au baseline
base_only <- sim_baseD |> dplyr::select(t, debt_base = debt)
libra_delta <- libra |> dplyr::left_join(base_only, by="t") |>
dplyr::mutate(delta = debt - debt_base) |>
dplyr::filter(scn != "Baseline")
ggplot(libra_delta, aes(t, delta)) +
geom_hline(yintercept=0, color="grey70") +
geom_line() +
facet_wrap(~scn, ncol=2, scales="free_y") +
labs(title="Dette/PIB — écarts au baseline", x="Horizon (t)", y="Δ dette (pts)")
# ====== MODULE DETTE (TDP) ======
# A coller après la création de D (et avant les visus finaux)
suppressWarnings({
if (!requireNamespace("dplyr", quietly=TRUE)) install.packages("dplyr")
if (!requireNamespace("broom", quietly=TRUE)) install.packages("broom")
if (!requireNamespace("ggplot2",quietly=TRUE)) install.packages("ggplot2")
})
library(dplyr); library(broom); library(ggplot2)
`%||%` <- function(x, y) if (is.null(x) || length(x)==0 || all(is.na(x))) y else x
L <- function(x, k=1) dplyr::lag(x, k)
D1 <- function(x) x - L(x)
# -- 0) Mapping robuste: retrouve TDP (dette/PIB) et SP (solde primaire) dans df_raw ----
normalize_names <- function(x) gsub("[^[:alnum:]]+", "", toupper(x))
pick1 <- function(cands, nms){
n1 <- normalize_names(nms); c1 <- normalize_names(cands)
hit <- which(n1 %in% c1)
if (length(hit)) return(nms[hit[1]])
h2 <- grep(paste(cands, collapse="|"), nms, ignore.case=TRUE, value=TRUE)
if (length(h2)) h2[1] else NA_character_
}
stopifnot(exists("df_raw"), exists("D")) # on suppose df_raw et D déjà créés
nms <- names(df_raw)
# IMPORTANT: ici on inclut "TDP" explicitement
col_debt <- pick1(c("TDP","DETTEPUBLIQUE","DETTE_PIB","DETTE","DETTETOT"), nms)
col_sp <- pick1(c("SP","SOLDEPRIMAIRE","SOLDE_PRIMAIRE"), nms)
has_debt <- !is.na(col_debt) && col_debt %in% names(df_raw)
if (!has_debt) {
message("Module Dette: colonne TDP (dette/PIB) introuvable → section sautée.")
} else {
# -- 1) S'assure que la croissance réelle existe (dlypy) et un proxy d'inflation attendue (pp) ----
if (!("dlypy" %in% names(D))) {
D <- D %>% mutate(dlypy = log(ypy) - log(L(ypy)))
}
if (!("pp" %in% names(D))) {
if (!("lpy" %in% names(D))) D <- D %>% mutate(lpy = log(py))
D <- D %>% mutate(
dlpy = c(NA, diff(lpy)),
pp = 0.2*dlpy + 0.4*L(dlpy,1) + 0.2*L(dlpy,2) + 0.1*L(dlpy,3) + 0.1*L(dlpy,4)
)
}
# -- 2) Construit les variables dette & r-g ----
has_tr <- "tr" %in% names(D) || "TR" %in% names(D)
if (!has_tr) message("⚠️ Série 'tr' (taux nominal) absente: r_nom mis à 0 (proxy).")
D <- D %>%
mutate(
debt = as.numeric(df_raw[[col_debt]]), # TDP (pts de PIB)
sp = if (!is.na(col_sp)) as.numeric(df_raw[[col_sp]]) else NA_real_,
r_nom = if ("tr" %in% names(D)) tr/100 else if ("TR" %in% names(D)) TR/100 else 0,
r_real = r_nom - pmax(pp, 0), # ~ r - π^e
g = dlypy, # croissance réelle
r_minus_g = r_real - g
)
# -- 3) Jeu de données ECM (on jette 8 1ers points pour lags) ----
debt_df <- D %>%
transmute(
debt,
d_debt = D1(debt),
Ldebt = L(debt),
rmg = r_minus_g,
Lrmg = L(r_minus_g),
drmg = D1(r_minus_g),
sp = sp,
Lsp = L(sp),
dsp = D1(sp),
d_debt_1 = L(D1(debt))
) %>%
filter(row_number() > 8)
use_sp <- any(is.finite(debt_df$sp))
# -- 4) Estimation ECM (avec ou sans solde primaire) ----
form_debt_full <- d_debt ~ Ldebt + Lrmg + drmg + d_debt_1 + Lsp + dsp
form_debt_nosp <- d_debt ~ Ldebt + Lrmg + drmg + d_debt_1
ecm_debt <- lm(if (use_sp) form_debt_full else form_debt_nosp,
data = debt_df, na.action = na.omit)
cat("\n=== ECM Dette (ΔTDP) — résumé ===\n"); print(summary(ecm_debt))
# -- 5) Multiplicateurs LT (approx) + impacts CT ----
cf <- coef(ecm_debt)
lambda <- cf[["Ldebt"]] %||% 0
beta_r <- cf[["Lrmg"]] %||% NA_real_
beta_s <- if (use_sp) (cf[["Lsp"]] %||% NA_real_) else NA_real_
if (is.finite(beta_s) && lambda!=0)
cat(sprintf("\nLong terme: Δdette ≈ %.3f × Δ(solde primaire)\n", -beta_s/lambda))
if (is.finite(beta_r) && lambda!=0)
cat(sprintf("Long terme: Δdette ≈ %.3f × Δ(r - g)\n", -beta_r/lambda))
if ("dsp" %in% names(cf)) cat(sprintf("Impact (CT) d’un Δsp_t : %.3f\n", cf[["dsp"]]))
if ("drmg" %in% names(cf)) cat(sprintf("Impact (CT) d’un Δ(r-g)_t : %.3f\n", cf[["drmg"]]))
# -- 6) Observé vs Ajusté (Δdette) ----
mf <- model.frame(ecm_debt)
aug <- augment(ecm_debt)
y <- as.numeric(model.response(mf))
ggplot(tibble::tibble(t=seq_len(nrow(mf)), y=y, yhat=aug$.fitted), aes(t)) +
geom_line(aes(y=y), linewidth=.5) +
geom_line(aes(y=yhat), linetype="dashed") +
labs(title="Dette — Δobservé vs Δajusté (ECM)", x=NULL, y=NULL)
# -- 7) Simulateur de trajectoires (corrigé pour éviter l’erreur de longueur nulle) ----
simulate_debt_paths <- function(T=20,
shock = list(
type = "none", # "none", "permanent", "temp"
d_sp = 0.00, # +0.01 = +1 pt de PIB
d_rmg = 0.00, # +0.0025 ≈ +1 pt/an
start = 1,
len = 4
)) {
stype <- shock$type %||% "none"
d0 <- tail(na.omit(D$debt), 1)
rmg0 <- tail(na.omit(D$r_minus_g), 1)
sp0 <- if (use_sp) tail(na.omit(D$sp), 1) else 0
rmg <- rep(rmg0, T); sp <- rep(sp0, T)
if (stype == "permanent") {
rmg <- rmg + (shock$d_rmg %||% 0)
sp <- sp + (shock$d_sp %||% 0)
} else if (stype == "temp") {
start <- shock$start %||% 1
len <- shock$len %||% 4
if (len > 0) {
idx <- seq.int(start, min(T, start+len-1))
rmg[idx] <- rmg[idx] + (shock$d_rmg %||% 0)
sp[idx] <- sp[idx] + (shock$d_sp %||% 0)
}
}
c0 <- cf[["(Intercept)"]] %||% 0
cLdebt <- cf[["Ldebt"]] %||% 0
cLrmg <- cf[["Lrmg"]] %||% 0
cdrmg <- cf[["drmg"]] %||% 0
cddebt1 <- cf[["d_debt_1"]] %||% 0
cLsp <- if (use_sp) (cf[["Lsp"]] %||% 0) else 0
cdsp <- if (use_sp) (cf[["dsp"]] %||% 0) else 0
d_debt_lag <- tail(na.omit(debt_df$d_debt), 1) %||% 0
debt <- numeric(T+1); debt[1] <- d0
for (t in 1:T) {
drmg_t <- if (t==1) (rmg[1]-rmg0) else (rmg[t]-rmg[t-1])
dsp_t <- if (t==1) (sp[1]-sp0) else (sp[t]-sp[t-1])
d_debt_t <- c0 +
cLdebt * debt[t] +
cLrmg * (if (t==1) rmg0 else rmg[t-1]) +
cdrmg * drmg_t +
cddebt1 * d_debt_lag +
cLsp * (if (t==1) sp0 else sp[t-1]) +
cdsp * dsp_t
debt[t+1] <- debt[t] + d_debt_t
d_debt_lag <- d_debt_t
}
tibble::tibble(t=0:T, debt=debt)
}
# -- 8) Scénarios & graphes ----
Tsim <- 20
sim_baseD <- simulate_debt_paths(T=Tsim, shock=list(type="none"))
sim_spP1 <- simulate_debt_paths(T=Tsim, shock=list(type="permanent", d_sp=+0.01))
sim_spM1 <- simulate_debt_paths(T=Tsim, shock=list(type="permanent", d_sp=-0.01))
sim_rmgP1a <- simulate_debt_paths(T=Tsim, shock=list(type="permanent", d_rmg=+0.0025))
sim_spTemp <- simulate_debt_paths(T=Tsim, shock=list(type="temp", d_sp=+0.01, start=1, len=4))
libra <- dplyr::bind_rows(
sim_baseD %>% mutate(scn="Baseline"),
sim_spP1 %>% mutate(scn="SP +1 pt (perm)"),
sim_spM1 %>% mutate(scn="SP -1 pt (perm)"),
sim_rmgP1a %>% mutate(scn="(r-g) +1 pt/an (perm)"),
sim_spTemp %>% mutate(scn="SP +1 pt (4 tr)")
)
ggplot(libra, aes(t, debt, color=scn)) +
geom_line() +
labs(title="Dette/PIB — trajectoires simulées (ECM)", x="Horizon (t)", y="Dette / PIB", color=NULL)
base_only <- sim_baseD %>% select(t, debt_base = debt)
libra_delta <- libra %>% left_join(base_only, by="t") %>%
mutate(delta = debt - debt_base) %>%
filter(scn != "Baseline")
ggplot(libra_delta, aes(t, delta)) +
geom_hline(yintercept=0, color="grey70") +
geom_line() +
facet_wrap(~scn, ncol=2, scales="free_y") +
labs(title="Dette/PIB — écarts au baseline", x="Horizon (t)", y="Δ dette (pts)")
}
##
## === ECM Dette (ΔTDP) — résumé ===
##
## Call:
## lm(formula = if (use_sp) form_debt_full else form_debt_nosp,
## data = debt_df, na.action = na.omit)
##
## Residuals:
## Min 1Q Median 3Q Max
## -5.2951 -0.4799 -0.1193 0.4813 6.4323
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -0.850170 0.405028 -2.099 0.036923 *
## Ldebt -0.004126 0.006651 -0.620 0.535650
## Lrmg 12.996856 3.706826 3.506 0.000548 ***
## drmg 9.872926 3.356803 2.941 0.003610 **
## d_debt_1 0.090996 0.067944 1.339 0.181823
## Lsp -0.017530 0.003483 -5.033 9.85e-07 ***
## dsp -0.061874 0.010176 -6.081 5.05e-09 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 1.086 on 226 degrees of freedom
## Multiple R-squared: 0.3647, Adjusted R-squared: 0.3479
## F-statistic: 21.63 on 6 and 226 DF, p-value: < 2.2e-16
##
##
## Long terme: Δdette ≈ -4.249 × Δ(solde primaire)
## Long terme: Δdette ≈ 3150.023 × Δ(r - g)
## Impact (CT) d’un Δsp_t : -0.062
## Impact (CT) d’un Δ(r-g)_t : 9.873
En pratique : des améliorations budgétaires durables ont un rendement élevé à LT (effet multiplicateur sur la dette/PIB), surtout lorsque \((r-g)\) n’est pas défavorable. Inversement, une détérioration durable de \((r-g)\) peut annuler des efforts primaires modestes.
# =========================
# MODULE DETTE (TDP / PIB)
# =========================
suppressWarnings({
if (!requireNamespace("broom", quietly=TRUE)) install.packages("broom")
if (!requireNamespace("strucchange", quietly=TRUE)) install.packages("strucchange")
if (!requireNamespace("readr", quietly=TRUE)) install.packages("readr")
})
library(dplyr); library(broom); library(ggplot2)
# --------- utilitaires robustes ----------
`%||%` <- function(x, y) if (is.null(x) || length(x)==0 || all(is.na(x))) y else x
L <- function(x, k=1) dplyr::lag(x, k)
D1 <- function(x) x - L(x)
safe_log <- function(x) log(pmax(as.numeric(x), .Machine$double.eps))
normalize_names <- function(x){ gsub("[^[:alnum:]]+", "", toupper(x)) }
pick1 <- function(cands, nms){
n1 <- normalize_names(nms); c1 <- normalize_names(cands)
hit <- which(n1 %in% c1)
if (length(hit)) return(nms[hit[1]])
h2 <- grep(paste(cands, collapse="|"), nms, ignore.case=TRUE, value=TRUE)
if (length(h2)) h2[1] else NA_character_
}
# --------- 0) détecte TDP (dette/PIB) & solde primaire ----------
stopifnot(exists("df_raw"), exists("D"))
nms <- names(df_raw)
col_debt <- pick1(c("TDP","DETTEPUBLIQUE","DETTEPIB","DETTETOT","DETTE","TDPDUPIB","TDPPIB"), nms)
col_sp <- pick1(c("SP","SOLDEPRIMAIRE","SOLDE_PRIMAIRE","SOLDEPRIM"), nms)
has_debt <- !is.na(col_debt) && col_debt %in% names(df_raw)
if (!has_debt) {
message("Module Dette: colonne TDP (dette/PIB) introuvable → section sautée.")
} else {
# --------- 1) garanties macro minimales (si chunk macro pas encore exécuté) ----------
if (!("dlypy" %in% names(D))) {
if (!("ypy" %in% names(D))) stop("Il manque 'ypy' dans D pour calculer dlypy.")
D <- D %>% mutate(dlypy = safe_log(ypy) - safe_log(L(ypy)))
}
if (!("pp" %in% names(D))) {
if (!("lpy" %in% names(D))) D <- D %>% mutate(lpy = safe_log(py))
D <- D %>% mutate(
dlpy = c(NA, diff(lpy)),
pp = 0.2*dlpy + 0.4*L(dlpy,1) + 0.2*L(dlpy,2) + 0.1*L(dlpy,3) + 0.1*L(dlpy,4)
)
}
# --------- 2) construit les variables dette ----------
D <- D %>%
mutate(
debt = as.numeric(df_raw[[col_debt]]), # TDP (pts de PIB)
sp = if (!is.na(col_sp)) as.numeric(df_raw[[col_sp]]) else NA_real_,
r_nom = as.numeric(tr)/100, # tr en %
r_real = r_nom - pmax(pp, 0), # ~ r - inflation attendue
g = as.numeric(dlypy), # croissance réelle
r_minus_g = r_real - g
)
# --------- 3) données pour ECM (on jette 8 1ers points) ----------
debt_df <- D %>%
transmute(
debt,
d_debt = D1(debt),
Ldebt = L(debt),
rmg = r_minus_g,
Lrmg = L(r_minus_g),
drmg = D1(r_minus_g),
sp = sp,
Lsp = L(sp),
dsp = D1(sp),
d_debt_1 = L(D1(debt))
) %>%
filter(row_number() > 8)
use_sp <- any(is.finite(debt_df$sp))
# --------- 4) ECM avec petite sélection AIC de variantes ----------
form_base <- d_debt ~ Ldebt + Lrmg + drmg + d_debt_1
forms <- list(form_base)
if (use_sp) {
forms <- c(forms, list(
update(form_base, . ~ . + Lsp + dsp),
d_debt ~ Ldebt + Lrmg + drmg + L(d_debt_1) + Lsp + dsp,
d_debt ~ L(debt,2) + Lrmg + drmg + d_debt_1 + Lsp + dsp,
d_debt ~ Ldebt + L(rmg,2) + drmg + d_debt_1 + Lsp + dsp
))
} else {
forms <- c(forms, list(
d_debt ~ Ldebt + Lrmg + drmg,
d_debt ~ L(debt,2) + Lrmg + drmg + d_debt_1
))
}
pick_best <- function(fs, data){
fits <- lapply(fs, function(f){
mf <- try(model.frame(f, data=data, na.action = na.omit), silent=TRUE)
if (inherits(mf,"try-error") || nrow(mf)<30) return(NULL)
lm(f, data=data, na.action = na.omit)
})
fits <- Filter(Negate(is.null), fits)
if (!length(fits)) stop("ECM dette: pas assez d'observations.")
fits[[ which.min(sapply(fits, AIC)) ]]
}
ecm_debt <- pick_best(forms, debt_df)
cat("\n=== ECM Dette (ΔTDP) — résumé ===\n"); print(summary(ecm_debt))
# --------- 5) Multiplicateurs LT + impacts CT (extraction robuste) ----------
cf <- coef(ecm_debt); cn <- names(cf)
# λ : 1er coeff dont le nom matche L(debt) ou L(debt, k)
idx_lambda <- grep("^L\\(debt(,\\s*\\d+)?\\)$", cn)
lambda <- if (length(idx_lambda)) unname(cf[idx_lambda[1]]) else NA_real_
# β_r et β_s si présents
beta_r <- if ("Lrmg" %in% cn) unname(cf["Lrmg"]) else NA_real_
beta_s <- if (use_sp && "Lsp" %in% cn) unname(cf["Lsp"]) else NA_real_
imp_dsp <- if ("dsp" %in% cn) unname(cf["dsp"]) else NA_real_
imp_drmg <- if ("drmg" %in% cn) unname(cf["drmg"]) else NA_real_
cat("\n--- Multiplicateurs (approx.) ---\n")
if (is.finite(beta_s) && is.finite(lambda) && lambda != 0) {
cat(sprintf("Long terme: Δdette ≈ %.3f × Δ(solde primaire)\n", -beta_s/lambda))
} else cat("Long terme: Δ(solde primaire) non calculable (λ ou Lsp absent).\n")
if (is.finite(beta_r) && is.finite(lambda) && lambda != 0) {
cat(sprintf("Long terme: Δdette ≈ %.3f × Δ(r - g)\n", -beta_r/lambda))
} else cat("Long terme: Δ(r - g) non calculable (λ ou Lrmg absent).\n")
if (is.finite(imp_dsp)) cat(sprintf("Impact (CT) d’un Δsp_t : %.3f\n", imp_dsp))
if (is.finite(imp_drmg)) cat(sprintf("Impact (CT) d’un Δ(r-g)_t : %.3f\n", imp_drmg))
# --------- 6) Observé vs Ajusté (Δdette) ----------
mf <- model.frame(ecm_debt)
aug <- broom::augment(ecm_debt)
y <- as.numeric(model.response(mf))
ggplot(tibble(t=seq_len(nrow(mf)), y=y, yhat=aug$.fitted), aes(t)) +
geom_line(aes(y=y), linewidth=.5) +
geom_line(aes(y=yhat), linetype="dashed") +
labs(title="Dette — Δobservé vs Δajusté (ECM)", x=NULL, y=NULL)
# --------- 7) Stabilité CUSUM (protégé contre mismatch) ----------
try({
efp_y <- strucchange::efp(formula(ecm_debt), data = mf, type="Rec-CUSUM")
cus <- data.frame(t = seq_along(efp_y$process),
proc = efp_y$process, bound = efp_y$bound)
ggplot(cus, aes(t, proc)) +
geom_line() +
geom_line(aes(y= bound), linetype="dashed") +
geom_line(aes(y=-bound), linetype="dashed") +
labs(title="CUSUM — stabilité des coefficients (ECM dette)", x=NULL, y=NULL)
}, silent = TRUE)
# --------- 8) Simulateur multi-scénarios (permanent/temporaire) ----------
simulate_debt_paths <- function(T=20,
shock = list(
type = "none", # "none", "permanent", "temp"
d_sp = 0.00, # +0.01 = +1 pt de PIB
d_rmg = 0.00, # +0.0025 ≈ +1 pt/an (~0.25 pt/tr)
start = 1,
len = 4
)) {
stype <- shock$type %||% "none"
d0 <- tail(na.omit(D$debt), 1)
rmg0 <- tail(na.omit(D$r_minus_g), 1)
sp0 <- if (use_sp) tail(na.omit(D$sp), 1) else 0
rmg <- rep(rmg0, T); sp <- rep(sp0, T)
if (stype == "permanent") {
rmg <- rmg + (shock$d_rmg %||% 0)
sp <- sp + (shock$d_sp %||% 0)
} else if (stype == "temp") {
start <- shock$start %||% 1
len <- shock$len %||% 4
if (len > 0) {
idx <- seq.int(start, min(T, start+len-1))
rmg[idx] <- rmg[idx] + (shock$d_rmg %||% 0)
sp[idx] <- sp[idx] + (shock$d_sp %||% 0)
}
}
# coefficients (robustes aux noms)
c0 <- if ("(Intercept)" %in% cn) unname(cf["(Intercept)"]) else 0
idx_lmbd <- grep("^L\\(debt(,\\s*\\d+)?\\)$", cn)
cLdebt <- if (length(idx_lmbd)) unname(cf[idx_lmbd[1]]) else 0
cLrmg <- if ("Lrmg" %in% cn) unname(cf["Lrmg"]) else 0
cdrmg <- if ("drmg" %in% cn) unname(cf["drmg"]) else 0
cddebt1 <- if ("d_debt_1" %in% cn) unname(cf["d_debt_1"]) else if ("L(d_debt_1)" %in% cn) unname(cf["L(d_debt_1)"]) else 0
cLsp <- if (use_sp && "Lsp" %in% cn) unname(cf["Lsp"]) else 0
cdsp <- if (use_sp && "dsp" %in% cn) unname(cf["dsp"]) else 0
d_debt_lag <- tail(na.omit(debt_df$d_debt), 1) %||% 0
debt <- numeric(T+1); debt[1] <- d0
for (t in 1:T) {
drmg_t <- if (t==1) (rmg[1]-rmg0) else (rmg[t]-rmg[t-1])
dsp_t <- if (t==1) (sp[1]-sp0) else (sp[t]-sp[t-1])
d_debt_t <- c0 +
cLdebt * debt[t] +
cLrmg * (if (t==1) rmg0 else rmg[t-1]) +
cdrmg * drmg_t +
cddebt1 * d_debt_lag +
cLsp * (if (t==1) sp0 else sp[t-1]) +
cdsp * dsp_t
debt[t+1] <- debt[t] + d_debt_t
d_debt_lag <- d_debt_t
}
tibble(t=0:T, debt=debt)
}
# --------- 9) scénarios prêts à l’emploi ----------
Tsim <- 20
sim_baseD <- simulate_debt_paths(T=Tsim, shock=list(type="none"))
sim_rmgP1 <- simulate_debt_paths(T=Tsim, shock=list(type="permanent", d_rmg=+0.0025))
sim_spP1 <- simulate_debt_paths(T=Tsim, shock=list(type="permanent", d_sp=+0.01))
sim_spM1 <- simulate_debt_paths(T=Tsim, shock=list(type="permanent", d_sp=-0.01))
sim_combo <- simulate_debt_paths(T=Tsim, shock=list(type="permanent", d_rmg=+0.0025, d_sp=-0.01))
sim_sp4q <- simulate_debt_paths(T=Tsim, shock=list(type="temp", d_sp=+0.01, start=1, len=4))
libra <- bind_rows(
sim_baseD %>% mutate(scn="Baseline"),
sim_rmgP1 %>% mutate(scn="(r - g) +1 pt/an (perm)"),
sim_spP1 %>% mutate(scn="SP +1 pt (perm)"),
sim_spM1 %>% mutate(scn="SP -1 pt (perm)"),
sim_combo %>% mutate(scn="(r-g)+1 & SP -1 (perm)"),
sim_sp4q %>% mutate(scn="SP +1 pt (4 tr)")
)
# Trajectoires
ggplot(libra, aes(t, debt, color=scn)) +
geom_line() +
labs(title="Dette/PIB — trajectoires simulées (ECM)",
x="Horizon (t)", y="Dette / PIB", color=NULL)
# Écarts au baseline
base_only <- sim_baseD %>% select(t, debt_base = debt)
libra_delta <- libra %>% left_join(base_only, by="t") %>%
mutate(delta = debt - debt_base) %>% filter(scn != "Baseline")
ggplot(libra_delta, aes(t, delta)) +
geom_hline(yintercept=0, color="grey70") +
geom_line() +
facet_wrap(~scn, ncol=2, scales="free_y") +
labs(title="Dette/PIB — écarts au baseline", x="Horizon (t)", y="Δ dette (pts)")
# Impulsions (ΔΔ dette)
imp <- libra_delta %>%
group_by(scn) %>%
mutate(impulse = c(NA, diff(delta))) %>%
ungroup()
ggplot(imp, aes(t, impulse)) +
geom_hline(yintercept=0, color="grey70") +
geom_line() +
facet_wrap(~scn, ncol=2, scales="free_y") +
labs(title="Δ(écart de dette) — impulsions par scénario", x="Horizon (t)", y="ΔΔ dette")
# Export CSV (optionnel)
if (!dir.exists("outputs")) dir.create("outputs")
readr::write_csv(libra, "outputs/sim_dette_trajectoires.csv")
readr::write_csv(libra_delta, "outputs/sim_dette_ecarts.csv")
}
##
## === ECM Dette (ΔTDP) — résumé ===
##
## Call:
## lm(formula = f, data = data, na.action = na.omit)
##
## Residuals:
## Min 1Q Median 3Q Max
## -5.2969 -0.4813 -0.1226 0.5101 6.4380
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -0.841734 0.411905 -2.044 0.042172 *
## L(debt, 2) -0.004214 0.006715 -0.628 0.530901
## Lrmg 12.932019 3.755984 3.443 0.000686 ***
## drmg 9.836447 3.386614 2.905 0.004047 **
## d_debt_1 0.086520 0.070034 1.235 0.217976
## Lsp -0.017530 0.003498 -5.011 1.10e-06 ***
## dsp -0.061921 0.010227 -6.054 5.88e-09 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 1.091 on 224 degrees of freedom
## (2 observations effacées parce que manquantes)
## Multiple R-squared: 0.3621, Adjusted R-squared: 0.3451
## F-statistic: 21.2 on 6 and 224 DF, p-value: < 2.2e-16
##
##
## --- Multiplicateurs (approx.) ---
## Long terme: Δdette ≈ -4.159 × Δ(solde primaire)
## Long terme: Δdette ≈ 3068.487 × Δ(r - g)
## Impact (CT) d’un Δsp_t : -0.062
## Impact (CT) d’un Δ(r-g)_t : 9.836
# =========================
# MODULE DETTE (TDP / PIB) — version pp/an + BIC + simulateur (robuste)
# =========================
suppressWarnings({
if (!requireNamespace("broom", quietly=TRUE)) install.packages("broom")
if (!requireNamespace("strucchange", quietly=TRUE)) install.packages("strucchange")
if (!requireNamespace("readr", quietly=TRUE)) install.packages("readr")
})
library(dplyr); library(broom); library(ggplot2)
`%||%` <- function(x, y) if (is.null(x) || length(x)==0 || all(is.na(x))) y else x
L <- function(x, k=1) dplyr::lag(x, k)
D1 <- function(x) x - L(x)
safe_log <- function(x) log(pmax(as.numeric(x), .Machine$double.eps))
# ---- Détection des colonnes "Dette/PIB (TDP)" et "Solde primaire (SP)" ----
normalize_names <- function(x) gsub("[^[:alnum:]]+", "", toupper(x))
pick1 <- function(cands, nms){
n1 <- normalize_names(nms); c1 <- normalize_names(cands)
hit <- match(c1, n1, nomatch = 0); hit <- hit[hit>0]
if (length(hit)) return(nms[hit[1]])
h2 <- grep(paste(cands, collapse="|"), nms, ignore.case=TRUE, value=TRUE)
if (length(h2)) h2[1] else NA_character_
}
nms <- names(df_raw)
col_debt <- pick1(c("TDP","DETTEPUBLIQUE","DETTEPIB","DETTETOT","DETTE"), nms) # <- ta TDP
col_sp <- pick1(c("SP","SOLDEPRIMAIRE","SOLDE_PRIMAIRE","SOLDEPRIM"), nms)
has_debt <- !is.na(col_debt) && col_debt %in% names(df_raw)
if (!has_debt) {
message("Module Dette: colonne TDP (dette/PIB) introuvable → section sautée.")
} else {
# ---- Garanties macro minimales (si chunks précédents non exécutés)
if (!("dlypy" %in% names(D))) {
if (!all(c("ypy") %in% names(D))) stop("Il manque 'ypy' pour calculer dlypy.")
D <- D %>% mutate(dlypy = safe_log(ypy) - safe_log(L(ypy)))
}
if (!("pp" %in% names(D))) {
if (!("lpy" %in% names(D)) && "py" %in% names(D)) D <- D %>% mutate(lpy = safe_log(py))
D <- D %>% mutate(
dlpy = c(NA, diff(lpy)),
pp = 0.2*dlpy + 0.4*L(dlpy,1) + 0.2*L(dlpy,2) + 0.1*L(dlpy,3) + 0.1*L(dlpy,4)
)
}
# ---- Variables dette & (r−g) en points de pourcentage annuels (pp/an) ----
D <- D %>%
mutate(
debt = as.numeric(df_raw[[col_debt]]), # TDP (pts de PIB)
sp = if (!is.na(col_sp)) as.numeric(df_raw[[col_sp]]) else NA_real_,
r_nom = as.numeric(tr)/100, # taux nominal (déc.)
r_real = r_nom - pmax(pp, 0), # r - inflation attendue (déc.)
g = as.numeric(dlypy), # croissance réelle (déc.)
r_minus_g = r_real - g, # (r - g) décimal trimestriel
rmg_pp_a = r_minus_g * 100 * 4 # → pp/an
)
# ---- Data ECM (on jette 8 premiers points pour les lags) ----
debt_df <- D %>%
transmute(
debt,
d_debt = D1(debt),
d_debt_1 = L(D1(debt)),
Ldebt = L(debt),
Ldebt2 = L(debt,2),
rmg_pp_a = rmg_pp_a,
Lrmg_pp_a = L(rmg_pp_a),
drmg_pp_a = D1(rmg_pp_a),
sp = sp,
Lsp = L(sp),
dsp = D1(sp)
) %>%
filter(dplyr::row_number()>8)
use_sp <- any(is.finite(debt_df$sp))
# ---- Candidats ECM (BIC) ----
forms <- list(
d_debt ~ Ldebt + Lrmg_pp_a + drmg_pp_a + d_debt_1,
d_debt ~ Ldebt + Lrmg_pp_a + drmg_pp_a,
d_debt ~ Ldebt2 + Lrmg_pp_a + drmg_pp_a + d_debt_1
)
if (use_sp) {
forms <- c(forms, list(
d_debt ~ Ldebt + Lrmg_pp_a + drmg_pp_a + d_debt_1 + Lsp + dsp,
d_debt ~ Ldebt + Lrmg_pp_a + drmg_pp_a + L(d_debt_1) + Lsp + dsp,
d_debt ~ Ldebt2 + Lrmg_pp_a + drmg_pp_a + d_debt_1 + Lsp + dsp
))
}
fits <- lapply(forms, function(f){
mf <- try(model.frame(f, data=debt_df, na.action=na.omit), silent=TRUE)
if (inherits(mf,"try-error") || nrow(mf)<30) return(NULL)
lm(f, data=debt_df, na.action=na.omit)
})
fits <- Filter(Negate(is.null), fits)
if (!length(fits)) stop("ECM dette: pas assez d'observations.")
ecm_debt <- fits[[ which.min(sapply(fits, BIC)) ]]
cat("\n=== ECM Dette (ΔTDP) — résumé ===\n")
print(summary(ecm_debt))
# ---- Multiplicateurs (LT) + impacts (CT) — robustes aux noms de coef
cf <- coef(ecm_debt)
coef_or0 <- function(cf, cand) { nm <- intersect(cand, names(cf)); if (length(nm)) cf[[nm[1]]] else 0 }
coef_orNA <- function(cf, cand) { nm <- intersect(cand, names(cf)); if (length(nm)) cf[[nm[1]]] else NA_real_ }
lambda <- coef_or0(cf, c("Ldebt","Ldebt2")) # terme d'ajustement (ECM)
beta_r <- coef_orNA(cf, c("Lrmg_pp_a")) # LT (r−g)
beta_s <- if (use_sp) coef_orNA(cf, c("Lsp")) else NA_real_
if (is.finite(beta_s) && lambda!=0)
cat(sprintf("\nLong terme (pp de dette par pt de PIB): %.3f × ΔSP\n", -beta_s/lambda))
if (is.finite(beta_r) && lambda!=0)
cat(sprintf("Long terme (pp de dette par pp/an de r-g): %.3f × Δ(r−g)\n", -beta_r/lambda))
if ("dsp" %in% names(cf)) cat(sprintf("Impact CT d’un ΔSP_t (pt PIB) : %.3f\n", cf[["dsp"]]))
if ("drmg_pp_a" %in% names(cf)) cat(sprintf("Impact CT d’un Δ(r−g)_t (pp/an): %.3f\n", cf[["drmg_pp_a"]]))
# ---- Observé vs Ajusté (Δdette)
mf <- model.frame(ecm_debt)
aug <- broom::augment(ecm_debt)
y <- as.numeric(model.response(mf))
ggplot(tibble(t=seq_len(nrow(mf)), y=y, yhat=aug$.fitted), aes(t)) +
geom_line(aes(y=y), linewidth=.5) +
geom_line(aes(y=yhat), linetype="dashed") +
labs(title="Dette — Δobservé vs Δajusté (ECM)", x=NULL, y=NULL)
# ---- CUSUM (stabilité)
try({
efp_y <- strucchange::efp(formula(ecm_debt), data = debt_df, type="Rec-CUSUM")
cus <- data.frame(t = seq_along(efp_y$process),
proc = efp_y$process, bound = efp_y$bound)
ggplot(cus, aes(t, proc)) +
geom_line() +
geom_line(aes(y= bound), linetype="dashed") +
geom_line(aes(y=-bound), linetype="dashed") +
labs(title="CUSUM — stabilité des coefficients (ECM dette)", x=NULL, y=NULL)
}, silent = TRUE)
# ---- Simulateur (T trimestres) — chocs en unités lisibles :
# d_sp = +1.0 => +1 point de PIB du solde primaire
# d_rmg = +1.0 => +1 point de pourcentage annuel de (r−g)
simulate_debt_paths <- function(T=20,
shock = list(
type = "none", # "none" | "permanent" | "temp"
d_sp = 0.00, # points de PIB
d_rmg = 0.00, # pp/an
start = 1,
len = 4
)) {
stype <- shock$type %||% "none"
d0 <- tail(na.omit(D$debt), 1)
rmg0 <- tail(na.omit(D$rmg_pp_a), 1) # baseline en pp/an
sp0 <- if (use_sp) tail(na.omit(D$sp), 1) else 0
rmg <- rep(rmg0, T); sp <- rep(sp0, T)
if (stype == "permanent") {
rmg <- rmg + (shock$d_rmg %||% 0)
sp <- sp + (shock$d_sp %||% 0)
} else if (stype == "temp") {
start <- shock$start %||% 1
len <- shock$len %||% 4
if (len > 0) {
idx <- seq.int(start, min(T, start+len-1))
rmg[idx] <- rmg[idx] + (shock$d_rmg %||% 0)
sp[idx] <- sp[idx] + (shock$d_sp %||% 0)
}
}
# Coefficients utiles (0 si absents) — robustes aux alias
c0 <- cf[["(Intercept)"]] %||% 0
cLdebt <- coef_or0(cf, c("Ldebt","Ldebt2"))
cLrmg <- coef_or0(cf, c("Lrmg_pp_a"))
cdrmg <- coef_or0(cf, c("drmg_pp_a"))
cddebt1 <- coef_or0(cf, c("d_debt_1","L(d_debt_1)"))
cLsp <- if (use_sp) coef_or0(cf, c("Lsp")) else 0
cdsp <- if (use_sp) coef_or0(cf, c("dsp")) else 0
d_debt_lag <- tail(na.omit(debt_df$d_debt), 1) %||% 0
debt <- numeric(T+1); debt[1] <- d0
for (t in 1:T) {
drmg_t <- if (t==1) (rmg[1]-rmg0) else (rmg[t]-rmg[t-1])
dsp_t <- if (t==1) (sp[1]-sp0) else (sp[t]-sp[t-1])
d_debt_t <- c0 +
cLdebt * debt[t] +
cLrmg * (if (t==1) rmg0 else rmg[t-1]) +
cdrmg * drmg_t +
cddebt1 * d_debt_lag +
cLsp * (if (t==1) sp0 else sp[t-1]) +
cdsp * dsp_t
debt[t+1] <- debt[t] + d_debt_t
d_debt_lag <- d_debt_t
}
tibble(t=0:T, debt=debt)
}
# ---- Scénarios exemples (20 trimestres)
Tsim <- 20
sim_baseD <- simulate_debt_paths(T=Tsim, shock=list(type="none"))
sim_rmgP1 <- simulate_debt_paths(T=Tsim, shock=list(type="permanent", d_rmg=+1.0))
sim_spP1 <- simulate_debt_paths(T=Tsim, shock=list(type="permanent", d_sp=+1.0))
sim_spM1 <- simulate_debt_paths(T=Tsim, shock=list(type="permanent", d_sp=-1.0))
sim_combo <- simulate_debt_paths(T=Tsim, shock=list(type="permanent", d_rmg=+1.0, d_sp=-1.0))
sim_sp4q <- simulate_debt_paths(T=Tsim, shock=list(type="temp", d_sp=+1.0, start=1, len=4))
# Trajectoires & écarts
libra <- bind_rows(
sim_baseD %>% mutate(scn="Baseline"),
sim_rmgP1 %>% mutate(scn="(r−g) +1 pp/an (perm)"),
sim_spP1 %>% mutate(scn="SP +1 pt (perm)"),
sim_spM1 %>% mutate(scn="SP −1 pt (perm)"),
sim_combo %>% mutate(scn="(r−g)+1 & SP −1 (perm)"),
sim_sp4q %>% mutate(scn="SP +1 pt (4 tr)")
)
ggplot(libra, aes(t, debt, color=scn)) +
geom_line() +
labs(title="Dette/PIB — trajectoires simulées (ECM, pp/an & pt PIB)",
x="Horizon (t)", y="Dette / PIB", color=NULL)
base_only <- sim_baseD %>% select(t, debt_base = debt)
libra_delta <- libra %>% left_join(base_only, by="t") %>%
mutate(delta = debt - debt_base) %>% filter(scn != "Baseline")
ggplot(libra_delta, aes(t, delta)) +
geom_hline(yintercept=0, color="grey70") +
geom_line() +
facet_wrap(~scn, ncol=2, scales="free_y") +
labs(title="Dette/PIB — écarts au baseline", x="Horizon (t)", y="Δ dette (pts)")
# Export CSV (optionnel)
if (!dir.exists("outputs")) dir.create("outputs")
readr::write_csv(libra, "outputs/sim_dette_trajectoires.csv")
readr::write_csv(libra_delta, "outputs/sim_dette_ecarts.csv")
}
##
## === ECM Dette (ΔTDP) — résumé ===
##
## Call:
## lm(formula = f, data = debt_df, na.action = na.omit)
##
## Residuals:
## Min 1Q Median 3Q Max
## -5.4055 -0.4886 -0.0997 0.4799 6.0655
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -0.685537 0.416973 -1.644 0.10156
## Ldebt -0.009456 0.006929 -1.365 0.17373
## Lrmg_pp_a 0.030621 0.009425 3.249 0.00134 **
## drmg_pp_a 0.020855 0.007881 2.646 0.00872 **
## L(d_debt_1) -0.091958 0.063161 -1.456 0.14681
## Lsp -0.022230 0.003601 -6.174 3.08e-09 ***
## dsp -0.065983 0.009891 -6.671 1.95e-10 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 1.088 on 225 degrees of freedom
## (1 observation effacée parce que manquante)
## Multiple R-squared: 0.3644, Adjusted R-squared: 0.3475
## F-statistic: 21.5 on 6 and 225 DF, p-value: < 2.2e-16
##
##
## Long terme (pp de dette par pt de PIB): -2.351 × ΔSP
## Long terme (pp de dette par pp/an de r-g): 3.238 × Δ(r−g)
## Impact CT d’un ΔSP_t (pt PIB) : -0.066
## Impact CT d’un Δ(r−g)_t (pp/an): 0.021
# =========================
# MODULE DETTE (TDP/PIB) — ECM + BIC + Simulations + Exports
# =========================
suppressWarnings({
pkgs <- c("dplyr","ggplot2","broom","strucchange","readr")
inst <- pkgs[!pkgs %in% installed.packages()[,1]]
if (length(inst)) install.packages(inst, dep = TRUE)
})
library(dplyr); library(ggplot2); library(broom); library(strucchange); library(readr)
`%||%` <- function(x, y) if (is.null(x) || length(x)==0 || all(is.na(x))) y else x
L <- function(x, k=1) dplyr::lag(x, k)
D1 <- function(x) x - L(x)
safe_log <- function(x) log(pmax(as.numeric(x), .Machine$double.eps))
normalize_names <- function(x) gsub("[^[:alnum:]]+", "", toupper(x))
pick1 <- function(cands, nms){
n1 <- normalize_names(nms); c1 <- normalize_names(cands)
hit <- match(c1, n1, nomatch = 0); hit <- hit[hit>0]
if (length(hit)) return(nms[hit[1]])
h2 <- grep(paste(cands, collapse="|"), nms, ignore.case=TRUE, value=TRUE)
if (length(h2)) h2[1] else NA_character_
}
safe_coef <- function(cf, name, default = NA_real_) {
if (length(cf) == 0) return(default)
nms <- names(cf)
if (!is.null(nms) && name %in% nms) return(unname(cf[[name]]))
default
}
stopifnot(exists("df_raw"), exists("D"))
# --- 0) Détection des colonnes Dette (TDP) & Solde primaire (SP)
nms <- names(df_raw)
col_debt <- pick1(c("TDP","DETTEPUBLIQUE","DETTEPIB","DETTETOT","DETTE"), nms)
col_sp <- pick1(c("SP","SOLDEPRIMAIRE","SOLDE_PRIMAIRE","SOLDEPRIM"), nms)
has_debt <- !is.na(col_debt) && col_debt %in% names(df_raw)
if (!has_debt) {
message("Module Dette: colonne TDP (dette/PIB) introuvable → section sautée (aucune erreur).")
} else {
# --- 1) Garanties macro (si chunk précédent ne les a pas créées)
if (!("dlypy" %in% names(D))) {
stopifnot("ypy" %in% names(D))
D <- D %>% mutate(dlypy = safe_log(ypy) - safe_log(L(ypy)))
}
if (!("pp" %in% names(D))) {
if (!("lpy" %in% names(D))) D <- D %>% mutate(lpy = safe_log(py))
D <- D %>% mutate(
dlpy = c(NA, diff(lpy)),
pp = 0.2*dlpy + 0.4*L(dlpy,1) + 0.2*L(dlpy,2) + 0.1*L(dlpy,3) + 0.1*L(dlpy,4)
)
}
# --- 2) Variables Dette & (r−g) en pp/an
# Essaie d’inférer l’année si disponible pour l’analyse par sous-périodes
year_vec <- NA_integer_
if ("Year" %in% names(df_raw)) {
year_vec <- suppressWarnings(as.integer(df_raw$Year))
} else if ("date" %in% names(df_raw)) {
year_vec <- suppressWarnings(as.integer(format(as.Date(df_raw$date), "%Y")))
}
D <- D %>%
mutate(
Year = year_vec,
debt = as.numeric(df_raw[[col_debt]]), # TDP (pts de PIB)
sp = if (!is.na(col_sp)) as.numeric(df_raw[[col_sp]]) else NA_real_,
r_nom = as.numeric(tr)/100, # taux nominal (déc.)
r_real = r_nom - pmax(pp, 0), # r - inflation attendue
g = as.numeric(dlypy), # croissance réelle (déc.)
r_minus_g = r_real - g, # décimal trimestriel
rmg_pp_a = r_minus_g * 100 * 4 # → pp/an
)
# --- 3) Data ECM (on jette 8 points pour les retards)
debt_df <- D %>%
transmute(
Year,
debt,
d_debt = D1(debt),
d_debt_1 = L(D1(debt)),
Ldebt = L(debt),
Ldebt2 = L(debt,2),
rmg_pp_a = rmg_pp_a,
Lrmg_pp_a = L(rmg_pp_a),
drmg_pp_a = D1(rmg_pp_a),
sp = sp,
Lsp = L(sp),
dsp = D1(sp)
) %>%
filter(row_number()>8)
use_sp <- any(is.finite(debt_df$sp))
# --- 4) Choix de spécification (BIC) — candidats robustes
forms <- list(
d_debt ~ Ldebt + Lrmg_pp_a + drmg_pp_a + d_debt_1,
d_debt ~ Ldebt + Lrmg_pp_a + drmg_pp_a,
d_debt ~ Ldebt2 + Lrmg_pp_a + drmg_pp_a + d_debt_1
)
if (use_sp) {
forms <- c(forms, list(
d_debt ~ Ldebt + Lrmg_pp_a + drmg_pp_a + d_debt_1 + Lsp + dsp,
d_debt ~ Ldebt + Lrmg_pp_a + drmg_pp_a + L(d_debt_1) + Lsp + dsp,
d_debt ~ Ldebt2 + Lrmg_pp_a + drmg_pp_a + d_debt_1 + Lsp + dsp
))
}
fits <- lapply(forms, function(f){
mf <- try(model.frame(f, data=debt_df, na.action=na.omit), silent=TRUE)
if (inherits(mf,"try-error") || nrow(mf)<30) return(NULL)
lm(f, data=debt_df, na.action=na.omit)
})
fits <- Filter(Negate(is.null), fits)
stopifnot(length(fits) > 0)
ecm_debt <- fits[[ which.min(sapply(fits, BIC)) ]]
cat("\n=== ECM Dette (ΔTDP) — résumé ===\n"); print(summary(ecm_debt))
# --- 5) Multiplicateurs (LT) + impacts (CT) — extraction 100% sûre
cf <- coef(ecm_debt)
lambda <- (safe_coef(cf,"Ldebt", NA_real_) %||% safe_coef(cf,"Ldebt2", NA_real_)) %||% 0
beta_r <- safe_coef(cf, "Lrmg_pp_a", NA_real_)
beta_s <- if (use_sp) safe_coef(cf, "Lsp", NA_real_) else NA_real_
gamma_sp <- safe_coef(cf, "dsp", NA_real_)
gamma_rg <- safe_coef(cf, "drmg_pp_a", NA_real_)
if (is.finite(beta_s) && lambda!=0)
cat(sprintf("\nLong terme (pp de dette par pt de PIB): %.3f × ΔSP\n", -beta_s/lambda))
if (is.finite(beta_r) && lambda!=0)
cat(sprintf("Long terme (pp de dette par pp/an de r−g): %.3f × Δ(r−g)\n", -beta_r/lambda))
if (is.finite(gamma_sp)) cat(sprintf("Impact CT d’un ΔSP_t (pt PIB) : %.3f\n", gamma_sp))
if (is.finite(gamma_rg)) cat(sprintf("Impact CT d’un Δ(r−g)_t (pp/an): %.3f\n", gamma_rg))
# --- 6) Observé vs Ajusté (Δdette)
mf <- model.frame(ecm_debt)
aug <- augment(ecm_debt)
y <- as.numeric(model.response(mf))
ggplot(tibble(t=seq_len(nrow(mf)), y=y, yhat=aug$.fitted), aes(t)) +
geom_line(aes(y=y), linewidth=.5) +
geom_line(aes(y=yhat), linetype="dashed") +
labs(title="Dette — Δobservé vs Δajusté (ECM)", x=NULL, y=NULL)
# --- 7) CUSUM (stabilité) — silencieux si indisponible
try({
efp_y <- strucchange::efp(formula(ecm_debt), data = mf, type="Rec-CUSUM")
cus <- data.frame(t = seq_along(efp_y$process),
proc = efp_y$process, bound = efp_y$bound)
ggplot(cus, aes(t, proc)) +
geom_line() +
geom_line(aes(y= bound), linetype="dashed") +
geom_line(aes(y=-bound), linetype="dashed") +
labs(title="CUSUM — stabilité des coefficients (ECM dette)", x=NULL, y=NULL)
}, silent = TRUE)
# --- 8) Simulateur (T trimestres) — chocs en unités lisibles
simulate_debt_paths <- function(T=20,
shock = list(
type = "none", # "none" | "permanent" | "temp"
d_sp = 0.00, # +1.0 = +1 pt de PIB
d_rmg = 0.00, # +1.0 = +1 pp/an
start = 1,
len = 4
)) {
stype <- shock$type %||% "none"
d0 <- tail(na.omit(D$debt), 1)
rmg0 <- tail(na.omit(D$rmg_pp_a), 1)
sp0 <- if (use_sp) tail(na.omit(D$sp), 1) else 0
rmg <- rep(rmg0, T); sp <- rep(sp0, T)
if (stype == "permanent") {
rmg <- rmg + (shock$d_rmg %||% 0)
sp <- sp + (shock$d_sp %||% 0)
} else if (stype == "temp") {
start <- shock$start %||% 1
len <- shock$len %||% 4
if (len > 0) {
idx <- seq.int(start, min(T, start+len-1))
rmg[idx] <- rmg[idx] + (shock$d_rmg %||% 0)
sp[idx] <- sp[idx] + (shock$d_sp %||% 0)
}
}
c0 <- safe_coef(cf, "(Intercept)", 0)
cLdebt <- (safe_coef(cf,"Ldebt", NA_real_) %||% safe_coef(cf,"Ldebt2", NA_real_)) %||% 0
cLrmg <- safe_coef(cf, "Lrmg_pp_a", 0)
cdrmg <- safe_coef(cf, "drmg_pp_a", 0)
cddebt1 <- (safe_coef(cf,"d_debt_1", NA_real_) %||% safe_coef(cf,"L(d_debt_1)", NA_real_)) %||% 0
cLsp <- if (use_sp) safe_coef(cf, "Lsp", 0) else 0
cdsp <- if (use_sp) safe_coef(cf, "dsp", 0) else 0
d_debt_lag <- tail(na.omit(debt_df$d_debt), 1) %||% 0
debt <- numeric(T+1); debt[1] <- d0
for (t in 1:T) {
drmg_t <- if (t==1) (rmg[1]-rmg0) else (rmg[t]-rmg[t-1])
dsp_t <- if (t==1) (sp[1]-sp0) else (sp[t]-sp[t-1])
d_debt_t <- c0 +
cLdebt * debt[t] +
cLrmg * (if (t==1) rmg0 else rmg[t-1]) +
cdrmg * drmg_t +
cddebt1 * d_debt_lag +
cLsp * (if (t==1) sp0 else sp[t-1]) +
cdsp * dsp_t
debt[t+1] <- debt[t] + d_debt_t
d_debt_lag <- d_debt_t
}
tibble(t=0:T, debt=debt)
}
# --- 9) Scénarios (20 trimestres)
Tsim <- 20
sim_baseD <- simulate_debt_paths(T=Tsim, shock=list(type="none"))
sim_rmgP1 <- simulate_debt_paths(T=Tsim, shock=list(type="permanent", d_rmg=+1.0))
sim_spP1 <- simulate_debt_paths(T=Tsim, shock=list(type="permanent", d_sp=+1.0))
sim_spM1 <- simulate_debt_paths(T=Tsim, shock=list(type="permanent", d_sp=-1.0))
sim_combo <- simulate_debt_paths(T=Tsim, shock=list(type="permanent", d_rmg=+1.0, d_sp=-1.0))
sim_sp4q <- simulate_debt_paths(T=Tsim, shock=list(type="temp", d_sp=+1.0, start=1, len=4))
libra <- bind_rows(
sim_baseD %>% mutate(scn="Baseline"),
sim_rmgP1 %>% mutate(scn="(r−g) +1 pp/an (perm)"),
sim_spP1 %>% mutate(scn="SP +1 pt (perm)"),
sim_spM1 %>% mutate(scn="SP −1 pt (perm)"),
sim_combo %>% mutate(scn="(r−g)+1 & SP −1 (perm)"),
sim_sp4q %>% mutate(scn="SP +1 pt (4 tr)")
)
ggplot(libra, aes(t, debt, color=scn)) +
geom_line() +
labs(title="Dette/PIB — trajectoires simulées (ECM, pp/an & pt PIB)",
x="Horizon (t)", y="Dette / PIB", color=NULL)
base_only <- sim_baseD %>% select(t, debt_base = debt)
libra_delta <- libra %>% left_join(base_only, by="t") %>%
mutate(delta = debt - debt_base) %>% filter(scn != "Baseline")
ggplot(libra_delta, aes(t, delta)) +
geom_hline(yintercept=0, color="grey70") +
geom_line() +
facet_wrap(~scn, ncol=2, scales="free_y") +
labs(title="Dette/PIB — écarts au baseline", x="Horizon (t)", y="Δ dette (pts)")
# --- 10) Sous-périodes (ex. avant/après 2008 si Year dispo)
try({
spec <- formula(ecm_debt)
sp1 <- debt_df %>% filter(is.finite(Year), Year < 2008)
sp2 <- debt_df %>% filter(is.finite(Year), Year >= 2008)
ok1 <- try(nrow(na.omit(model.frame(spec, sp1)))>30, silent=TRUE)
ok2 <- try(nrow(na.omit(model.frame(spec, sp2)))>30, silent=TRUE)
if (!inherits(ok1,"try-error") && !inherits(ok2,"try-error") && ok1 && ok2) {
cat("\n--- ECM Dette — sous-période <2008 ---\n"); print(summary(lm(spec, data=sp1, na.action=na.omit)))
cat("\n--- ECM Dette — sous-période ≥2008 ---\n"); print(summary(lm(spec, data=sp2, na.action=na.omit)))
}
}, silent=TRUE)
# --- 11) Exports
if (!dir.exists("outputs")) dir.create("outputs")
readr::write_csv(libra, "outputs/sim_dette_trajectoires.csv")
readr::write_csv(libra_delta, "outputs/sim_dette_ecarts.csv")
coef_tbl <- tibble(term = names(cf), estimate = unname(cf))
readr::write_csv(coef_tbl, "outputs/ecm_dette_coeffs.csv")
}
##
## === ECM Dette (ΔTDP) — résumé ===
##
## Call:
## lm(formula = f, data = debt_df, na.action = na.omit)
##
## Residuals:
## Min 1Q Median 3Q Max
## -5.4055 -0.4886 -0.0997 0.4799 6.0655
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -0.685537 0.416973 -1.644 0.10156
## Ldebt -0.009456 0.006929 -1.365 0.17373
## Lrmg_pp_a 0.030621 0.009425 3.249 0.00134 **
## drmg_pp_a 0.020855 0.007881 2.646 0.00872 **
## L(d_debt_1) -0.091958 0.063161 -1.456 0.14681
## Lsp -0.022230 0.003601 -6.174 3.08e-09 ***
## dsp -0.065983 0.009891 -6.671 1.95e-10 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 1.088 on 225 degrees of freedom
## (1 observation effacée parce que manquante)
## Multiple R-squared: 0.3644, Adjusted R-squared: 0.3475
## F-statistic: 21.5 on 6 and 225 DF, p-value: < 2.2e-16
##
##
## Long terme (pp de dette par pt de PIB): -2.351 × ΔSP
## Long terme (pp de dette par pp/an de r−g): 3.238 × Δ(r−g)
## Impact CT d’un ΔSP_t (pt PIB) : -0.066
## Impact CT d’un Δ(r−g)_t (pp/an): 0.021
library(knitr)
cf <- coef(ecm_debt)
lambda <- (cf[["Ldebt"]] %||% cf[["Ldebt2"]]) %||% 0
beta_r <- cf[["Lrmg_pp_a"]] %||% NA_real_
beta_s <- if ("Lsp" %in% names(cf)) cf[["Lsp"]] else NA_real_
gamma_sp <- cf[["dsp"]] %||% NA_real_
gamma_rg <- cf[["drmg_pp_a"]] %||% NA_real_
res_tab <- data.frame(
Indicateur = c("Impact CT ΔSP (pt PIB)",
"Impact CT Δ(r−g) (pp/an)",
"LT ΔDette/ΔSP",
"LT ΔDette/Δ(r−g)"),
Valeur = c(round(gamma_sp,3),
round(gamma_rg,3),
ifelse(lambda!=0, round(-beta_s/lambda,3), NA),
ifelse(lambda!=0, round(-beta_r/lambda,3), NA))
)
kable(res_tab, caption = "Multiplicateurs de la dette publique (ECM)")
Indicateur | Valeur |
---|---|
Impact CT ΔSP (pt PIB) | -0.066 |
Impact CT Δ(r−g) (pp/an) | 0.021 |
LT ΔDette/ΔSP | -2.351 |
LT ΔDette/Δ(r−g) | 3.238 |
set.seed(123)
sim_boot <- replicate(500, {
resids <- sample(residuals(ecm_debt), size=20, replace=TRUE)
sim <- simulate_debt_paths(T=20, shock=list(type="none"))
sim$debt + c(0, cumsum(resids))
}, simplify=TRUE)
df_fan <- data.frame(
t = 0:20,
median = apply(sim_boot,1,median),
p10 = apply(sim_boot,1,quantile,0.1),
p90 = apply(sim_boot,1,quantile,0.9)
)
ggplot(df_fan, aes(t, median)) +
geom_ribbon(aes(ymin=p10,ymax=p90), fill="steelblue", alpha=0.3) +
geom_line(color="blue", size=1) +
labs(title="Dette/PIB — Fan chart (incertitude résiduelle)",
x="Horizon (t)", y="Dette / PIB")
Les bandes d’incertitude rappellent que les trajectoires simulées sont conditionnelles à la spécification retenue et aux hypothèses sur les chocs. Les résultats sont robustes qualitativement (signes, ordres de grandeur), mais la quantification exacte varie selon : - le choix de période d’estimation (ex. inclusion/exclusion de crises) ; - l’échelle des variables (pp/an vs décimal trimestriel) ; - d’éventuels régimes (changements structurels) mis en évidence par CUSUM.
simulate_rule <- function(d0, T=20, target=60, alpha=0.05){
debt <- numeric(T+1); debt[1] <- d0
for (t in 1:T){
s_rule <- alpha * (debt[t] - target)/100
d_debt_t <- -s_rule
debt[t+1] <- debt[t] + d_debt_t
}
tibble(t=0:T, debt=debt)
}
d0 <- tail(na.omit(D$debt),1)
sim_rule <- simulate_rule(d0, T=20, target=60, alpha=0.1)
ggplot(sim_rule, aes(t,debt)) +
geom_line(color="darkred", size=1) +
labs(title="Scénario avec règle de consolidation (ancre 60 % PIB)",
x="Horizon", y="Dette/PIB")
library(reshape2)
mat <- expand.grid(d_sp = seq(-2,2,1), d_rmg = seq(-2,2,1))
mat$final_debt <- sapply(1:nrow(mat), function(i){
sim <- simulate_debt_paths(T=20,
shock=list(type="permanent",
d_sp=mat$d_sp[i],
d_rmg=mat$d_rmg[i]))
tail(sim$debt,1)
})
ggplot(mat, aes(d_rmg, d_sp, fill=final_debt)) +
geom_tile() +
scale_fill_viridis_c() +
labs(title="Dette finale (t+20) selon chocs permanents",
x="Δ(r−g) (pp/an)", y="ΔSP (pt PIB)", fill="Dette/PIB")
Les résultats économétriques confirment que : - Le solde primaire joue un rôle déterminant : une amélioration de 1 pt de PIB réduit significativement la dette à court terme et de manière amplifiée à long terme. - L’écart \(r-g\) accroît fortement la dette : un taux d’intérêt supérieur à la croissance pèse mécaniquement sur la dynamique de dette. - L’ajustement (coefficient sur la dette retardée) suggère que le système converge, mais lentement, vers une trajectoire d’équilibre.
Ces constats rejoignent la littérature sur la soutenabilité de la dette publique (Bohn, 1998 ; Escolano, FMI 2010).
Historique France. Les épisodes de hausse rapide de dette/PIB coïncident avec des \((r-g)\) défavorables et/ou des déficits primaires persistants (chocs pétroliers, crise financière mondiale, Covid). Les phases d’accalmie reflètent des périodes où la croissance et l’inflation ont allégé le poids de la dette (r<g) ou des efforts budgétaires soutenus.
Comparaison internationale (idée à développer si données disponibles) : l’élasticité de la dette à \((r-g)\) et au SP pour la France se situe généralement dans la fourchette observée en zone euro, avec des différences dues à la structure d’échéances, à la composition de la dépense/recette et à la dynamique de croissance potentielle.
Historiquement, la soutenabilité de la dette française a reposé sur : - des épisodes de \(r < g\) (années 1950-1970, post-Covid avec inflation élevée), - ou des consolidations budgétaires (années 1990 avec Maastricht).
En comparaison avec d’autres pays de la zone euro, la France présente : - une sensibilité élevée aux variations de \(r-g\), - mais une capacité de stabilisation proche de la moyenne, sous réserve de discipline budgétaire.
Ces résultats suggèrent que la France est « moyenne » en termes de soutenabilité : pas la plus vulnérable, mais pas la plus robuste non plus.
Les simulations illustrent plusieurs enseignements : - Choc budgétaire permanent positif : l’effet cumulé permet de ramener la dette sur une trajectoire soutenable, mais la baisse est progressive, reflétant l’inertie du stock. - Choc budgétaire négatif : une dégradation de 1 pt de PIB du solde primaire entraîne une hausse quasi symétrique de la dette, ce qui met en évidence la symétrie des effets. - Choc de \(r-g\) : un simple +1 pp/an sur le différentiel taux-croissance se traduit par plusieurs points de dette supplémentaires, soulignant la sensibilité de la France aux conditions financières. - Amélioration temporaire : un effort de 1 pt de PIB limité à un an ne modifie pas durablement la trajectoire, sauf si répété ou consolidé dans le temps.
En résumé, seule une action budgétaire durable permet de compenser un \(r-g\) défavorable.
L’analyse met en évidence que les signes et ordres de grandeur des coefficients sont stables, mais les estimations ponctuelles varient selon : - la période d’estimation (avant ou après 2008, la crise financière modifie les paramètres), - l’échelle retenue pour mesurer \(r-g\) (décimal trimestriel ou pp/an), - les hypothèses de choc dans les simulations.
Cela souligne la nécessité de prudence dans l’interprétation : les trajectoires projetées sont des ordres de grandeur, pas des prévisions exactes.
Implication politique : une stratégie crédible combinant ancrage budgétaire (efforts ciblés et durables) et politiques pro-croissance (productivité, investissement, emploi) est nécessaire pour stabiliser puis réduire la dette/PIB, surtout en contexte de normalisation des taux.