Ateliers R Trucs et Astuces

Introduction à la modélisation statistique et aux simulations de données

Raphaël Royauté

INRAE UMR EcoSys

5/12/24

Packages nécessaires

library(tidyverse); library(patchwork)
library(easystats); library(tictoc); 
library(progress); library(viridis)
library(marginaleffects); library(broom)
library(palmerpenguins)

# style graphique
theme_set(theme_bw(16))

Objectifs

  • Apprendre à définir un modèle générateur de données
  • Maîtriser les fonctions de base pour simuler des données

Petit rappel

5 étapes pour simuler ses données

  1. Définir la question biologique et la quantité à estimer

  2. Convertir en modèle linéaire

  3. Simuler les données depuis (2)

  4. Valider le modèle

  5. Appliquer aux données

Exercice 1

Simulez des données sur la base du modèle suivant: \[y = N(\mu, \sigma)\] à l’aide de la fonction rnorm(n = , mean = , sd = )

  • Calculez la moyenne (mean()) et l’écart type (sd())
  • Représentez les données sous forme d’histogramme (hist())
  • Faites variez la taille d’échantillon n

Exercice 2

Simulez des données avec deux groupes sur la base du modèle suivant: \[y_i = N(\mu_i, \sigma) \\ \mu_i = \beta_0 + \beta_1 \times x_i \\ x_i = \begin{cases} 0, & i = 1 \\ 1, & i = 2 \\ \end{cases} \]

  • Calculez la moyenne (mean()) et l’écart type (sd()) par groupe
  • Extraire les coefficients avec lm() et summary()
  • Faites variez la taille d’échantillon n

Biais, précision et variance d’échantillonage

  • La variance d’échantillonage diminue avec la taille d’échantillon

  • Forte probabilité d’obtenir une valeure biaisée avec une faible taille d’échantillon

Biais, précision et variance d’échantillonage

Code
iter = 100
n = c(5:200)
b0 = 100
b1 = 10
sigma = 10
counter = 0 # compteur à incrémenter de 1 à chaque itération

out = matrix(NA, nrow = iter*length(n), ncol = 7)
colnames(out) = c("iter", # numéro de simulation
                  "n", # nombre de mesures par groupe
                  "b1_sim", # différence simulée
                  "b1_est", # différence estimée
                  "p", # p-value
                  "diff_est", # erreur de mesure (est - sim)
                  "perc_diff" # % erreur (est - sim) / sim * 100
)

# Barre de progression
pb <- progress_bar$new(
  format = "  downloading [:bar] :percent eta: :eta",
  total = nrow(out), clear = FALSE, width= 60)

set.seed(42)

for (i in 1:iter) {
  for (reps in 1:length(n)) {
    pb$tick()
    Sys.sleep(1 / nrow(out))
    
    counter = counter + 1
    
    # Génération du jeu de données simulées
    dfsim = data.frame(Id = 1:(n[reps]*2),
                       groupe = c(rep("1", n[reps]),
                                  rep("2", n[reps])))
    dfsim$xi = ifelse(dfsim$groupe == "1", 0, 1)
    dfsim = dfsim %>% 
      mutate(y = rnorm(n(), b0 + b1 * xi, sigma))
    
    # modèle statistique
    lm.sim = lm(y ~ groupe, dfsim)
    b1_est = as.numeric(coef(lm.sim)[2]) # valeur de b1 estimée
    p = p_value(lm.sim)$p[2] # p-value pour b1
    diff_est = b1_est - b1
    perc_diff = diff_est / b1 * 100
    
    # stockage des données à la ième ligne de la matrice 'out'
    out[counter, 1] = i # numéro de simulation
    out[counter, 2] = n[reps] # nombre de mesures par groupe
    out[counter, 3] = b1 # différence simulée
    out[counter, 4] = b1_est # différence estimée
    out[counter, 5] = p # p-value
    out[counter, 6] = diff_est # erreur de mesure (est - sim)
    out[counter, 7] = perc_diff # % erreur (est - sim) / sim * 100
  }
  
}

out %>% 
  data.frame() %>% 
  ggplot(aes(x = n, y = b1_est, fill = n)) +
  geom_point(alpha = .9, size = 2, shape = 21, color = "black") +
  geom_hline(yintercept = b1, linetype = "dashed", size = 1.5) +
  scale_fill_viridis(option = "H", direction = -1) +
  xlab("Taille d'echantillon par groupe") + 
  ylab("b1")

Puissance et typologie des erreurs

  • Puissance : probabilité de détecter un effet lorsqu’il existe
  • Erreur de type I : détecter un effet inexistant
  • Erreur de type II : rejeter un effet existant
  • Erreur de type S : se tromper dans le signe de l’effet
  • Erreur de type M : se tromper dans l’intensité de l’effet

Puissance et typologie des erreurs

Synthèse des différents types d’erreur statistiques pouvant être étudiées par simulation
Métrique Définition
Puissance Proportion des simulations avec des effets significatifs pour un \(\alpha\) donné
Taux de faux positifs Proportion des simulations avec des effets significatifs lorsque les données sont simulées sous l’hpothèse nulle
Couverture statistique % des simulations pour lesquelles l’intervalle de confiance inclue la valeure simulée
Erreur de type M moyenne de la valeure absolue des estimés significatifs pondérée par la taille d’effet
Erreur de type S % d’estimés du signe opposé à l’effet

Visualisation de l’erreur de type M & S

Code
out = as.data.frame(out)
out = out %>% 
  mutate(S = ifelse(b1_est < 0, 1, 0),
           M = ifelse(abs(b1_est - b1_sim) / b1_sim > .2,
                      1, 0))

p1 = out %>% 
  ggplot(aes(x = n, y = b1_est, fill = factor(M))) +
  geom_point(alpha = .9, size = 2, shape = 21) +
  geom_hline(yintercept = b1, linetype = "dashed", size = 1.5) +
  scale_fill_manual(values = c("white", "red")) +
  xlab("Taille d'echantillon par groupe") + 
  ylab("b1") +
  theme(legend.position = "none") +
  labs(title = "Erreur de type M",
       subtitle = "Estimés > 20 % d'erreur")

p2 = out %>% 
  ggplot(aes(x = n, y = b1_est, fill = factor(S))) +
  geom_point(alpha = .9, size = 2, shape = 21) +
  geom_hline(yintercept = b1, linetype = "dashed", size = 1.5) +
  scale_fill_manual(values = c("white", "red")) +
  xlab("Taille d'echantillon par groupe") + 
  ylab("b1") +
  theme(legend.position = "none") +
  labs(title = "Erreur de type S")

p1 + p2

Taille du bec x taille des ailes chez les manchots

Définir la question

Question scientifique

???

Mettre en équations

Solution - Modèle \[ bill_i = N(\mu_i, \sigma) \\ \mu_i = \beta_0 + \\ \beta_1 \times (flipper - flipper_{\mu}) + \\ \beta_2 \times Sp_{[i = 2]} + \beta_3 \times Sp_{[i = 3]} +\\ \beta_4 \times (flipper - flipper_{\mu}) \times Sp_{[i = 2]} \\ \beta_5 \times (flipper - flipper_{\mu}) \times Sp_{[i = 3]} \]

Coder le modèle

n = 50
sigma_bill = 2.5
sigma_flipper = 5
Sp = c("Ad", "Ch", "Gen")
b0 = 40 # moyenne bec manchots d'Adélie
b1 = 0.1 # pente bec x aile Adélie
b2 = 10 # différence  bec Chinstrap - Adélie
b3 = 2 # différence  bec Gentoo - Adélie
b4 = .1 # différence de pente Chinstrap - Adélie
b5 = .2 # différence de pente Gentoo - Adélie

g0 = 190
g1 = 5
g2 = 30

df = crossing(n = 1:n, Sp)
df$xi_2 = ifelse(df$Sp == "Ch", 1, 0)
df$xi_3 = ifelse(df$Sp == "Gen", 1, 0)

set.seed(42)
df = df %>% 
  mutate(flipper_mu = 200,
         flipper = rnorm(n(), g0 + g1 * xi_2 + g2 * xi_3, sigma_flipper)) %>%
  mutate(flipper_cen = flipper -  flipper_mu) %>% 
  mutate(bill = rnorm(n(), 
                      b0 + 
                        b1 * flipper_cen +
                        b2 * xi_2 + 
                        b3 * xi_3 +
                        b4 * flipper_cen * xi_2 +
                        b5 * flipper_cen * xi_3,
                      sigma_bill))

Coder le modèle

Exercice 3

  • Tester le modèle statistique avec les fonctions lm() et summary()

  • Utilisez la fonction check_model() pour évaluer la pertinence du modèle sur les données simulées

Inspecter le modèle

Code

lm.penguins = lm(bill ~ 1 + flipper_cen * Sp, df)
model_parameters(lm.penguins)
Parameter              | Coefficient |   SE |         95% CI | t(144) |      p
------------------------------------------------------------------------------
(Intercept)            |       39.71 | 0.77 | [38.19, 41.24] |  51.44 | < .001
flipper cen            |        0.06 | 0.07 | [-0.07,  0.20] |   0.97 | 0.335 
Sp [Ch]                |       10.81 | 0.93 | [ 8.97, 12.64] |  11.67 | < .001
Sp [Gen]               |        1.98 | 1.63 | [-1.25,  5.21] |   1.21 | 0.228 
flipper cen × Sp [Ch]  |        0.28 | 0.10 | [ 0.09,  0.47] |   2.87 | 0.005 
flipper cen × Sp [Gen] |        0.25 | 0.10 | [ 0.06,  0.44] |   2.65 | 0.009 

Inspecter le modèle

Code

plot(model_parameters(lm.penguins))

Inspecter le modèle

Code

estimate_means(lm.penguins)
Estimated Marginal Means

Sp  |  Mean |   SE |         95% CI
-----------------------------------
Ad  | 39.81 | 0.86 | [38.10, 41.52]
Ch  | 51.04 | 0.60 | [49.86, 52.22]
Gen | 42.17 | 1.34 | [39.53, 44.82]

Marginal means estimated at Sp

Inspecter le modèle

Code

check_model(lm.penguins)

Effets du cuivre et de la température sur la biomasse des organismes du sol

Hypothèses :

  • Une augmentation de température du sol modérée (~2°C) stimule la croissance

  • Les pics de températures sont très néfastes aux organismes du sol

  • La contamination au cuivre a un effet modéré sur la biomasse des organismes du sol

  • La présence de cuivre combinée lors d’un pic de température amplifie la perte de biomasse

Exercice 4

Simuler un modèle à partir des informations suivantes Design expérimental

  • 3 réplicats / conditions

  • 2 niveaux de contamination : contrôles et 80 mg / kg

  • 3 températures : 18°, 20° et 28°

Données connues

  • Biomasse à 18° : 500 mg

  • Biomasse à 20° : 550 mg

  • Biomasse à 28° : 350 mg

BONUS : Test d’équivalence

  • Région d’équivalence = +/- 5 unités
  • p(Equiv) > 0.05 -> l’effet n’est pas dans la région d’équivalence
Code
n = 10
sigma = 10
b0 = 100
b1 = 50
Eq = 5 # Région d'équivalence = +/- 5

groupe = c("1", "2")

df = data.frame(Rep = 1:(n[1]*2),
                groupe = c(rep("1", n[1]),
                           rep("2", n[1])))
df$xi = ifelse(df$groupe == "1", 0, 1)

set.seed(42)
df = df %>% 
  mutate(y = rnorm(n(), b0 + b1 * xi, sigma))

lm.eq = lm(y ~ groupe, df)
# pred.b1 = avg_slopes(lm.eq, variables = "groupe")
hyp = hypotheses(lm.eq, 
                 equivalence = 
                   c(- Eq, Eq))
hyp[2]

 Estimate Std. Error   z Pr(>|z|)    S 2.5 % 97.5 % p (NonSup) p (NonInf)
     42.9       5.79 7.4   <0.001 42.8  31.5   54.2          1     <0.001
 p (Equiv)
         1

Term: groupe2
Columns: term, estimate, std.error, statistic, p.value, s.value, conf.low, conf.high, statistic.noninf, statistic.nonsup, p.value.noninf, p.value.nonsup, p.value.equiv 

BONUS : Test d’équivalence

  • Région d’équivalence = +/- 5 unités
  • p(Equiv) > 0.05 -> l’effet n’est pas dans la région d’équivalence
Code
hyp[2] %>% 
    data.frame() %>% 
    ggplot(aes(y = term, x = estimate)) +
    geom_point() +
    geom_pointrange(aes(xmin = conf.low, xmax = conf.high)) +
    geom_rect(aes(xmin = -Eq, xmax = Eq, ymin = 0, ymax = 2), , fill = "grey", alpha = .6)

BONUS : Analyse de puissance

Code
out %>% 
  mutate(sig = case_when(p < 0.05 ~ 1, p >= 0.05 ~ 0)) %>%
  group_by(n) %>% 
  summarise(sig.prop = sum(sig)/n() * 100) %>% 
  ggplot(aes(x = n, y = sig.prop, fill = n)) +
  geom_line() +
  geom_point(alpha = .9, size = 3, shape = 21, color = "black") +
  geom_hline(aes(yintercept=80),linetype="dashed") +
  scale_fill_viridis(option = "H", direction = -1) +
  xlab("Taille d'echantillon par groupe") + 
  ylab("Puissance statistique")