Exemple complet d’analyse de survie en R : Kaplan-Meier, log-rank, Cox — de A à Z

Un projet de bout en bout sur les données de cancer du poumon — poser la question, préparer les données, estimer les courbes de Kaplan-Meier et les médianes, exécuter le test du log-rank, ajuster et interpréter un modèle de Cox, vérifier les risques proportionnels, et rédiger le rapport prêt à publier

Une analyse de survie complète et détaillée en R de A à Z sur le jeu de données de cancer du poumon NCCTG. Posez la question et construisez la réponse Surv(), estimez les courbes de survie de Kaplan-Meier et la survie médiane selon le sexe, comparez les groupes avec le test du log-rank, ajustez des modèles de Cox univariés et multivariés à risques proportionnels et interprétez les hazard ratios, validez l’hypothèse des risques proportionnels avec cox.zph et ggcoxzph, et assemblez la figure prête à publier et le paragraphe de rapport — chaque étape reliée à la leçon qui l’enseigne.

Date de publication

26 juin 2026

Modifié

7 juillet 2026

AstucePoints clés
  • Ce capstone exécute une analyse de survie complète de bout en bout sur les données de cancer du poumon NCCTG — la séquence exacte que vous suivriez sur votre propre étude de temps jusqu’à l’événement, chaque étape étant reliée à la leçon qui l’enseigne en profondeur.
  • Le déroulé : poser la question → préparer les données (Surv) → courbes de Kaplan-Meier + survie médiane → test du log-rank → modèle de Cox (hazard ratios) → vérifier les risques proportionnels → rapporter.
  • Le résultat sur lung : survie médiane de 270 jours (hommes) vs 426 jours (femmes) ; le test du log-rank confirme l’écart (χ²(1) = 10.3, p = 0.001) ; un modèle de Cox multivarié donne HR ≈ 0.58 pour le sexe féminin (un risque inférieur de 42 %) et HR ≈ 1.59 pour le score ECOG (un risque supérieur de 59 % par point), l’âge disparaissant après ajustement.
  • L’hypothèse des risques proportionnels tient pour le modèle final (cox.zph GLOBAL p = 0.22), donc les hazard ratios sont fiables.
  • Deux packages font tout : survival (les modèles et les tests) et survminer (les figures prêtes à publier ggsurvplot, ggforest et ggcoxzph).

Introduction

Vous avez appris les éléments — courbes de Kaplan-Meier, le test du log-rank, le modèle de Cox, les diagnostics PH. Cette leçon est le projet qui les réunit : une analyse de survie unique et complète, des données brutes à un paragraphe que vous pourriez coller dans un article.

Nous répondrons à une question clinique sur les données de cancer du poumon NCCTG — quels facteurs liés au patient modifient le risque de décès, et de combien les femmes survivent-elles plus longtemps que les hommes ? — en parcourant le déroulé complet :

  1. Poser la question et préparer les données — construire la réponse Surv(time, status).
  2. Estimer la survie avec les courbes de Kaplan-Meier ; lire la survie médiane et son IC à 95 %.
  3. Comparer les sexes avec le test du log-rank.
  4. Modéliser l’effet de plusieurs covariables à la fois avec la régression de Cox — hazard ratios avec IC.
  5. Valider le modèle : tester l’hypothèse des risques proportionnels.
  6. Rapporter — la figure de publication et le paragraphe de synthèse.

Chaque étape est volontairement concise : la profondeur pédagogique vit dans les leçons reliées. Ici, vous les voyez fonctionner ensemble sur un même jeu de données jusqu’à un résultat fini. Deux packages portent toute l’analyse — survival (modèles et tests) et survminer (les figures).

Étape 1 — Poser la question et préparer les données

La question. Chez 228 patients atteints d’un cancer du poumon avancé, la survie diffère-t-elle selon le sexe, et quels facteurs cliniques (âge, statut de performance, perte de poids) augmentent ou diminuent le risque de décès ? L’issue est le temps jusqu’au décès, avec une censure pour les patients encore en vie au dernier suivi — exactement le cadre pour lequel l’analyse de survie est conçue.

Les données. Nous utilisons lung, le jeu de données fourni avec le package survival. Il s’attache automatiquement, donc vous référencez lung directement — aucun appel à data() n’est nécessaire (voir préparer les données de survie pour la liste complète de préparation des données) :

library(survival)

head(lung)
  inst time status age sex ph.ecog ph.karno pat.karno meal.cal wt.loss
1    3  306      2  74   1       1       90       100     1175      NA
2    3  455      2  68   1       0       90        90     1225      15
3    3 1010      1  56   1       0       90        90       NA      15
4    5  210      2  57   1       1       90        60     1150      11
5    1  883      2  60   1       0      100        90       NA       0
6   12 1022      1  74   1       1       50        80      513       0

Les colonnes que cette analyse utilise :

  • time — temps de suivi en jours.
  • status — statut de censure : 1 = censuré (en vie au dernier suivi), 2 = décédé (l’événement).
  • sex1 = homme, 2 = femme.
  • age — âge en années.
  • ph.ecog — score de performance ECOG (0 = pleinement actif … plus élevé = plus altéré).
  • ph.karno — score de performance de Karnofsky (plus élevé = meilleure fonction).
  • wt.loss — perte de poids au cours des six derniers mois (kg).

Une vérification rapide de cohérence — combien d’événements, et quelle quantité de données manquantes dans les covariables que nous allons modéliser :

library(survival)

# Events vs censored (status: 1 = censored, 2 = dead)
table(status = lung$status)
status
  1   2 
 63 165 
# Missing values per covariate we'll use
colSums(is.na(lung[c("age", "sex", "ph.ecog", "ph.karno", "wt.loss")]))
     age      sex  ph.ecog ph.karno  wt.loss 
       0        0        1        1       14 

Il y a 165 décès parmi 228 patients (le reste censuré), et une poignée de valeurs de covariables manquantes — à noter dès maintenant, car un modèle de Cox utilise les cas complets et écartera discrètement ces lignes.

Construire la réponse Surv()

Tout modèle de survie prend une paire temps–événement du côté gauche, construite avec Surv(time, status). Un + après un temps marque un patient censuré (en vie lors de la dernière observation) :

library(survival)

head(Surv(lung$time, lung$status))
[1]  306   455  1010+  210   883  1022+

306 et 455 sont des décès ; 1010+ est censuré — ce patient était en vie à 1010 jours, donc nous savons seulement que sa survie dépasse 1010 jours. Cet objet est la réponse pour tout ce qui suit.

Étape 2 — Estimer la survie : Kaplan-Meier

Estimez les courbes de survie avec survfit(), globalement et selon le sexe. La sortie imprimée donne le résumé phare — la survie médiane et son IC à 95 % par groupe (la profondeur est dans l’estimation de Kaplan-Meier et survie médiane et IC) :

library(survival)

# Overall, then by sex
survfit(Surv(time, status) ~ 1, data = lung)
Call: survfit(formula = Surv(time, status) ~ 1, data = lung)

       n events median 0.95LCL 0.95UCL
[1,] 228    165    310     285     363
fit <- survfit(Surv(time, status) ~ sex, data = lung)
fit
Call: survfit(formula = Surv(time, status) ~ sex, data = lung)

        n events median 0.95LCL 0.95UCL
sex=1 138    112    270     212     310
sex=2  90     53    426     348     550

Lisez-le : la survie médiane globale est de 310 jours. Décomposée selon le sexe, la survie médiane est de 270 jours pour les hommes (sex = 1) et de 426 jours pour les femmes (sex = 2) — les femmes survivent nettement plus longtemps. Les IC à 95 % (212–310 pour les hommes, 348–550 pour les femmes) se chevauchent à peine, suggérant une vraie différence que nous testerons à l’étape 3.

La figure de publication : ggsurvplot()

Les nombres sont précis, mais la survie se lit sur la courbe. Le ggsurvplot() de survminer trace les courbes de Kaplan-Meier avec tout ce qu’un relecteur attend — les bandes de confiance, la table des sujets à risque, les lignes de médiane et la p-value du log-rank — sur un seul panneau (le guide de mise en forme complet est dans courbes de survie prêtes à publier) :

library(survival)
library(survminer)

fit <- survfit(Surv(time, status) ~ sex, data = lung)

ggsurvplot(
  fit,
  data = lung,
  risk.table = TRUE,        # number at risk under the plot
  conf.int = TRUE,          # 95% confidence bands
  pval = TRUE,              # log-rank p-value, on the plot
  surv.median.line = "hv",  # dashed median-survival guides
  palette = "jco",          # colourblind-safe journal palette
  legend.labs = c("Male", "Female"),
  legend.title = "Sex",
  xlab = "Time (days)",
  ylab = "Survival probability",
  ggtheme = theme_minimal()
)

A publication-ready survminer Kaplan-Meier plot of survival by sex on the lung-cancer data. Two step-function survival curves descend from 1.0; the female curve stays consistently above the male curve, each with a shaded 95% confidence band. Dashed median-survival lines mark 270 days for men and 426 days for women, the log-rank p-value of 0.0013 is printed on the plot, and a risk table beneath shows the number of patients still at risk over time.

La courbe des femmes se situe au-dessus de celle des hommes à chaque instant ; les lignes de médiane en pointillés rencontrent l’axe des x à 270 et 426 jours ; le log-rank p = 0.0013 sur le panneau annonce déjà l’étape 3. La table des sujets à risque montre combien peu de patients restent tard dans le suivi — c’est pourquoi les bandes s’évasent à la queue.

Étape 3 — Comparer les groupes : le test du log-rank

Le graphique suggère que les femmes survivent plus longtemps ; le test du log-rank décide si l’écart est réel. Exécutez-le avec survdiff(), même formule que survfit() (leçon complète : test du log-rank) :

library(survival)

survdiff(Surv(time, status) ~ sex, data = lung)
Call:
survdiff(formula = Surv(time, status) ~ sex, data = lung)

        N Observed Expected (O-E)^2/E (O-E)^2/V
sex=1 138      112     91.6      4.55      10.3
sex=2  90       53     73.4      5.68      10.3

 Chisq= 10.3  on 1 degrees of freedom, p= 0.001 

Lisez-le : les hommes ont eu 112 décès observés vs 91.6 attendus (plus que prévu), les femmes 53 vs 73.4 (moins) — cet écart est le signal. Il s’accumule en χ² = 10.3 à 1 df, p = 0.001. Comme p < 0.05, nous rejetons l’hypothèse nulle d’égalité de survie : les femmes survivent significativement plus longtemps que les hommes. Le test du log-rank donne la p-value mais aucune taille d’effet — pour savoir de combien le risque diffère, et pour ajuster sur d’autres facteurs, nous passons au modèle de Cox.

Étape 4 — Modéliser les covariables : risques proportionnels de Cox

Le modèle de Cox gère les prédicteurs continus et catégoriels et ajuste chaque effet sur les autres, renvoyant un hazard ratio (HR) par facteur (leçon complète : risques proportionnels de Cox). Rappel : HR < 1 = risque inférieur (meilleure survie), HR > 1 = risque supérieur (pire), HR = 1 ou un IC traversant 1 = aucun effet convaincant.

Examiner les covariables une à une (univarié)

Commencez par ajuster un modèle de Cox univarié pour chaque covariable candidate et tabuler les HR — un dépistage rapide des facteurs qui comptent :

library(survival)

covariates <- c("age", "sex", "ph.ecog", "ph.karno", "wt.loss")
univ_formulas <- sapply(covariates,
                        function(x) as.formula(paste("Surv(time, status) ~", x)))
univ_models <- lapply(univ_formulas, function(f) coxph(f, data = lung))

univ_results <- lapply(univ_models, function(m) {
  s <- summary(m)
  HR    <- signif(s$coef[2], 2)
  lower <- signif(s$conf.int[, "lower .95"], 2)
  upper <- signif(s$conf.int[, "upper .95"], 2)
  p     <- signif(s$wald["pvalue"], 2)
  c(HR = paste0(HR, " (", lower, "-", upper, ")"), p.value = p)
})
as.data.frame(t(as.data.frame(univ_results, check.names = FALSE)))
                       HR p.value.pvalue
age               1 (1-1)          0.042
sex      0.59 (0.42-0.82)         0.0015
ph.ecog       1.6 (1.3-2)        2.7e-05
ph.karno    0.98 (0.97-1)          0.005
wt.loss        1 (0.99-1)           0.83

Ce que cela nous indique : sex, ph.ecog, ph.karno, et (de justesse) age portent des effets univariés significatifs ; wt.loss non (p = 0.83). ph.ecog (HR ≈ 1.6) et age (HR ≈ 1.02) augmentent le risque ; sex (HR ≈ 0.59) et ph.karno (HR ≈ 0.98) le diminuent. Mais les effets univariés peuvent être confondus — ph.ecog et ph.karno sont deux vues du même statut de performance — donc le vrai test est de les ajuster ensemble.

Les ajuster ensemble (multivarié)

Ajustez les covariables cliniquement pertinentes en une fois, de sorte que chaque effet soit ajusté sur les autres :

library(survival)

res.full <- coxph(Surv(time, status) ~ age + sex + ph.ecog + ph.karno + wt.loss, data = lung)
summary(res.full)
Call:
coxph(formula = Surv(time, status) ~ age + sex + ph.ecog + ph.karno + 
    wt.loss, data = lung)

  n= 213, number of events= 151 
   (15 observations effacées parce que manquantes)

              coef exp(coef)  se(coef)      z Pr(>|z|)    
age       0.015157  1.015273  0.009763  1.553 0.120538    
sex      -0.631422  0.531835  0.177134 -3.565 0.000364 ***
ph.ecog   0.740204  2.096364  0.191332  3.869 0.000109 ***
ph.karno  0.015251  1.015368  0.009797  1.557 0.119553    
wt.loss  -0.009298  0.990745  0.006699 -1.388 0.165168    
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

         exp(coef) exp(-coef) lower .95 upper .95
age         1.0153     0.9850    0.9960    1.0349
sex         0.5318     1.8803    0.3758    0.7526
ph.ecog     2.0964     0.4770    1.4408    3.0502
ph.karno    1.0154     0.9849    0.9961    1.0351
wt.loss     0.9907     1.0093    0.9778    1.0038

Concordance= 0.64  (se = 0.026 )
Likelihood ratio test= 33.53  on 5 df,   p=3e-06
Wald test            = 32.27  on 5 df,   p=5e-06
Score (logrank) test = 32.83  on 5 df,   p=4e-06

Interprétez la sortie ajustée : seuls sex (HR = 0.53, p = 0.0004) et ph.ecog (HR = 2.10, p = 0.0001) survivent à l’ajustement. age, ph.karno et wt.loss deviennent tous non significatifs (leurs IC traversent 1) — une fois connus le sexe et le score ECOG d’un patient, ils apportent peu. Notez aussi n = 213, et non 228 : coxph() a écarté 15 patients avec une covariable manquante (analyse en cas complets). Un modèle plus léger est à la fois plus simple et évite de perdre des lignes à cause des données manquantes de wt.loss — la façon formelle de le choisir est construction et sélection d’un modèle de Cox. Ici, nous gardons les deux facteurs forts et cliniquement interprétables plus age, et nous retenons age + sex + ph.ecog comme modèle final.

library(survival)

res.cox <- coxph(Surv(time, status) ~ age + sex + ph.ecog, data = lung)
summary(res.cox)
Call:
coxph(formula = Surv(time, status) ~ age + sex + ph.ecog, data = lung)

  n= 227, number of events= 164 
   (1 observation effacée parce que manquante)

             coef exp(coef)  se(coef)      z Pr(>|z|)    
age      0.011067  1.011128  0.009267  1.194 0.232416    
sex     -0.552612  0.575445  0.167739 -3.294 0.000986 ***
ph.ecog  0.463728  1.589991  0.113577  4.083 4.45e-05 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

        exp(coef) exp(-coef) lower .95 upper .95
age        1.0111     0.9890    0.9929    1.0297
sex        0.5754     1.7378    0.4142    0.7994
ph.ecog    1.5900     0.6289    1.2727    1.9864

Concordance= 0.637  (se = 0.025 )
Likelihood ratio test= 30.5  on 3 df,   p=1e-06
Wald test            = 29.93  on 3 df,   p=1e-06
Score (logrank) test = 30.5  on 3 df,   p=1e-06

Le modèle final en langage clair :

  • sex : HR = 0.58, IC à 95 % 0.41–0.80, p = 0.001. À âge et score ECOG constants, être une femme réduit le risque de décès d’environ 42 % — le signal le plus fort et le plus robuste.
  • ph.ecog : HR = 1.59, IC à 95 % 1.27–1.99, p < 0.001. Chaque aggravation d’un point du score de performance augmente le risque d’environ 59 % — un facteur de mauvais pronostic marqué.
  • age : HR = 1.01, IC à 95 % 0.99–1.03, p = 0.23. Après ajustement, l’âge n’est pas significatif (l’IC inclut 1) ; son effet univarié était expliqué par les autres covariables.

Le forest plot : ggforest()

La figure de Cox emblématique montre le HR, l’IC et la p-value de chaque covariable sur un seul panneau, avec la ligne de référence à HR = 1 — à gauche c’est protecteur, à droite c’est délétère :

library(survival)
library(survminer)

res.cox <- coxph(Surv(time, status) ~ age + sex + ph.ecog, data = lung)
ggforest(res.cox, data = lung)

A survminer ggforest hazard-ratio forest plot of the final Cox model (age, sex, ECOG score) on the lung data. Female sex sits clearly left of the reference line at hazard ratio 1 (protective), ECOG performance score sits right of it (harmful), and age sits on the line. Each row shows the hazard ratio, 95% confidence interval, and p-value.

En un coup d’œil : sex nettement à gauche de la ligne (protecteur), ph.ecog à droite (délétère), age à cheval dessus (aucun effet net) — exactement ce que disaient les nombres.

Étape 5 — Valider le modèle : risques proportionnels

Chaque hazard ratio ci-dessus suppose que l’effet est constant dans le temps — l’hypothèse des risques proportionnels (PH). Avant de faire confiance aux HR, testez-la avec cox.zph() (résidus de Schoenfeld mis à l’échelle), qui donne un test par covariable et un test GLOBAL (leçon complète : tester les hypothèses PH) :

library(survival)

res.cox <- coxph(Surv(time, status) ~ age + sex + ph.ecog, data = lung)
test.ph <- cox.zph(res.cox)
test.ph
        chisq df    p
age     0.188  1 0.66
sex     2.305  1 0.13
ph.ecog 2.054  1 0.15
GLOBAL  4.464  3 0.22

Lisez-le : la règle de décision est p ≥ 0.05 → PH tient, p < 0.05 → PH violée. Chaque test par covariable est non significatif (age p = 0.66, sex p = 0.13, ECOG p = 0.15) et le test GLOBAL est p = 0.22 (χ² = 4.46 à 3 df). L’hypothèse est soutenue — les hazard ratios peuvent être lus comme constants dans le temps.

NotePourquoi le modèle plus léger se valide aussi plus proprement

Le modèle complet à cinq covariables de l’étape 4 était presque correct, mais cox.zph() a signalé ph.karno avec p = 0.03 — une légère violation de PH. Écarter les covariables redondantes et non significatives n’a pas seulement simplifié le modèle ; cela a supprimé la seule variable dont l’effet dérivait dans le temps. Un modèle parcimonieux est souvent un modèle mieux comporté.

Voyez-le sur le graphique ggcoxzph() — une ligne de lissage plate dans chaque panneau signifie aucune tendance temporelle :

library(survival)
library(survminer)

res.cox <- coxph(Surv(time, status) ~ age + sex + ph.ecog, data = lung)
test.ph <- cox.zph(res.cox)

ggcoxzph(test.ph)

A survminer ggcoxzph diagnostic with one panel per covariate (age, sex, ECOG score), each showing scaled Schoenfeld residuals against transformed survival time. In every panel the solid smoothing spline is roughly flat and horizontal, staying within the dashed plus-or-minus two-standard-error band, supporting the proportional-hazards assumption.

Les trois splines sont essentiellement plats et restent à l’intérieur de la bande — aucune tendance systématique avec le temps, en accord avec les p-values non significatives de cox.zph(). Comme vérification finale, les observations influentes (ggcoxdiagnostics(), dfbeta) montrent qu’aucun patient unique ne déforme l’ajustement :

library(survival)
library(survminer)

res.cox <- coxph(Surv(time, status) ~ age + sex + ph.ecog, data = lung)

ggcoxdiagnostics(res.cox, type = "dfbeta", linear.predictions = FALSE)

A survminer ggcoxdiagnostics dfbeta plot with one panel per covariate (age, sex, ECOG score), each showing the change in that coefficient when each observation is deleted, plotted against observation index. The points scatter around zero with no single observation dominating, indicating no terribly influential patients.

Une poignée de points pour age et ph.ecog sont plus grands que les autres, mais aucun n’est grand par rapport à son coefficient — aucun patient à retirer. Le modèle est validé.

Étape 6 — Rapporter

Toute l’analyse, rédigée comme elle apparaîtrait dans un article clinique :

La survie globale chez 228 patients atteints d’un cancer du poumon avancé (données NCCTG ; 165 décès) a été estimée par la méthode de Kaplan-Meier. La survie médiane était de 270 jours (IC à 95 % 212–310) pour les hommes et de 426 jours (IC à 95 % 348–550) pour les femmes, une différence statistiquement significative au test du log-rank (χ²(1) = 10.3, p = 0.001). Un modèle de Cox multivarié à risques proportionnels (n = 227, 164 événements) ajustant sur l’âge, le sexe et le score de performance ECOG a montré que le sexe féminin était associé à un risque de décès significativement plus faible (HR = 0.58, IC à 95 % 0.41–0.80, p = 0.001) et qu’un score ECOG plus élevé était associé à un risque plus élevé (HR = 1.59, IC à 95 % 1.27–1.99, p < 0.001) ; l’âge n’était pas significatif après ajustement (HR = 1.01, IC à 95 % 0.99–1.03, p = 0.23). L’hypothèse des risques proportionnels était satisfaite (résidus de Schoenfeld mis à l’échelle, GLOBAL χ²(3) = 4.46, p = 0.22), et les diagnostics d’influence n’ont révélé aucune observation indûment influente.

Ce paragraphe — médianes avec IC, résultat du log-rank, hazard ratios ajustés, et vérification de l’hypothèse — est une analyse de survie complète et défendable.

Ce que vous feriez ensuite

L’analyse lung est propre car le décès est le seul événement et PH tient. Les vraies études ajoutent souvent une subtilité — et chacune a une leçon dans cette série :

Essayez en direct

Exécutez vous-même toute l’analyse — changez la variable de regroupement, ajoutez une covariable au modèle de Cox, ou remplacez le modèle final. Le bac à sable démarre au premier Run.

🟢 Avec un agent IA

Demandez à Prova « exécute une analyse de survie complète sur mes données — courbes de Kaplan-Meier avec survie médiane, un test du log-rank, un modèle de Cox avec hazard ratios, et une vérification des risques proportionnels — et rédige-moi le paragraphe de rapport » — elle répond avec du code survival + survminer que vous pouvez exécuter sur vos propres données. The runtime is the judge. Demander à Prova →

Problèmes courants

Votre modèle de Cox a « perdu » des patients — le n a diminué. coxph() utilise les cas complets : tout patient avec une valeur manquante dans n’importe quelle covariable est écarté. Le modèle complet à cinq covariables est tombé à n = 213 (15 écartés), surtout parce que wt.loss est manquant pour certains patients ; le modèle plus léger age + sex + ph.ecog a gardé n = 227. Vérifiez toujours model$n par rapport à nrow(lung), et préférez un modèle avec moins de covariables, plus complètes (ou une imputation) plutôt que d’analyser discrètement un échantillon différent.

Vous avez lu la direction du hazard ratio à l’envers. Dans lung, sex vaut 1 = homme, 2 = femme, donc le HR est femme vs homme. HR = 0.58 signifie que les femmes ont un risque inférieur (meilleure survie) — protecteur. Avec un factor, R utilise le premier niveau comme référence ; vérifiez le codage avant de qualifier un facteur de « délétère ».

Vous avez rapporté les hazard ratios sans vérifier les risques proportionnels. Un HR de Cox n’a de sens que si l’effet est constant dans le temps. Exécutez toujours cox.zph() (et regardez ggcoxzph()) : un test significatif ou un graphique de résidus en pente signifie que le HR unique est une moyenne temporelle trompeuse — corrigez-le par stratification ou un terme variant dans le temps (voir tester les hypothèses PH), ne publiez pas simplement le nombre.

Questions fréquentes

Suivez cette séquence : (1) construisez la réponse avec Surv(time, status) ; (2) estimez les courbes de Kaplan-Meier avec survfit(Surv(time, status) ~ group, data) et lisez la survie médiane ; (3) comparez les groupes avec le test du log-rank, survdiff() ; (4) ajustez un modèle de Cox, coxph(), et interprétez les hazard ratios avec les IC à 95 % ; (5) vérifiez l’hypothèse des risques proportionnels avec cox.zph() et ggcoxzph() ; (6) rapportez les médianes, le résultat du log-rank, les hazard ratios ajustés et la vérification PH. Deux packages couvrent tout : survival et survminer.

Kaplan-Meier d’abord. Il décrit la survie dans chaque groupe (les courbes et la survie médiane) sans hypothèses, et le test du log-rank vous dit si les groupes diffèrent. Ajustez ensuite le modèle de Cox pour quantifier l’effet sous forme de hazard ratio, utiliser des prédicteurs continus et ajuster sur plusieurs covariables à la fois. Kaplan-Meier est descriptif ; Cox est l’étape de modélisation qui s’appuie dessus.

lung (dans le package survival) contient des données de survie sur 228 patients atteints d’un cancer du poumon avancé du North Central Cancer Treatment Group : time (suivi en jours), status (1 = censuré, 2 = décédé), sex (1 = homme, 2 = femme), age, et les covariables de statut de performance ph.ecog, ph.karno, pat.karno, meal.cal et wt.loss. Il s’attache automatiquement avec le package, donc vous référencez lung directement.

Un hazard ratio est un effet multiplicatif sur le risque de décès. HR = 0.58 pour le sexe féminin signifie que les femmes ont un risque inférieur de 42 % à celui des hommes ((1 − 0.58) × 100) — protecteur. HR = 1.59 pour le score ECOG signifie que chaque aggravation d’un point augmente le risque de 59 % — délétère. HR = 1.01 pour l’âge avec un IC qui inclut 1 signifie aucun effet convaincant. Lisez toujours un HR avec son intervalle de confiance à 95 % et sa p-value.

Deux. survival fait la modélisation et les tests : Surv(), survfit() (Kaplan-Meier), survdiff() (log-rank), coxph() (Cox), et cox.zph() (test PH). survminer fait les figures de qualité publication : ggsurvplot() (courbes KM avec table des sujets à risque, IC, lignes de médiane, p-value), ggforest() (forest plot des hazard ratios), et ggcoxzph() / ggcoxdiagnostics() (diagnostics du modèle). Les deux fournissent le jeu de données lung utilisé ici.

Testez vos connaissances

  1. Exécutez-le. Dans la cellule interactive ci-dessous, étendez l’analyse : ajustez le modèle de Cox final en ajoutant wt.loss comme quatrième covariable, puis vérifiez les risques proportionnels. Remplissez le blanc. wt.loss est-il un prédicteur significatif après ajustement, et le test GLOBAL cox.zph() soutient-il toujours PH ?
  2. Interprétez. Votre modèle de Cox rapporte, pour un indicateur de traitement, HR = 0.65 (IC à 95 % 0.48–0.88, p = 0.005), et cox.zph() donne un p GLOBAL = 0.40. En langage clair, quel est l’effet du traitement, est-il statistiquement convaincant, et pouvez-vous faire confiance au hazard ratio ?

Remplissez le blanc avec wt.loss. Dans summary(), lisez l’exp(coef) de wt.loss (le HR) et sa p-value — un IC traversant 1 (ou p ≥ 0.05) signifie aucun effet convaincant. Dans cox.zph(), lisez la ligne GLOBAL : p ≥ 0.05 signifie que PH est soutenu.

res.cox <- coxph(Surv(time, status) ~ age + sex + ph.ecog + wt.loss, data = lung)
summary(res.cox)
#> wt.loss HR ~ 0.99, p ~ 0.5 — NOT significant after adjustment (CI crosses 1);
#> sex and ph.ecog remain the strong predictors. n drops to ~214 (wt.loss missingness).
cox.zph(res.cox)
#> GLOBAL p > 0.05 — the proportional-hazards assumption is still supported.

wt.loss n’apporte rien une fois le sexe et le score ECOG dans le modèle, et il vous coûte des lignes par données manquantes — une bonne raison de le laisser de côté. PH tient toujours globalement.

Pour la question 2 : HR = 0.65 signifie un risque de décès inférieur de 35 % dans le groupe traité ((1 − 0.65) × 100) — un effet de traitement protecteur. Il est statistiquement convaincant : l’IC à 95 % (0.48–0.88) se situe entièrement sous 1 et p = 0.005. Et comme cox.zph() GLOBAL p = 0.40 (≥ 0.05), les risques proportionnels tiennent, donc le hazard ratio unique est fiable — vous pouvez le rapporter comme un effet constant au cours du suivi.

AstuceVérification rapide

Vous exécutez une analyse de survie et constatez que les courbes de Kaplan-Meier se séparent nettement, mais le test du log-rank donne p = 0.18. Un relecteur demande le hazard ratio « pour être sûr ». Quelle est la bonne étape suivante ?

Ajustez un modèle de Cox pour obtenir le hazard ratio avec son IC à 95 % — le test du log-rank ne donne qu’une p-value, pas une taille d’effet. Mais interprétez-le honnêtement : un test du log-rank non significatif (p = 0.18) signifie qu’il n’y a pas de preuve forte que les courbes diffèrent, donc attendez-vous à ce que l’intervalle de confiance du HR de Cox traverse 1. Si les courbes se croisent nettement, le test du log-rank perd de la puissance et PH est probablement violée — vérifiez cox.zph(), et envisagez un test du log-rank pondéré ou le RMST plutôt que de forcer un HR unique.

Conclusion

Vous avez réalisé une analyse de survie complète en R de A à Z : posé la question, construit la réponse Surv(time, status), estimé les courbes de Kaplan-Meier et lu la survie médiane (270 vs 426 jours), confirmé la différence avec le test du log-rank (p = 0.001), ajusté et interprété un modèle de Cox multivarié (sexe féminin HR 0.58, ECOG HR 1.59), validé le tout par rapport à l’hypothèse des risques proportionnels (GLOBAL p = 0.22), et assemblé la figure prête à publier et le paragraphe de rapport. Cette séquence — décrire, comparer, modéliser, valider, rapporter — est la colonne vertébrale de toute étude de temps jusqu’à l’événement ; remplacez par vos propres données et les six mêmes étapes vous mènent à un résultat défendable. Quand vos données ajoutent une subtilité (événements concurrents, courbes qui se croisent, forme paramétrique), les leçons ce que vous feriez ensuite prennent le relais.

Leçons connexes

Cette page vous a-t-elle été utile ?

Prouvez que vous savez le faire. Maîtrisez toute la série Analyse de survie en R — suivez votre parcours, construisez des projets et obtenez un certificat.

Commencer gratuitement →

Passez à Pro — Prova illimité sur vos propres données et un certificat vérifiable qui atteste la compétence.

dès 15 $/mois facturé annuellement

Passer à Pro →

✓ Vous êtes Pro — continuez. The runtime is the judge.

Recevez les nouvelles leçons R & Python par e-mail

Pratique, reproductible, sans spam. Désinscription à tout moment.

Double opt-in. Nous ne partageons jamais votre e-mail.

Partager cette pageXLinkedInRedditHN
Note

Chaque résultat de cette page a été produit par le code montré, exécuté au moment du build contre un environnement R figé — modifiez n’importe quel bloc et faites Run pour le reproduire vous-même.

Réutilisation

Citation

BibTeX
@online{2026,
  author = {},
  title = {Exemple complet d’analyse de survie en R : Kaplan-Meier,
    log-rank, Cox — de A à Z},
  date = {2026-06-26},
  url = {https://www.datanovia.com/learn/biostatistics/survival-analysis/capstone-lung-analysis},
  langid = {fr}
}
Veuillez citer ce travail comme suit :
“Exemple complet d’analyse de survie en R : Kaplan-Meier, log-rank, Cox — de A à Z.” 2026. June 26. https://www.datanovia.com/learn/biostatistics/survival-analysis/capstone-lung-analysis.