Ce TP introduit les modèles de durée sous R via les
outils classiques du package {survival}.
Nous utilisons les données pbc, issues d’un essai
clinique sur la cirrhose biliaire primitive.
À la fin du TP, vous devrez être capables de :
Surv() (durée +
censure) ;Surv()).
setup avec include = FALSE pour
charger les packages.
set.seed() unique au début pour rendre vos résultats
reproductibles.
Cette section présente les packages essentiels que vous utiliserez
tout au long du TP.
Ils permettent d’estimer, visualiser et interpréter les modèles de
durée.
{survival} — Le package indispensableLe package de base pour toute analyse de survie en
R.
Il fournit les briques fondamentales pour :
Dans un TP, c’est souvent le seul package vraiment
indispensable : les autres (survminer,
ggsurvfit…) servent surtout à améliorer les graphiques ou
la présentation.
{survival}Surv() — Construire l’objet de survieLa fonction fondamentale : elle encode pour chaque individu le couple \((t_i, \delta_i)\).
Principales variantes :
Censure à droite (cas le plus courant)
Données start–stop (processus en comptage)
Censure par intervalle
Dans l’affichage de
Surv, un+indique une observation censurée.
survfit() — Kaplan–Meier et hazard cumuléPermet d’estimer :
# Survie globale
km <- survfit(S ~ 1)
# Survie par groupe (ex. sexe)
km_sex <- survfit(S ~ sex, data = df)Pour le hazard cumulé (Nelson–Aalen) :
Les objets retournés contiennent : - time : temps
distincts où un événement a lieu ; - n.risk : nombre à
risque ; - n.event : nombre d’événements ; -
surv : survie estimée \(\hat
S(t)\) ; - std.err, lower,
upper : erreurs standard et bornes d’IC.
coxph() — Modèle de Cox à risques proportionnels (PH)Ajuste le modèle semi-paramétrique le plus utilisé en analyse de survie.
On obtient notamment :
exp(coef(cox1))
;survreg() — Modèles paramétriques et AFTPermet d’ajuster des modèles paramétriques ou de type Accelerated Failure Time (AFT).
Distributions usuelles :
"exponential""weibull""lognormal""loglogistic"Les coefficients de survreg() s’interprètent sur
l’échelle de \(\log(T)\), et leur
exponentielle donne des time ratios.
survdiff() — Test du log-rankCompare des courbes de survie entre groupes.
La sortie fournit une statistique de type \(\chi^2\). Le p-value associé se calcule par :
| Fonction | Rôle principal |
|---|---|
Surv() |
Créer l’objet de survie (durée + censure) |
survfit() |
Estimer KM et hazard cumulé (NA/FH) |
coxph() |
Ajuster un modèle de Cox (PH) |
cox.zph() |
Tester la proportionnalité des risques (PH) |
survreg() |
Modèles paramétriques / AFT (exp, Weibull) |
survdiff() |
Test du log-rank entre groupes |
{survminer} — Courbes de survie claires et lisiblesCe package est spécialisé dans la visualisation des
objets issus de {survival}.
ggsurvplot() : graphique Kaplan–Meier prêt à l’emploi
;ggcoxzph() : graphique du test PH (résidus de
Schoenfeld).{ggsurvfit} — Alternative moderne pour les courbes de
surviePackage léger, pensé pour les étudiants.
Il produit des courbes propres, lisibles et intuitives.
{cmprsk}
— Étudier les risques concurrentsQuand plusieurs événements possibles se font concurrence
(ex : décès vs greffe dans les données PBC),
ce package devient intéressant.
cuminc()).crr()).{gtsummary} — Produire des tableaux de résultatsTrès utilisé en biostatistique, ce package permet de transformer les sorties de modèle en tableaux propres et lisibles, au format publication.
| Package | À quoi sert-il ? |
|---|---|
survival |
Modèles de survie (KM, Cox, paramétriques). |
survminer |
Graphiques KM riches et lisibles. |
ggsurvfit |
Courbes KM simples, pédagogiques. |
cmprsk |
Risques concurrents (Fine–Gray). |
gtsummary |
Tableaux professionnels pour modèles de survie. |
S <- Surv(time, event)
survfit(S ~ 1)
survfit(S ~ groupe)
ggsurvplot()
survfit(S ~ 1, type = “fh”) +
ggsurvplot(…, fun = “cumhaz”)
coxph(S ~ X1 + X2, data = df)
cox.zph() (+ ggcoxzph())
survreg(S ~ X1 + X2, dist = “weibull”)
exp(coef(cox))
exp(coef(aft))
Objectif de cours : comprendre le couple \((t_i, \delta_i)\), le rôle de la censure, et comment cela se reflète dans le code
Surv().
La vraisemblance s’écrit alors :
\[
L(\theta)
= \prod_{\delta_i = 1} f(t_i \mid \theta)
\times
\prod_{\delta_i = 0} S(t_i \mid \theta).
\] Si event est mal codé → toute l’analyse est
biaisée.
Quelle est la variable de durée dans les données PBC ?
Quelle variable indique l’événement (décès) ?
Calculez la proportion de censures :
mean(df$status == 0)
Combien de modalités distinctes a la variable status
?
table(df$status)
→ Interprétez chaque modalité : laquelle correspond à la censure ? Au décès ? À une greffe ?
La variable time contient-elle des valeurs aberrantes
?
summary(df$time)
→ Y a-t-il des durées très courtes (< 5 jours) ou très longues (>
4000 jours) ?
Comment cela pourrait-il affecter une estimation
paramétrique ?
La censure semble-t-elle administrative ou liée à un mécanisme plus complexe ?
plot(df$time, df$status == 0)
→ Censures concentrées en fin de suivi → censure administrative.
→
Sinon → possible censure informative.
Quelles seraient les conséquences d’un codage incorrect de l’événement ?
Expliquez l’impact sur la courbe KM et sur un modèle de Cox.
Pourquoi delta_i doit-il être binaire (0/1 ou
TRUE/FALSE) ?
Que se passerait-il si on conservait les valeurs brutes 0 / 1 / 2 dans
Surv() ?
Combien d’individus sont observés avant 1000 jours ?
sum(df$time < 1000)
→ Expliquez pourquoi cela influence la précision de la KM au début du suivi.
Quelle est la médiane brute des durées (sans censure) ?
median(df$time)
→ Pourquoi cette médiane n’est-elle pas une médiane de survie ?
Peut-on détecter rapidement si la censure dépend d’une covariable ?
tapply(df$status == 0, df$sex, mean)
→ Si les différences sont fortes, que cela implique-t-il pour une analyse naïve ?
Surv() et première courbe Kaplan–MeierSurv()Surv()Surv()
encode pour chaque individu le couple \((t_i,
\delta_i)\) utilisé dans la vraisemblance : \[ L(\theta) = \prod_{\delta_i = 1} f(t_i \mid
\theta) \times \prod_{\delta_i = 0} S(t_i \mid \theta). \]
Surv, un + à droite d’un
temps indique une observation censurée.
S[1:10], repérez :
+).
event en 0/1 ou
TRUE/FALSE plutôt qu’en 0/1/2 ? Indice : que se passerait-il si
vous écriviez
S_bad <- Surv(time = df$time, event = df$status)
en gardant les 3 modalités de status ? Que voyez-vous dans
S_bad[1:10] ?
Surv
correspondant.
S dépend uniquement de deux vecteurs :
time et event. Donnez deux exemples de
situations (dans un autre contexte que PBC) où :
survfit() et ggsurvplot()km <- survfit(S ~ 1)
ggsurvplot(
km,
conf.int = TRUE,
risk.table = TRUE,
censor = TRUE,
ggtheme = theme_minimal(),
xlab = "Temps (jours)",
ylab = "Probabilité de survie"
)xlim = c(0, 3000) dans ggsurvplot). La
conclusion sur la médiane de survie et la forme globale de la courbe
change-t-elle ? Comment le choix de l’échelle de temps peut-il
influencer la perception visuelle des résultats ?
conf.int = FALSE. Est-ce plus « lisible » ? Quelles
informations importantes perdez-vous en supprimant les intervalles de
confiance ?
Objectif de cours : reconstruire pas à pas l’estimateur de Kaplan–Meier,
comprendre le rôle des quantités \(t_i, d_i, n_i\),
et visualiser comment elles déterminent toute la courbe de survie.
On commence par extraire un sous-échantillon de taille modérée pour garder des tables lisibles.
Questions :
event et is_cens ?On construit maintenant une table de survie où chaque ligne correspond à un temps où au moins un événement (décès) se produit.
On ne garde que les temps où il y a au moins un décès, car ce sont les seuls temps où la survie va “descendre”.
Questions :
n_i décroît quand le temps augmente.
Pourquoi est-ce logique ?n_i.n_i en ne
retirant que les décès et en ignorant les censures ?Le hazard discret au temps \(t_i\) est défini par :
\[ h_i = \frac{d_i}{n_i}. \]
La survie à la date \(t_i\) est obtenue comme produit-limite :
\[ S_i = \prod_{j : t_j \le t_i} (1 - h_j). \]
Questions :
On estime maintenant la courbe de Kaplan–Meier standard sur le même sous-échantillon et on la compare à la courbe reconstruite “à la main”.
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
## ℹ The deprecated feature was likely used in the ggpubr package.
## Please report the issue at <https://github.com/kassambara/ggpubr/issues>.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
## Ignoring unknown labels:
## • colour : "Strata"
Questions de synthèse :
survfit() implémente exactement le
produit-limite de Kaplan–Meier à partir des quantités \(t_i, d_i, n_i\) ?n_i devient petit, la courbe
reconstruite vous semble-t-elle plus instable ? Comment
l’interprétez-vous en termes d’information disponible ?Objectif : comprendre le lien entre hazard discret, hazard cumulé et survie,
et utiliser la forme de \(H(t)\) pour diagnostiquer l’évolution du hazard.
Le hazard cumulé estimé par Nelson–Aalen est défini par :
\[ \hat H(t_i) = \sum_{j : t_j \le t_i} \frac{d_j}{n_j}. \]
On peut l’interpréter comme une mesure cumulée du “risque instantané” accumulé dans le temps.
Questions :
ggplot(tab, aes(x = time, y = H_i)) +
geom_step(linewidth = 1) +
labs(
title = "Hazard cumulé de Nelson–Aalen (calcul à la main)",
x = "Temps",
y = "H(t)"
) +
theme_minimal()Questions d’interprétation :
Nelson–Aalen fournit une approximation de la survie :
\[ \tilde S(t) = \exp[-\hat H(t)]. \]
On compare cette approximation à la survie \(S_i\) obtenue via le produit-limite de Kaplan–Meier.
ggplot(tab, aes(x = time)) +
geom_step(aes(y = S_i), linewidth = 1) +
geom_step(aes(y = S_na), linetype = "dashed") +
labs(
title = "Survie KM (plein) vs exp(-H_NA) (pointillés)",
x = "Temps",
y = "Survie"
) +
theme_minimal()Questions :
Objectif de cours : comprendre le lien entre la forme du hazard, la forme de la survie, et les hypothèses implicites des modèles exponentiel et Weibull.
m_exp <- survreg(S ~ 1, data = df, dist = "exponential")
summary(m_exp)
# Dans survreg : scale = 1 / lambda
lambda_hat <- 1 / m_exp$scale
lambda_hat# Superposition KM vs modèle exponentiel
ggsurvplot(km, conf.int = FALSE) +
stat_function(fun = function(t) exp(-lambda_hat * t))m_weib <- survreg(S ~ 1, data = df, dist = "weibull")
summary(m_weib)
k <- 1 / m_weib$scale
lam <- exp(-m_weib$coefficients * k)
k
lamLiens avec les modèles paramétriques :
Si \(H(t)\) est à peu près linéaire, un modèle
exponentiel (hazard constant) est plausible.
Si \(H(t)\) est clairement convexe ou concave,
un modèle Weibull (hazard croissant ou décroissant) est souvent plus
adapté.
Objectif de cours : distinguer les deux grands cadres (PH vs AFT) et apprendre à lire HR vs TR.
cox1 <- coxph(S ~ age + sex + bili, data = df)
summary(cox1)
exp(coef(cox1)) # hazard ratios
exp(confint(cox1)) # intervalles de confiance sur HRRappel : modèle PH de Cox
\[
h(t \mid X) = h_0(t)\,\exp(X\beta)
\] Un HR > 1 signifie un risque instantané plus élevé
d’événement ; HR < 1, un risque plus faible.
med_exp
à la médiane de survie lue sur la KM (Ex1 / Ex2).
ggcoxzph() :
m_weib <- survreg(S ~ 1, data = df, dist = "weibull")
summary(m_weib)
# Dans survreg (dist = "weibull") :
# scale = 1 / k
k_hat <- 1 / m_weib$scale
# coef = log(lambda^{-1/k}) => lambda = exp(-coef * k)
lam_hat <- exp(-m_weib$coefficients * k_hat)
k_hat
lam_hat
# Médiane théorique Weibull
med_weib <- (log(2) / lam_hat)^(1 / k_hat)
med_weib
p_weib <- ggsurvplot(
km,
conf.int = FALSE,
risk.table = FALSE,
ggtheme = theme_minimal(),
xlab = "Temps (jours)",
ylab = "S(t)"
)
p_weib$plot +
stat_function(
fun = function(t) exp(-lam_hat * t^k_hat),
linetype = "dashed"
) +
labs(title = "Kaplan–Meier (plein) vs Weibull (pointillé)")med_weib à :
med_exp ;
Rappel : modèle AFT
\[
\log(T) = X\gamma + \varepsilon
\quad \Leftrightarrow \quad
T = \exp(X\gamma)\, T_0.
\] TR > 1 : durée plus longue ; TR < 1 : durée plus
courte.
Surv() correctement codé.