Introduction à la modélisation statistique et aux simulations de données
INRAE UMR EcoSys
2025-03-20
5 étapes pour simuler ses données
Définir la question biologique et la quantité à estimer
Convertir en modèle linéaire
Simuler les données depuis (2)
Valider le modèle
Appliquer aux données
Simulez des données sur la base du modèle suivant: \[y = N(\mu, \sigma)\] à l’aide de la fonction rnorm(n = , mean = , sd = )
mean()
) et l’écart type (sd()
)hist()
)n
Simulez des données avec deux groupes sur la base du modèle suivant: \[y_i = N(\mu_i, \text{ } \sigma) \\ \mu_i = \beta_0 + \beta_1 \times x_i \\ x_i = \begin{cases} 0, & i = 1 \\ 1, & i = 2 \\ \end{cases} \]
mean()
) et l’écart type (sd()
) par groupelm()
et summary()
n
marginaleffects::hypotheses()
# Définir les paramètres
n = 10 # Taille d'échantillon par groupe
b0 = 100 # moyenne du groupe 1 (référence)
b1 = 10 # différence de moyenne du groupe 2 vàv de la référence (contraste)
sigma = 10 # écart type
# Simuler les donnée
dfsim = data.frame(Id = 1:(n*2),
groupe = c(rep("1", n),
rep("2", n)))
dfsim$xi = ifelse(dfsim$groupe == "1", 0, 1)
dfsim = dfsim %>%
mutate(y = rnorm(n(), b0 + b1 * xi, sigma))
# Stocker le modèle et explorer les coefficients
mod <- lm(y ~ groupe, dfsim)
summary(mod)
Call:
lm(formula = y ~ groupe, data = dfsim)
Residuals:
Min 1Q Median 3Q Max
-29.0483 -4.7348 0.1901 6.5998 22.3351
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 96.995 3.487 27.82 3.04e-16 ***
groupe2 13.806 4.931 2.80 0.0118 *
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 11.03 on 18 degrees of freedom
Multiple R-squared: 0.3034, Adjusted R-squared: 0.2647
F-statistic: 7.84 on 1 and 18 DF, p-value: 0.01184
Hypothesis Estimate Std. Error z Pr(>|z|) S 2.5 % 97.5 %
(b1+b2)=5*b1 -374 17.8 -21 <0.001 324.2 -409 -339
La variance d’échantillonage diminue avec la taille d’échantillon
Forte probabilité d’obtenir une valeure biaisée avec une faible taille d’échantillon
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")
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 |
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
Un chercheur vous contacte pour un conseil statistique. Il constate que les mâle et femelles des manchots de sa station expérimentale ont des tailles très différentes. Il pense que les manchots d’Adélie expriment un plus faible dimorphisme de part un régime alimentaire plus similaire entre sexes. Ses premières observations semblent aller dans son sens. Attraper des manchots est difficile ! Il pense pouvoir capturer au maximum 10 individus de chaque sexe et espèce. As-t-il une bonne chance de détecter une différence de dimorphisme entre espèce ?
set.seed(42)
df <- penguins %>%
drop_na() %>%
group_by(sex, species) %>%
slice_sample(n = 5) %>%
summarise(mean_fl = mean(flipper_length_mm, na.rm = T),
sd_fl = sd(flipper_length_mm, na.rm = T)) %>%
mutate(N = 5) %>%
pivot_wider(names_from = sex, values_from = c(mean_fl, sd_fl)) %>%
mutate(Delta_mean = mean_fl_male - mean_fl_female) %>%
select(species, N,
mean_fl_female, sd_fl_female,
mean_fl_male, sd_fl_male,
Delta_mean) %>%
rename(Species = species,
# Females = mean_fl_female,
# Males = mean_fl_male,
Delta = Delta_mean)
df %>%
tt(digits = 3) %>%
setNames(c("", "", "mean",
"sd", "mean", "sd", "")) %>%
group_tt(j = list(
"Species" = 1,
"N" = 2,
"Females" = 3:4,
"Males" = 5:6,
"Delta" = 7))
Species | N | Females | Males | Delta | ||
---|---|---|---|---|---|---|
mean | sd | mean | sd | |||
Adelie | 5 | 185 | 2.83 | 192 | 5.4 | 7.2 |
Chinstrap | 5 | 189 | 7.98 | 200 | 3.35 | 11 |
Gentoo | 5 | 214 | 5.4 | 226 | 2.68 | 12.4 |
Question scientifique Les manchots d’Adélie ont-ils un dimorphisme sexuel plus faible comparé aux manchots Chinstrap et Gentoo?
Prédiction La différence male-femelle chez les manchots d’Adélie est plus faible que celle des deux autres espèces
Modèle \[ \begin{aligned} flipper_{i,j} &= N(\mu_{i,j}, \text{ } \sigma) \\ \mu_{i,j} &= \beta_0 + \\ &\beta_1 \times Sex_{[i = 1]} + \\ &\beta_2 \times Sp_{[j = 1]} + \beta_3 \times Sp_{[j = 2]} +\\ &\beta_4 \times Sex_{[i = 1]} \times Sp_{[j = 1]} \\ &\beta_5 \times Sex_{[i = 1]} \times Sp_{[j = 2]}\\ i = 0: Females&; i = 1: Males\\ j = 0: Adélie; j = 1&: Chintrap; j = 2: Adélie \end{aligned} \]
Quantité à estimer \[ \begin{aligned} &\text{Dimorphisme Adélie } = \beta_1 \\ &\text{Dimorphisme Chinstrap } = \beta_1 + \beta_4 \\ &\text{Dimorphisme Gentoo } = \beta_1 +\beta_5 \\ \end{aligned} \]
# Lister les paramètres
n = 10
sigma = 15
Sex = c("Females", "Males")
Sp = c("Ad", "Ch", "Gen")
mu_A_F = 185; mu_A_M = 192
mu_C_F = 189; mu_C_M = 200
mu_G_F = 214; mu_G_M = 226
b0 = mu_A_F # moyenne ailes manchots d'Adélie femelles
b1 = mu_A_M - mu_A_F # différence male - femelles Adélie
b2 = mu_C_F - mu_A_F # différence femelles Chinstrap - femelles Adélie
b3 = mu_G_F - mu_A_F # différence femelles Gentoo - femelles Adélie
b4 = mu_C_M - (b0+b1+b2) # interaction sexe = males x Sp = Chinstrap
b5 = mu_G_M - (b0+b1+b3) # interaction sexe = males x Sp = Gentoo
c(b0, b1, b2, b3, b4, b5)
[1] 185 7 4 29 4 5
# Generer le dataframe
df = crossing(n = 1:n, Sex, Sp) %>%
mutate(Sex_bool = ifelse(Sex == "Males", 1, 0),
Sp_bool_1 = ifelse(Sp == "Ch", 1, 0),
Sp_bool_2 = ifelse(Sp == "Gen", 1, 0))
# Générer les données
set.seed(42)
df = df %>%
mutate(flipper_length = rnorm(n(),
b0 +
b1 * Sex_bool +
b2 * Sp_bool_1 +
b3 * Sp_bool_2 +
b4 * Sex_bool * Sp_bool_1 +
b5 * Sex_bool * Sp_bool_2,
sigma))
# Graphique
df %>%
ggplot(aes(x = Sex, y = flipper_length, color = Sp, group = Sp)) +
geom_point(size = 4, alpha = .6) +
geom_smooth(method = "lm") +
facet_wrap(~Sp) +
scale_color_manual(values =
c("darkorange","darkorchid","cyan4")) +
theme(legend.position = "none")
lm()
et summary()
performance::check_model()
pour évaluer la pertinence du modèle sur les données simuléesmarginaleffects::hypotheses()
pour tester l’hypothèse d’un dimorphisme moins prononcé chez les manchots d’AdélieCode
Call:
lm(formula = flipper_length ~ 1 + Sex * Sp, data = df)
Residuals:
Min 1Q Median 3Q Max
-45.189 -8.141 3.241 10.351 33.711
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 186.5537 5.6682 32.913 < 2e-16 ***
SexMales 0.6877 8.0160 0.086 0.93195
SpCh 2.4627 8.0160 0.307 0.75986
SpGen 27.3535 8.0160 3.412 0.00123 **
SexMales:SpCh 10.5888 11.3363 0.934 0.35443
SexMales:SpGen 11.9939 11.3363 1.058 0.29476
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 17.92 on 54 degrees of freedom
Multiple R-squared: 0.4399, Adjusted R-squared: 0.388
F-statistic: 8.482 on 5 and 54 DF, p-value: 5.631e-06
Code
Parameter | Coefficient | SE | CI | CI_low | CI_high | t | df_error | p |
---|---|---|---|---|---|---|---|---|
(Intercept) | 186.55 | 5.7 | 0.95 | 175 | 198 | 32.913 | 54 | 0.0000000000000000000000000000000000002 |
SexMales | 0.69 | 8 | 0.95 | -15 | 17 | 0.086 | 54 | 0.9319468401391421963353423052467405796 |
SpCh | 2.46 | 8 | 0.95 | -14 | 19 | 0.307 | 54 | 0.7598588900983966887281439994694665074 |
SpGen | 27.35 | 8 | 0.95 | 11 | 43 | 3.412 | 54 | 0.0012273135022853084562938219548300367 |
SexMales:SpCh | 10.59 | 11.3 | 0.95 | -12 | 33 | 0.934 | 54 | 0.3544323133403803893770600552670657635 |
SexMales:SpGen | 11.99 | 11.3 | 0.95 | -11 | 35 | 1.058 | 54 | 0.2947633515564935247077471558441175148 |
Code
df_parms = data.frame(Parameter =
c("(Intercept)", "SexMales",
"SpCh", "SpGen", "SexMales:SpCh",
"SexMales:SpGen"),
Coefficient = c(b0, b1, b2, b3, b4, b5))
parms <- model_parameters(lm.penguins) %>% data.frame()
parms %>%
ggplot(aes(x = Coefficient, y = Parameter)) +
geom_pointrange(aes(xmin = CI_low, xmax = CI_high)) +
geom_point(data = df_parms,
aes(x = Coefficient,
y = Parameter,
fill = "red"),
shape = 23,
size = 2.5,
alpha = .6) +
theme(legend.position = "none")
Code
Code
Code
term | contrast | Sp | estimate | std.error | statistic | p.value | s.value | conf.low | conf.high |
---|---|---|---|---|---|---|---|---|---|
Sex | Males - Females | Ad | 0.69 | 8.02 | 0.09 | 0.93 | 0.10 | -15.02 | 16.40 |
Sex | Males - Females | Ch | 11.28 | 8.02 | 1.41 | 0.16 | 2.65 | -4.43 | 26.99 |
Sex | Males - Females | Gen | 12.68 | 8.02 | 1.58 | 0.11 | 3.14 | -3.03 | 28.39 |
p <- plot_comparisons(lm.penguins, variables = "Sex", condition = "Sp")
df_dim = data.frame(marginaleffects_term_index = rep("Sex", 3),
Sp = c("Ad", "Ch", "Gen"),
Estimate = c(mu_A_M - mu_A_F,
mu_C_M - mu_C_F,
mu_G_M - mu_G_F))
p +
geom_point(data = df_dim,
aes(x = Sp,
y = Estimate,
fill = "red"),
shape = 23,
size = 3,
alpha = .9) +
theme(legend.position = "none")
Code
Hypothesis Estimate Std. Error z Pr(>|z|) S 2.5 % 97.5 %
b2=(b5-b3) -7.44 24 -0.309 0.757 0.4 -54.6 39.7
Hypothesis Estimate Std. Error z Pr(>|z|) S 2.5 % 97.5 %
b2=(b6-b4) 16 24 0.667 0.505 1.0 -31.1 63.2
Hypothesis Estimate Std. Error z Pr(>|z|) S 2.5 % 97.5 %
(b5-b3)=(b6-b4) 23.5 17.9 1.31 0.19 2.4 -11.6 58.6
Faîtes une analyse de puissance pour calculer un plan d’échantillonnage optimal pour estimer une différence de dimorphisme d’au moins 20 % entre manchots d’Adélie et les deux autres espèces
OU
Simulez votre plan d’expérience
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")
Différences de survie entre espèces \[ \begin{aligned} Survie_i &= Binomial(n,p) \\ logit(p_i) &= \beta_0 + \\ &\beta_1 \times Sp_{[i = 1]} + \\ &\beta_2 \times Sp_{[i = 2]}\\ i = 0: Adélie; i = 1&: Chintrap; i = 2: Adélie \end{aligned} \]
Différences de survie entre espèces
n = 20
Sp = c("Ad", "Ch", "Gen")
b0 = plogis(-.4) # moyenne de survie manchots d'Adélie = 40 %
b1 = plogis(1) # différence de survie Chinstrap - Adélie = 64 %
b2 = plogis(1.5) # différence de survie Gentoo - Adélie
# Generer le dataframe
df <- crossing(n = 1:n, Sp) %>%
mutate(Sp_bool_1 = ifelse(Sp == "Ch", 1, 0),
Sp_bool_2 = ifelse(Sp == "Gen", 1, 0))
# Générer les données
set.seed(42)
df <- df %>%
mutate(logit_p = b0 +
b1 * Sp_bool_1 +
b2 * Sp_bool_2) %>% # Echelle logit
mutate(p = plogis(logit_p)) %>% # Echelle des probabilités
mutate(Survie = rbinom(n(), prob = p, size = 1))
glm.binom <- glm(Survie ~ Sp, df, family = "binomial")
model_parameters(glm.binom) # Coefficients
Parameter | Log-Odds | SE | 95% CI | z | p
-------------------------------------------------------------
(Intercept) | -0.41 | 0.46 | [-1.34, 0.48] | -0.89 | 0.374
Sp [Ch] | 1.25 | 0.67 | [-0.02, 2.62] | 1.87 | 0.061
Sp [Gen] | 1.25 | 0.67 | [-0.02, 2.62] | 1.87 | 0.061
Contrast Estimate Std. Error z Pr(>|z|) S 2.5 % 97.5 %
Ch - Ad 0.3 0.15 2 0.0455 4.5 0.00601 0.594
Gen - Ad 0.3 0.15 2 0.0455 4.5 0.00601 0.594
Term: Sp
Type: response
\[ \begin{aligned} Fécondité_i &= Poisson(\lambda_i) \\ log(\lambda_i) &= \beta_0 + \\ &\beta_1 \times Sp_{[i = 1]} + \\ &\beta_2 \times Sp_{[i = 2]}\\ i = 0: Adélie; i = 1&: Chintrap; i = 2: Adélie \end{aligned} \]
n = 20
Sp = c("Ad", "Ch", "Gen")
b0 = log(10) # moyenne de fécondité manchots d'Adélie = 10
b1 = log(2) # différence de fécondité Chinstrap - Adélie = +2
b2 = plogis(4) # différence de fécondité Gentoo - Adélie + 4
# Generer le dataframe
df <- crossing(n = 1:n, Sp) %>%
mutate(Sp_bool_1 = ifelse(Sp == "Ch", 1, 0),
Sp_bool_2 = ifelse(Sp == "Gen", 1, 0))
# Générer les données
set.seed(42)
df <- df %>%
mutate(log_lambda = b0 +
b1 * Sp_bool_1 +
b2 * Sp_bool_2) %>% # Echelle log
mutate(lambda = exp(log_lambda)) %>% # Echelle des comptages
mutate(Fecondite = rpois(n(), lambda = lambda))
glm.poiss <- glm(Fecondite ~ Sp, df, family = "poisson")
model_parameters(glm.poiss) # Coefficients
Parameter | Log-Mean | SE | 95% CI | z | p
-------------------------------------------------------------
(Intercept) | 2.31 | 0.07 | [2.17, 2.44] | 32.72 | < .001
Sp [Ch] | 0.73 | 0.09 | [0.57, 0.90] | 8.53 | < .001
Sp [Gen] | 1.05 | 0.08 | [0.89, 1.21] | 12.75 | < .001
Contrast Estimate Std. Error z Pr(>|z|) S 2.5 % 97.5 %
Ch - Ad 10.9 1.24 8.72 <0.001 58.3 8.41 13.3
Gen - Ad 18.5 1.39 13.34 <0.001 132.5 15.83 21.3
Term: Sp
Type: response
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
Term Estimate Std. Error z Pr(>|z|) S 2.5 % 97.5 % p (NonSup)
(Intercept) 105.5 4.10 25.7 <0.001 483.1 97.4 113.5 1
groupe2 42.9 5.79 7.4 <0.001 42.8 31.5 54.2 1
p (NonInf) p (Equiv)
<0.001 1
<0.001 1