Partie 2 Construction du modèle hiérarchique bayésien version 1

Author

NGUYEN Duc Hoang Viet

1 Construction du modèle hiérarchique bayésien

Pour l’année \(i\) et le site \(j\), on note \(Y_{i,j}\) le nombre d’occurrences du moustique tigre.

On propose le modèle hiérarchique suivant :

\[ Y_{i,j} \mid \lambda_i \sim \text{Poisson}(\lambda_i), \]

\[ \log(\lambda_i) \mid \mu, \sigma^2 \sim \mathcal{N}(\mu, \sigma^2). \]

On cherche à estimer \(\mu\) et \(\sigma^2\), et à prédire les \(\lambda_i\).Ces derniers, appelés effets aléatoires, représentent la moyenne (et également la variance) annuelle des occurrences.

Hyperparamètres : \(\sigma^2 \sim \text{Exp}(1)\), \(\mu \sim \mathcal{N}(0,4)\)

Reparamétrisation : \(\gamma = \log(\sigma^2)\), \(Z_i = \log(\lambda_i)\)

Il y a 5 fonction. Ce sont:

  • fonction log_posterior : calculer la log posterior
  • fonction les gradients : calculer les gradients de -log_postierior par les parametres \(Z_i\), \(\mu\), \(\gamma\)
  • fonction leapfrog. : calculer les steps pour algo HMC
  • fonction HMC_step : algo HMC
  • fonction run_chain : pour faire des chaines en même temps

2 Extraction des données

# ------------------------------------------------------------------------------
#                   CHARGEMENT ET PRÉ-TRAITEMENT DES DONNÉES
# ------------------------------------------------------------------------------
library(rgbif)
library(dplyr)
library(sf)
library(maps)
library(readr)

# ------------------------------------------------------------------------------
#                   RESUME DES DONNÉES
# ------------------------------------------------------------------------------

summary_table<- read_csv("data/summary_tigre_moustique.csv", show_col_types = FALSE)
df <- summary_table
df$i <- as.integer(factor(df$year))
df$j <- as.integer(factor(df$stateProvince))
df[, c("year", "stateProvince", "i", "j")]
# A tibble: 84 × 4
    year stateProvince                  i     j
   <dbl> <chr>                      <int> <int>
 1  2014 Languedoc-Roussillon           1    12
 2  2015 Provence-Alpes-Côte d'Azur     2    18
 3  2016 Provence-Alpes-Côte d'Azur     3    18
 4  2017 Languedoc-Roussillon           4    12
 5  2017 Midi-Pyrénées                  4    14
 6  2018 Languedoc-Roussillon           5    12
 7  2018 Midi-Pyrénées                  5    14
 8  2019 Aquitaine                      6     2
 9  2019 Corse                          6     8
10  2019 Languedoc-Roussillon           6    12
# ℹ 74 more rows
# ------------------------------------------------------------------------------
#                   CHARGEMENT ET PRÉ-TRAITEMENT DES DONNÉES
# ------------------------------------------------------------------------------
# Calcul des agrégats annuels (données synthétiques par année)
df_annual <- summary_table %>%
  group_by(year) %>%
  summarise(
    Y_sum = sum(n_occurrences),          # Nombre total de moustiques capturés par an
    J_counts = n_distinct(stateProvince) # Nombre de provinces ayant rapporté des données
  ) %>%
  arrange(year)

# Extraction sous forme de vecteurs pour le modèle
Y <- df_annual$Y_sum
J <- df_annual$J_counts
I <- nrow(df_annual) # Nombre total d'années d'observation (généralement 12)

3 La fonction de log-posteriori

log_posterior <- function(Z, Y, J, mu, gamma) { 
  sigma2 <- exp(gamma)
  
  # Terme 1: Vraisemblance de Poisson
  log_vrais <- sum(Y * Z - J * exp(Z))
  
  # Terme 2: Loi a priori conditionnelle des Zi (Loi Normale)
  log_prior_Z <- sum(dnorm(Z, mean = mu, sd = sqrt(sigma2), log = TRUE))
  
  # Terme 3: Loi a priori de μ, σ2
  log_prior_mu <- dnorm(mu, mean = 0, sd = 2, log = TRUE)
  log_prior_sigma2 <- dexp(sigma2, rate = 1, log = TRUE) + gamma 
  
  res <- log_vrais + log_prior_Z + log_prior_mu + log_prior_sigma2
  
  if (is.na(res) || is.infinite(res)) return(-1e20) 
  return(res)
}

4 Les fonctions de gradients

# ------------------------------------------------------------------------------
#       Gradient de l'Énergie Potentielle U(theta) = -log_posterior
# ------------------------------------------------------------------------------
# Calcul du gradient de l'énergie (nabla U) pour l'algorithme HMC.
# Rappel : U = -log(pi(theta|y)), nous implémentons ici les dérivées 
# partielles analytiques obtenues précédemment.
# ------------------------------------------------------------------------------

# Derivees partielles par rapport aux Zi (Vectorise)
grad_Z <- function(Z, Y, J, mu, gamma) {
  sigma2 <- exp(gamma)
  return(J * exp(Z) - Y + (Z - mu) / sigma2)
}

# # Derivee partielle par rapport à mu
grad_mu <- function(Z, mu, gamma) {
  sigma2 <- exp(gamma)
  return(sum(mu - Z) / sigma2 + mu / 4)
}

# Dérivée partielle par rapport à gamma (log-variance)
# Inclut la correction du Jacobien pour la reparamétrisation
grad_gamma <- function(Z, mu, gamma) {
  sigma2 <- exp(gamma)
  I <- length(Z)
  return(-sum((Z - mu)^2) / (2 * sigma2) + I / 2 + exp(gamma) - 1)
}

5 Algorithme Hamiltonian Monte Carlo avec méthode de leapfrog

# ------------------------------------------------------------------------------
#                 MOTEUR PHYSIQUE : HAMILTONIAN MONTE CARLO
# ------------------------------------------------------------------------------
#                          1. Algorithme Leapfrog
# Cette fonction simule la trajectoire hamiltonienne en mettant à jour
# alternativement les positions et les impulsions.
# ------------------------------------------------------------------------------

leapfrog <- function(Z, mu, gamma, P_Z, P_mu, P_gamma, Y, J, eps, L, f_grad_Z, f_grad_mu, f_grad_gamma) {
  
  # Premier demi-pas pour les impulsions (Momentum half-step)
  P_Z     <- P_Z     - 0.5 * eps * f_grad_Z(Z, Y, J, mu, gamma)
  P_mu    <- P_mu    - 0.5 * eps * f_grad_mu(Z, mu, gamma)
  P_gamma <- P_gamma - 0.5 * eps * f_grad_gamma(Z, mu, gamma)
  
  for (l in 1:L) {
    # Mise à jour des positions (Position update)
    Z     <- Z     + eps * P_Z
    mu    <- mu    + eps * P_mu
    gamma <- gamma + eps * P_gamma
    
    if (l < L) {
      # Mise à jour complète des impulsions (sauf pour le dernier pas)
      P_Z     <- P_Z     - eps * f_grad_Z(Z, Y, J, mu, gamma)
      P_mu    <- P_mu    - eps * f_grad_mu(Z, mu, gamma)
      P_gamma <- P_gamma - eps * f_grad_gamma(Z, mu, gamma)
    }
  }
  
  # Dernier demi-pas pour les impulsions (Final momentum half-step)
  P_Z     <- P_Z     - 0.5 * eps * f_grad_Z(Z, Y, J, mu, gamma)
  P_mu    <- P_mu    - 0.5 * eps * f_grad_mu(Z, mu, gamma)
  P_gamma <- P_gamma - 0.5 * eps * f_grad_gamma(Z, mu, gamma)
  
  return(list(Z = Z, mu = mu, gamma = gamma, P_Z = P_Z, P_mu = P_mu, P_gamma = P_gamma))
}
# ------------------------------------------------------------------------------
#              Fonction principal Hamiltonian Monte Carlo 
# ------------------------------------------------------------------------------
HMC_step_MC <- function(Z, mu, gamma, Y, J, eps, L, lp_func, f_grad_Z, f_grad_mu, f_grad_gamma) {
  
  # 1. Initialisation des impulsions aleatoire
  P_Z_init  <- rnorm(length(Z))
  P_mu_init <- rnorm(1)
  P_gamma_init <- rnorm(1)
  
  # 2. Calcul de l'énergie initiale (Hamiltonien initial)
  init_U <- -lp_func(Z, Y, J, mu, gamma)
  init_K <- sum(P_Z_init^2)/2 + P_mu_init^2/2 + P_gamma_init^2/2
  
  # 3. Simulation de la trajectoire avec l'intégrateur Leapfrog
  res <- leapfrog(Z, mu, gamma, P_Z_init, P_mu_init, P_gamma_init, Y, J, eps, L, f_grad_Z, f_grad_mu, f_grad_gamma)
  
  # 4. Calcul de l'énergie finale (Hamiltonien final)
  final_U <- -lp_func(res$Z, Y, J, res$mu, res$gamma)
  final_K <- sum(res$P_Z^2)/2 + sum(res$P_mu^2)/2 + sum(res$P_gamma^2)/2
  
  # 5. Étape d'acceptation de Metropolis-Hastings
  if (log(runif(1)) < (init_U + init_K - final_U - final_K)) {
    return(list(Z = res$Z, mu = res$mu, gamma = res$gamma, accepted = TRUE))
  } else {
    return(list(Z = Z, mu = mu, gamma = gamma, accepted = FALSE))
  }
}
# ------------------------------------------------------------------------------
# Fonction pour exécuter une chaîne MCMC (Hamiltonian Monte Carlo)
# ------------------------------------------------------------------------------
run_chain <- function(id, n_iter, Y, J, eps, L) {
  cat(sprintf("Exécution de la Chaîne %d...\n", id))
  
  # 1. Initialisation des paramètres (Valeurs de départ)
  # On utilise les données observées pour un démarrage proche de la zone cible
  curr_Z      <- log((Y / J) + 0.1)  
  curr_mu     <- mean(curr_Z)            
  curr_gamma  <- log(var(curr_Z) + 0.01) # Initialisation de la log-variance
  
  # 2. Préparation des structures de stockage (Traces)
  mu_trace    <- numeric(n_iter)
  sigma_trace <- numeric(n_iter)
  Z_trace     <- matrix(0, n_iter, length(Y)) # Stockage pour les 12 années
  lp_trace    <- numeric(n_iter)              # Évolution du log-posterior
  
  # Compteur pour le taux d'acceptation
  accepted_count <- 0
  
  # 3. Boucle principale d'échantillonnage
  for (k in 1:n_iter) {
    # Appel de l'étape HMC pour mettre à jour l'état de la chaîne
    res <- HMC_step_MC(curr_Z, curr_mu, curr_gamma, Y, J, eps, L, 
                       lp_func = log_posterior, 
                       f_grad_Z = grad_Z, 
                       f_grad_mu = grad_mu, 
                       f_grad_gamma = grad_gamma)
    
    # Mise à jour des valeurs actuelles (Acceptées ou Rejetées)
    curr_Z     <- res$Z
    curr_mu    <- res$mu
    curr_gamma <- res$gamma
    
    if (res$accepted) {
      accepted_count <- accepted_count + 1
    }
    
    # 4. Stockage des échantillons et transformation de gamma en sigma
    mu_trace[k]    <- curr_mu
    sigma_trace[k] <- sqrt(exp(curr_gamma)) # Retour à l'écart-type original
    Z_trace[k, ]   <- curr_Z
    
    # Calcul de la densité log-posterior pour le suivi de la convergence
    lp_trace[k]    <- log_posterior(curr_Z, Y, J, curr_mu, curr_gamma)  
    
    # 5. Affichage de la progression
    if (k %% (n_iter / 4) == 0) {
      current_acc_rate <- (accepted_count / k) * 100
      cat(sprintf("  Chaîne %d | Progression : %d / %d (%d%%) | Taux d'acc. : %.1f%%\n", 
                  id, k, n_iter, round(k/n_iter*100), current_acc_rate))
    }
  }
  
  # Retourne les résultats sous forme de liste
  final_acc_rate <- (accepted_count / n_iter) 
  cat(sprintf("=> Chaîne %d terminée avec succès. Taux d'acceptation final : %.2f%%\n", id, final_acc_rate* 100))
  
  return(list(mu = mu_trace, sigma = sigma_trace, Z = Z_trace, lp = lp_trace, acc_rate = final_acc_rate))
  #return(list(mu = mu_trace, sigma = sigma_trace, Z = Z_trace, lp = lp_trace))
}

6 DIAGNOSTICS DE CONVERGENCE ET PERFORMANCES

# ------------------------------------------------------------------------------
#   Main de lancement de la Simulation MCMC (Multi-chaînes)
# ------------------------------------------------------------------------------

# Configuration des hyperparamètres de l'algorithme HMC
n_chains <- 5          # Nombre de chaînes indépendantes pour tester la convergence
n_iter   <- 100000       # Nombre total d'itérations par chaîne
eps      <- 0.02       # Taille du pas (Step size) - à ajuster selon l'acceptation
L        <- 100        # Nombre de pas Leapfrog par itération

# Initialisation de la liste pour stocker les résultats de chaque chaîne
all_results <- list()

# Exécution des chaînes en boucle
set.seed(123) 
for (i in 1:n_chains) {
  start_time <- Sys.time() # Chronométrage pour estimer le temps total
  
  all_results[[i]] <- run_chain(i, n_iter, Y, J, eps, L)
  
  end_time <- Sys.time()
  cat(sprintf("Chaîne %d terminée en %.2f minutes.\n", i, as.numeric(end_time - start_time, units="mins")))
}
Exécution de la Chaîne 1...
  Chaîne 1 | Progression : 25000 / 100000 (25%) | Taux d'acc. : 98.6%
  Chaîne 1 | Progression : 50000 / 100000 (50%) | Taux d'acc. : 98.6%
  Chaîne 1 | Progression : 75000 / 100000 (75%) | Taux d'acc. : 98.6%
  Chaîne 1 | Progression : 100000 / 100000 (100%) | Taux d'acc. : 98.6%
=> Chaîne 1 terminée avec succès. Taux d'acceptation final : 98.62%
Chaîne 1 terminée en 0.96 minutes.
Exécution de la Chaîne 2...
  Chaîne 2 | Progression : 25000 / 100000 (25%) | Taux d'acc. : 98.6%
  Chaîne 2 | Progression : 50000 / 100000 (50%) | Taux d'acc. : 98.6%
  Chaîne 2 | Progression : 75000 / 100000 (75%) | Taux d'acc. : 98.6%
  Chaîne 2 | Progression : 100000 / 100000 (100%) | Taux d'acc. : 98.6%
=> Chaîne 2 terminée avec succès. Taux d'acceptation final : 98.60%
Chaîne 2 terminée en 0.96 minutes.
Exécution de la Chaîne 3...
  Chaîne 3 | Progression : 25000 / 100000 (25%) | Taux d'acc. : 98.7%
  Chaîne 3 | Progression : 50000 / 100000 (50%) | Taux d'acc. : 98.7%
  Chaîne 3 | Progression : 75000 / 100000 (75%) | Taux d'acc. : 98.7%
  Chaîne 3 | Progression : 100000 / 100000 (100%) | Taux d'acc. : 98.7%
=> Chaîne 3 terminée avec succès. Taux d'acceptation final : 98.68%
Chaîne 3 terminée en 0.96 minutes.
Exécution de la Chaîne 4...
  Chaîne 4 | Progression : 25000 / 100000 (25%) | Taux d'acc. : 98.7%
  Chaîne 4 | Progression : 50000 / 100000 (50%) | Taux d'acc. : 98.6%
  Chaîne 4 | Progression : 75000 / 100000 (75%) | Taux d'acc. : 98.7%
  Chaîne 4 | Progression : 100000 / 100000 (100%) | Taux d'acc. : 98.6%
=> Chaîne 4 terminée avec succès. Taux d'acceptation final : 98.62%
Chaîne 4 terminée en 0.95 minutes.
Exécution de la Chaîne 5...
  Chaîne 5 | Progression : 25000 / 100000 (25%) | Taux d'acc. : 98.6%
  Chaîne 5 | Progression : 50000 / 100000 (50%) | Taux d'acc. : 98.6%
  Chaîne 5 | Progression : 75000 / 100000 (75%) | Taux d'acc. : 98.6%
  Chaîne 5 | Progression : 100000 / 100000 (100%) | Taux d'acc. : 98.6%
=> Chaîne 5 terminée avec succès. Taux d'acceptation final : 98.63%
Chaîne 5 terminée en 0.95 minutes.
library(ggplot2)
library(tidyr)
library(dplyr)

# 1. Préparation des données pour ggplot2
# Extraction de la log-postériore pour toutes les chaînes de simulation
lp_data <- data.frame(
  Iteration = 1:n_iter,
  Chaine_1  = all_results[[1]]$lp,
  Chaine_2  = all_results[[2]]$lp,
  Chaine_3  = all_results[[3]]$lp,
  Chaine_4  = all_results[[4]]$lp,
  Chaine_5  = all_results[[5]]$lp
)

# Transformation des données du format large au format long pour ggplot2
lp_long <- lp_data %>%
  pivot_longer(cols = starts_with("Chaine"), 
               names_to = "Chaine", 
               values_to = "Log_Posterior")


# 2. Visualisation avec ggplot2
ggplot(lp_long, aes(x = Iteration, y = Log_Posterior, color = Chaine)) +
  geom_line(alpha = 0.8) +
  theme_minimal() +
  
  # Utilisation de scale_color_manual pour définir manuellement les couleurs
  scale_color_manual(
    values = c(
      "Chaine_1" = "red", "Chaine_2" = "blue",      
      "Chaine_3" = "darkgreen", "Chaine_4" = "orange",  "Chaine_5" = "purple" ),
    labels = paste("Chaîne", 1:5) # Mise à jour des noms affichés dans la légende
  ) +
  
  # Configuration des étiquettes et du titre du graphique
  labs(
    title = "Évolution de la Log-Posteriori (Diagnostics HMC)",
    subtitle = "Comparaison de la convergence des 5 chaînes MCMC",
    x = "Itérations",y = "Log-Posterior",color = "Légende"
  ) +
  
  # Personnalisation esthétique du thème
  theme(
    legend.position = "right",
    plot.title = element_text(face = "bold", size = 14),
    panel.grid.minor = element_blank()
  )

La figure montre une convergence rapide des cinq chaînes vers une même zone de forte densité. Leur superposition indique un excellent mélange et une exploration efficace de l’espace des paramètres.

La stabilité des trajectoires confirme que la phase de chauffe (burn-in) est suffisante et que les estimations postérieures obtenues sont fiables.

# ---------------------------------------------------------
#                   Main run 1 chain 
# ---------------------------------------------------------
n_chains <- 1
n_iter   <- 5000
eps      <- 0.02
L        <- 100

all_results <- list()

for (i in 1:n_chains) {
  start_time <- Sys.time()
  all_results[[i]] <- run_chain(i, n_iter, Y, J, eps, L)
  end_time <- Sys.time()
  cat(sprintf("Chaîne %d terminée en %.2f minutes.\n", i, as.numeric(end_time - start_time, units="mins")))
}
Exécution de la Chaîne 1...
  Chaîne 1 | Progression : 1250 / 5000 (25%) | Taux d'acc. : 99.1%
  Chaîne 1 | Progression : 2500 / 5000 (50%) | Taux d'acc. : 99.1%
  Chaîne 1 | Progression : 3750 / 5000 (75%) | Taux d'acc. : 99.0%
  Chaîne 1 | Progression : 5000 / 5000 (100%) | Taux d'acc. : 98.9%
=> Chaîne 1 terminée avec succès. Taux d'acceptation final : 98.88%
Chaîne 1 terminée en 0.06 minutes.
par(mfrow=c(2,2))

# Diagnostic de la Chaîne 1 : Analyse du Burn-in et de l'Autocorrélation
chaine <- all_results[[1]]$lp
chaine_after_burnin <- all_results[[1]]$lp[1001:5000]

# 1. Tracé de la trajectoire (Traceplot) de la Log-Posterior
plot(chaine, type="l", col="red", 
     main="Log-Posterior: Full vs Post-Burn-in", 
     xlab="Iteration", ylab="Log-Probability")

lines(1001:5000, chaine_after_burnin, col="darkorange", lwd=1.5)

# 2. Histogramme de la distribution de la Log-Posterior
hist(chaine,col="darkblue")

# 3. Analyse de l'autocorrélation avant Burn-in
acf(chaine, main="Avant Burn-In (Chain 1)")

# 4. Analyse de l'autocorrélation après Burn-in
acf(chaine_after_burnin, main="Apres Burn-In (Chain 1)")

L’analyse de la fonction d’autocorrélation (ACF) montre une diminution rapide de la corrélation entre les itérations successives. Après quelques lags, l’autocorrélation devient négligeable, indiquant que les échantillons générés sont quasiment indépendants.

Cette propriété témoigne de l’efficacité de l’algorithme HMC, qui permet une exploration plus rapide de l’espace des paramètres que les méthodes MCMC classiques. De plus, la stabilité observée après la phase de burn-in confirme que les échantillons utilisés pour l’inférence proviennent bien d’une distribution stationnaire.

# Plots
par(mfrow=c(2,2))

# Visualisation des paramètres globaux (Mu et Sigma) - Chaîne 1

# --- ANALYSE DE MU (Moyenne globale des log-intensités) ---
mu_trace <- all_results[[1]]$mu
plot(mu_trace, type="l" ,col="darkcyan", main="Traceplot mu")
hist(mu_trace, col="darkcyan",main="Posterior mu")

# --- ANALYSE DE SIGMA (Écart-type de la variabilité interannuelle) ---
sigma_trace <- all_results[[1]]$sigma
plot(sigma_trace, type="l",col="darkblue", main="Traceplot sigma")
hist(sigma_trace,col="darkblue", main="Posterior sigma")

# Visualisation des paramètres latents Zi
par(mfrow=c(1,2))

# Récupération de la trace des paramètres Z pour la première chaîne
Z_trace <- all_results[[1]]$Z

# 1. Nuage de points (Scatter plot) de tous les Z cumulés
plot(Z_trace, type="p",col="darkorange", main="Traceplot Z")

# 2. Histogramme global de la distribution des Z
hist(Z_trace,col = "darkorange", main="Posterior Z")

# Transformation des paramètres et construction de la matrice finale
# ------------------------------------------------------------------------------

# Conversion des log-intensités (Z) en intensités réelles (Lambda)
# On applique la fonction exponentielle pour revenir à l'échelle naturelle
Z_samples <- Z_trace
λ_samples <- exp(Z_samples)

# Regroupement des Lambda (12 colonnes), de Mu et de Sigma
samples_matrix <- cbind(λ_samples, mu_trace, sigma_trace )
n_l <- ncol(λ_samples)
colnames(samples_matrix) <- c(paste0("λ", 1:n_l), "μ", "σ^2")

head(round(samples_matrix,5))
          λ1      λ2      λ3      λ4      λ5      λ6       λ7      λ8      λ9
[1,] 1.36418 2.08942 2.05047 1.71138 1.13624 3.02476 10.04982 5.11309 7.81304
[2,] 3.83072 1.47090 1.97260 1.76831 3.21791 3.16393 10.56762 4.64051 7.97642
[3,] 1.22159 2.21965 1.68127 0.84491 1.40232 3.03692 10.06556 3.96697 8.42561
[4,] 3.62186 1.39417 1.38896 3.15475 2.71189 3.87901 10.49444 3.97176 7.56576
[5,] 1.54245 1.79993 1.71271 0.56607 0.59195 2.76289 10.01988 3.86077 7.03152
[6,] 2.90173 2.38533 1.76174 3.23028 2.07454 4.40099 10.12834 3.87640 6.80088
          λ10     λ11     λ12       μ     σ^2
[1,] 29.06131 8.01244 6.94032 1.42823 0.90337
[2,] 24.85536 8.38765 6.82949 1.46344 0.71434
[3,] 26.83229 8.73036 5.94960 1.27629 0.88761
[4,] 25.53980 7.69775 6.82758 1.70539 0.66579
[5,] 26.01189 9.07609 7.09337 1.30736 1.00804
[6,] 29.30472 7.20842 6.79573 1.68846 0.63991
# Visualisation des distributions postérieures pour chaque Lambda (Années 1-12)
# ------------------------------------------------------------------------------
par(mfrow=c(3, 4), mar=c(4, 4, 2, 1)) 

# 1. Détermination du nombre de paramètres lambda
n_lambda <- ncol(λ_samples) 

# 2. Boucle pour générer les histogrammes (après exclusion de la période de burn-in)
burn_in <- 1001
for (i in 1:n_lambda) {
  hist(λ_samples[burn_in:n_iter, i], 
       breaks = 30, 
       col = "skyblue", 
       border = "white",
       main = paste("Posterior", colnames(samples_matrix)[i]),
       xlab = expression(lambda),
       ylab = "Nombre")
  
 # Ajout d'une ligne verticale pour représenter la moyenne (Mean)
  abline(v = mean(λ_samples[burn_in:n_iter, i]), col = "red", lwd = 2)
}

# Réinitialisation de la disposition graphique par défaut (1x1)
par(mfrow=c(1, 1))

Les distributions postérieures des paramètres \(\lambda_1\) à \(\lambda_{12}\) montrent une augmentation progressive de l’intensité de présence du moustique tigre au cours du temps. Les estimations passent d’une faible valeur initiale (\(\lambda_1 \approx 2,5\)) à un niveau beaucoup plus élevé vers l’année 10 (\(\lambda_{10} \approx 26,5\)), mettant en évidence une forte variabilité inter-annuelle.

Par ailleurs, les distributions deviennent plus concentrées au fil des années, ce qui traduit une diminution de l’incertitude et une amélioration de la précision des estimations grâce à des données plus abondantes.

7 Estimateurs ponctuels et des intervalles de confiances pour chaque parametre

# Eliminer 1000 premiere ligne 
clean_samples <- samples_matrix[1001:5000, ]

# 1.Posterior Mean)
post_mean <- colMeans(clean_samples)

# 2. Interval de confiances 95% (95% Credible Interval)
post_ci <- apply(clean_samples, 2, quantile, probs = c(0.025, 0.975))

# 3. Met dans un tableau 
results <- data.frame(
  Parameter = colnames(clean_samples),
  Estimate = post_mean,
  CI_lower = post_ci[1, ],
  CI_upper = post_ci[2, ]
)

print(results)
    Parameter  Estimate   CI_lower  CI_upper
λ1         λ1  2.595188  0.6717871  5.702999
λ2         λ2  1.915954  0.4013943  4.735973
λ3         λ3  1.966777  0.3397732  5.111009
λ4         λ4  1.567801  0.4355213  3.444184
λ5         λ5  1.942655  0.6009258  4.046036
λ6         λ6  3.444607  2.0429837  5.175769
λ7         λ7 10.326771  8.0621939 12.899710
λ8         λ8  5.157979  3.7684199  6.915044
λ9         λ9  8.564633  6.9572531 10.348355
λ10       λ10 26.643701 23.9859650 29.559000
λ11       λ11  7.102151  5.8193101  8.542604
λ12       λ12  6.811094  5.7274463  7.978633
μ           μ  1.377365  0.7029029  1.990959
σ^2       σ^2  1.032254  0.6485090  1.597945
library(ggplot2)

# ------------------------------------------------------------------------------
# 7. Création du Forest Plot (Adapté à vos noms de colonnes)
# ------------------------------------------------------------------------------

# 1. Préparation de l'ordre des paramètres
# On utilise 'Parameter' car c'est le nom dans votre tableau results
results$Parameter <- factor(results$Parameter, levels = rev(results$Parameter))

# 2. Génération du graphique Forest Plot
ggplot(results, aes(x = Parameter, y = Estimate)) +
  
  # Barres d'erreur (on utilise CI_lower et CI_upper de votre tableau)
  geom_errorbar(aes(ymin = CI_lower, ymax = CI_upper), 
                width = 0.2, 
                color = "#E44A33", # Le rouge de l'image
                size = 0.8) + 
  
  # Points de la moyenne (on utilise Estimate de votre tableau)
  geom_point(size = 3, color = "#2C3E50") + 
  
  # Inversion des axes pour l'effet horizontal
  coord_flip() + 
  
  # Style visuel
  theme_light() +
  
  # Titres en français
  labs(
    title = "Variabilité Inter-Annuelle des Occurrences (Paramètres λ)",
    subtitle = "Moyenne a posteriori et Intervalles de crédibilité à 95%",
    x = "Effet aléatoire (Année)",
    y = "Nombre moyen d'occurrences estimé (λ)"
  ) +
  
  # Esthétique du texte
  theme(
    plot.title = element_text(face = "bold", size = 14, hjust = 0),
    plot.subtitle = element_text(size = 11, color = "grey30"),
    panel.grid.minor = element_blank(),
    axis.text.y = element_text(size = 10, face = "italic"),
    axis.title = element_text(size = 12)
  )

Les estimations a posteriori des paramètres \(\lambda_i\) montrent une augmentation marquée de l’intensité de présence du moustique tigre au cours du temps. Une transition nette apparaît entre les premières années (\(\lambda < 5\)) et les années 7 à 10, caractérisées par une forte croissance de l’abondance.

L’année 10 se distingue par une estimation particulièrement élevée (\(\hat{\lambda}_{10} \approx 26,6\)), influencée par l’observation d’un site ayant enregistré un nombre exceptionnel de moustiques. Malgré cette valeur extrême, le modèle hiérarchique fournit une estimation stable grâce au partage d’information entre les années.

Les intervalles de crédibilité à 95 % permettent d’évaluer l’incertitude des estimations. Les premières années présentent des intervalles plus larges, tandis que les années récentes bénéficient d’estimations plus précises, reflétant la disponibilité de données plus abondantes et une meilleure confiance statistique.

8 Analyser la qualité prédictive du modèle.

# Création de y_list : chaque élément de la liste est un vecteur contenant 
# le nombre d'occurrences (moustiques) par province pour chaque année.
y_list <- split(df$n_occurrences, df$i)

# Extraction des résultats de la Chaîne 1 (MCMC)
res_chain1 <- all_results[[1]]

# Élimination de la période de chauffe (burn-in, ex: 1000 premières itérations)
burn_in <- 1001
n_iter_total <- length(res_chain1$mu)
indices_kept <- burn_in:n_iter_total

# Création d'un dataframe contenant les valeurs de lambda_i (exp(Z_i))
# On transforme les échantillons de Z (espace log) en intensité réelle (lambda)
lambda_samples <- exp(res_chain1$Z[indices_kept, ])
colnames(lambda_samples) <- paste0("lambda_", 1:ncol(lambda_samples))

# Conversion en structure dataframe pour faciliter les analyses PPC suivantes
samples_df <- as.data.frame(lambda_samples)
library(ggplot2)

# --- 1. Préparation des données observées (Réalité) ---
y_obs <- unlist(y_list) # Liste des occurrences observées par an
stat_obs_zero <- mean(y_obs == 0) # Proportion réelle de zéros
stat_obs_max  <- max(y_obs)       # Valeur maximale réelle observée

# --- 2. Configuration de la simulation ---
n_samples_ppc <- 1000 
set.seed(42) # Pour la reproductibilité
indices_ppc <- sample(1:nrow(samples_df), n_samples_ppc)

rep_stat_zero <- numeric(n_samples_ppc)
rep_stat_max  <- numeric(n_samples_ppc)

I <- length(y_list) # Nombre d'années (12)
J_list <- sapply(y_list, length) # Nombre de sites par année

cat("Génération des données prédictives a posteriori en cours...\n")
Génération des données prédictives a posteriori en cours...
# --- 3. Boucle de simulation des données répliquées (y_rep) ---
for (t in 1:n_samples_ppc) {
    idx <- indices_ppc[t]
    y_rep_t <- numeric(0)
    
    for (i in 1:I) {
        # Extraction du lambda pour l'année i à l'itération t
        lambda_i_t <- samples_df[idx, paste0("lambda_", i)]
        
        # Simulation de nouvelles observations selon la loi de Poisson
        # J_list[i] représente l'effort d'échantillonnage de l'année i
        y_rep_t <- c(y_rep_t, rpois(J_list[i], lambda = lambda_i_t))
    }
    
    # Calcul des statistiques sur les données simulées
    rep_stat_zero[t] <- mean(y_rep_t == 0)
    rep_stat_max[t]  <- max(y_rep_t)
}

# --- 4. Calcul des P-values bayésiennes ---
# Si la p-value est proche de 0.5, le modèle est bien ajusté.
p_val_zero <- mean(rep_stat_zero >= stat_obs_zero)
p_val_max  <- mean(rep_stat_max >= stat_obs_max)

cat(sprintf("P-value bayésienne (Proportion de zéros) : %.3f\n", p_val_zero))
P-value bayésienne (Proportion de zéros) : 1.000
cat(sprintf("P-value bayésienne (Maximum observé) : %.3f\n", p_val_max))
P-value bayésienne (Maximum observé) : 0.000
# --- 5. Visualisation graphique ---
ppc_long <- data.frame(
    Valeur = c(rep_stat_zero, rep_stat_max),
    Statistique = rep(c("1. Proportion de sites sans moustiques (Zéro)", 
                        "2. Pire Invasion (Maximum observé)"), 
                      each = n_samples_ppc)
)

obs_lines <- data.frame(
    Valeur_obs = c(stat_obs_zero, stat_obs_max),
    Statistique = c("1. Proportion de sites sans moustiques (Zéro)", 
                    "2. Pire Invasion (Maximum observé)")
)

ppc_plot <- ggplot(ppc_long, aes(x = Valeur)) +
    geom_histogram(fill = "#95A5A6", color = "white", bins = 30, alpha = 0.8) +
    geom_vline(data = obs_lines, aes(xintercept = Valeur_obs), 
               color = "#E74C3C", linewidth = 1.2, linetype = "dashed") +
    facet_wrap(~ Statistique, scales = "free") +
    theme_minimal() +
    labs(
        title = "Vérification Prédictive a Posteriori (PPC)",
        subtitle = "L'histogramme gris représente les prédictions du modèle. La ligne rouge est la réalité observée.",
        x = "Valeur de la statistique",
        y = "Fréquence"
    ) +
    theme(
        strip.background = element_rect(fill = "#EFEFEF", color = NA),
        strip.text = element_text(face = "bold", size = 10)
    )

print(ppc_plot)

Les résultats montrent que le modèle tend à légèrement surestimer le nombre de sites sans moustiques, ce qui suggère une surestimation modérée de la probabilité d’absence. Cette différence peut être liée aux limites de la loi de Poisson pour représenter des données très dispersées.

Par ailleurs, le modèle sous-estime les événements extrêmes. La valeur maximale observée (252 moustiques) est largement supérieure aux valeurs simulées, indiquant qu’un épisode exceptionnel n’est pas entièrement capturé par le modèle.

Malgré cette limite, le modèle reproduit correctement la tendance générale et la variabilité inter-annuelle de l’invasion du moustique tigre.

V

MADE BY NGUYEN DUC HOANG VIET

PROJET MODÈLE HIÉRARCHIQUE BAYÉSIEN

© 2026 NGUYEN Duc Hoang Viet & Tchanang Yvan Michel — Tous droits réservés.
Université de Lille — Projet Modèle hiérarchique bayésien pour l’écologie
Toute reproduction, diffusion ou utilisation sans autorisation est interdite.