Introduction et question de recherche

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

Données, périmètre et conventions

  • Fréquence : trimestrielle ; période 1963:1–2023:1.
  • Variable cible : Dette publique/PIB (TDP).
  • Explicatifs principaux : Solde primaire (SP) en points de PIB, et \((r-g)\) (taux d’intérêt effectif – croissance réelle), exprimé en points de pourcentage annuels lorsque spécifié.
  • Traitements : lissage simple de l’inflation attendue pour approcher le coût réel de financement, lags/retards pour capter l’inertie, et exclusion de quelques observations initiales pour stabiliser les régressions.
  • Choix de spécification : diverses formes d’ECM candidates sont comparées via un critère d’information (BIC). On retient la plus parcimonieuse compatible avec les données, puis on simule sur cette base.

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

Validation visuelle : observé vs ajusté

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

Résultats clés de l’ECM : interprétation économique

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

Lecture des scénarios et ordres de grandeur

  • SP +1 pt de PIB permanent : la trajectoire simulée montre une baisse progressive de dette/PIB, avec un effet qui s’accumule dans le temps. La vitesse dépend de l’inertie estimée (termes retardés) et de l’ampleur du coefficient d’ajustement.
  • SP −1 pt permanent : effet miroir, la dette augmente durablement.
  • \((r-g) +1\) pp/an permanent : la dette s’élève structurellement ; ce scénario illustre la sensibilité aux taux d’intérêt réels et à la croissance.
  • SP +1 pt temporaire (4 trimestres) : le gain est transitoire ; une fois le choc dissipé, la trajectoire revient vers le baseline, avec un résidu qui dépend de l’inertie du système.

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

Incertitude et robustesse

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

Discussion des résultats

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

Mise en perspective historique et internationale

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.

Perspective élargie

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.

Implications pratiques des scénarios

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.

Limites et pistes d’extension

  • Endogénéité potentielle du SP et de \((r-g)\) : un élargissement en VAR ou SVAR permettrait de tenir compte des rétroactions.
  • Mesure de \((r-g)\) : affiner le taux d’intérêt effectif (rendement apparent de la dette, structure d’échéances) et la croissance potentielle.
  • Hétérogénéité de chocs : différencier chocs permanents vs temporaire, et choc macro vs mesures discrétionnaires (one-offs).
  • Changements de régime : estimer des ECM à régimes (Markov-switching) ou changements structurels multiples.

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.

Conclusion — messages clés

  1. La dynamique de la dette/PIB en France est fortement inerte, mais très sensible au solde primaire et à \((r-g)\).
  2. Un ajustement budgétaire permanent de 1 point de PIB réduit la dette de plusieurs points à long terme (effet multiplicateur), toutes choses égales par ailleurs.
  3. Une dégradation durable de \((r-g)\) (taux plus élevés et/ou croissance plus faible) accroît la dette, pouvant annuler des efforts primaires modestes.
  4. Les scénarios montrent que des trajectoires de dette soutenables sont atteignables avec des combinaisons raisonnables d’effort primaire et de conditions macro favorables ; à l’inverse, sans amélioration du SP, un \((r-g)\) défavorable pèse lourdement.

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.