L’analisi de supervivència es una branca de l’estadística que s’utilitza per estudiar el temps que passa fins que ocorre un esdeveniment. També se’n diu anàlisi de temps a l’esdeveniment.
Les aplicacions clíniques habituals son l’anàlisi del temps fins a la mort, recidiva, ingres hospitalàri o perdua de seguiment d’un pacient.
1.1 El concepte de censura
En estudis reals, sovint no coneixem el temps exacte de l’esdeveniment per a tothom, ni tothom comença i acaba un estudi al mateix temps. Per fer calculs amb els seguiments dels que se disposa, existeix el concepte de censura.
📌 Censura per la dreta vol dir que sabem que l’esdeveniment no ha passat fins a cert moment, però no sabem què passa després.
1.1.1 🔍 Causes habituals de censura per la dreta
Un subjecte pot estar censurat per diversos motius:
Pèrdua de seguiment
Retirada voluntària de l’estudi
Final del període d’estudi sense que hagi passat l’esdeveniment
Aquests són casos de censura per la dreta, que és la més habitual. Existeixen censura per l’esquerra o per intervals que no tractarem en aquest curs.
1.2 Exemple gràfic: temps fins a esdeveniment o censura
Per il·lustrar la censura i la supervivència, observem la següent figura:
1.2.1 Com interpretar aquest gràfic?
Pregunta: quina proporció de pacients es mantenen lliures de l’esdeveniment al cap de 10 anys?
Pacients 6 i 7: encara no han tingut l’esdeveniment a l’any 10 → lliures d’esdeveniment
Pacients 2, 9 i 10: han tingut l’esdeveniment abans de l’any 10
Pacients 1, 3, 4, 5 i 8: han estat censurats abans de l’any 10 → no sabem si han tingut l’esdeveniment o no
📌 Tot i que no sabem què ha passat amb els censurats, sí sabem que han estat seguits durant un temps determinat sense l’esdeveniment.
1.3 Com han de ser les dades?
Per fer una anàlisi de supervivència en R, necessitem un conjunt de dades que inclogui com a mínim:
Variable
Descripció
temps
Temps de seguiment per a cada subjecte (pot estar en dies, mesos, anys…)
event
Indicador de si ha passat l’esdeveniment (1) o si és censurat (0)
📌 Aquestes dues variables s’utilitzen per construir un objecte especial de tipus Surv(), que encapsula tota la informació necessària per als mètodes de supervivència: el temps, l’estat de censura i (més endavant) grups comparatius.
1.3.1 ℹ️ Què és un objecte Surv()?
Un objecte Surv() és un format especial que R utilitza per representar dades de supervivència.
Conté, per a cada cas:
el temps de seguiment
un indicador de si l’esdeveniment ha passat (1) o està censurat (0)
Aquest objecte és imprescindible per poder fer servir funcions com survfit(), coxph() o survdiff() del paquet survival.
1.3.2 Preparació de les dades
Abans de calcular el temps, cal assegurar-se que les dates estiguin en format correcte. A R això pot ser complicat, però hi ha un paquet molt útil per treballar amb dates:
lubridate és un paquet de la família tidyverse que facilita molt la conversió de textos en dates, així com càlculs, extracció d’anys, mesos, etc.
Per exemple, ymd("2020-01-01") crea una data en format “any-mes-dia”.
Ara tenim preparades les dades per a estimar la funció de supervivència.
Continuarem amb la corba de Kaplan-Meier i la seva representació visual.
2 Estimació de la funció de supervivència (Kaplan-Meier)
Ara que tenim el nostre objecte Surv() creat, podem estimar la funció de supervivència amb la funció survfit().
2.0.1 Estimació global (tots els pacients)
Code
km_total <-survfit(surv_lung ~1, data = lung)summary(km_total)$table
records n.max n.start events rmean se(rmean) median 0.95LCL
228.00000 228.00000 228.00000 165.00000 376.27475 19.70779 310.00000 285.00000
0.95UCL
363.00000
La taula ens mostra que la mediana de supervivència és de 310 dies (IC 95%: 285–363 dies), que correspon al moment en què la probabilitat de supervivència arriba al 50%.
A la pràctica clínica, però, sovint necessitem respondre a una altra pregunta clau: quina és la probabilitat de supervivència a un temps determinat (p. ex., als 6 mesos o a l’any)? Per extreure aquestes xifres sense haver d’inspeccionar tota la taula d’esdeveniments, utilitzem l’argument times dins de summary():
Code
# Supervivència acumulada estimada als 6 mesos (180 dies) i a 1 any (365 dies)summary(km_total, times =c(180, 365))
Call: survfit(formula = surv_lung ~ 1, data = lung)
time n.risk n.event survival std.err lower 95% CI upper 95% CI
180 160 63 0.722 0.0298 0.666 0.783
365 65 58 0.409 0.0358 0.345 0.486
Als 180 dies (6 mesos): La supervivència estimada és d’un 71.9% (IC 95%: 66.2% – 78.1%).
2.1Als 365 dies (1 any): La supervivència estimada baixa al 40.9% (IC 95%: 34.8% – 48.1%).
2.1.1 📈 Visualització de la corba de supervivència
Per representar la corba utilitzarem el paquet survminer, que facilita gràfics clars i publicables.
Code
library(survminer)ggsurvplot(km_total,conf.int =TRUE,xlab ="Dies de seguiment",ylab ="Probabilitat de supervivència",title ="Corba de supervivència (Kaplan-Meier)",surv.median.line ="hv")
La línia de punts mostra la mediana de supervivència estimada.
2.1.2 Comparació per sexe
Podem estimar i visualitzar la supervivència segons el sexe:
Code
km_sexe <-survfit(surv_lung ~ sex, data = lung)ggsurvplot(km_sexe,data = lung,conf.int =TRUE,pval =TRUE,risk.table =TRUE,xlab ="Dies de seguiment",ylab ="Supervivència estimada",title ="Supervivència segons el sexe",legend.labs =c("Home", "Dona"))
Aquest gràfic ens permet observar si hi ha diferències clíniques rellevants i si són estadísticament significatives (valor p del test de log-rank).
2.2 ⚖️ Comparació de grups: test de log-rank
Un cop hem representat les corbes de supervivència per grups (per exemple, per sexe), volem saber si la diferència observada és estadísticament significativa.
El test de log-rank (o test de Mantel-Haenszel) és la prova estàndard per comparar dues o més corbes de Kaplan-Meier.
2.2.1 Què comprova el test?
Comprova la hipòtesi nul·la que les funcions de supervivència són iguals entre grups. És a dir:
\[
H_0: S_1(t) = S_2(t) \quad \text{per a tot } t
\]
Si el valor p és petit, rebutgem ( H_0 ) → hi ha diferències estadísticament significatives entre grups
Si és gran, no podem afirmar que hi hagi diferències
2.2.2 Aplicació amb survdiff()
Code
logrank <-survdiff(surv_lung ~ sex, data = lung)logrank
Call:
survdiff(formula = surv_lung ~ sex, data = lung)
N Observed Expected (O-E)^2/E (O-E)^2/V
sex=Home 138 112 91.6 4.55 10.3
sex=Dona 90 53 73.4 5.68 10.3
Chisq= 10.3 on 1 degrees of freedom, p= 0.001
La sortida ens dona un estadístic de tipus \(\chi^2\) amb els graus de llibertat i el p-valor associat.
3 Model de Cox (riscos proporcionals)
El model de Cox ens permet estimar l’efecte de diverses variables (edat, sexe, tractament…) sobre el risc instantani de tenir l’esdeveniment (mort, recidiva, etc.).
És l’equivalent de la regressió logística per a dades de supervivència, però tenint en compte el temps de seguiment.
3.0.1 Què fa el model?
Modelitza el hazard (risc instantani de morir) en funció de predictors:
\(\exp(\beta_i)\) : hazard ratio (HR) associat a cada variable
3.0.2 ⚙️ Ajust del model amb coxph()
Code
cox_model <-coxph(surv_lung ~ sex + age, data = lung)summary(cox_model)
Call:
coxph(formula = surv_lung ~ sex + age, data = lung)
n= 228, number of events= 165
coef exp(coef) se(coef) z Pr(>|z|)
sexDona -0.513219 0.598566 0.167458 -3.065 0.00218 **
age 0.017045 1.017191 0.009223 1.848 0.06459 .
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
exp(coef) exp(-coef) lower .95 upper .95
sexDona 0.5986 1.6707 0.4311 0.8311
age 1.0172 0.9831 0.9990 1.0357
Concordance= 0.603 (se = 0.025 )
Likelihood ratio test= 14.12 on 2 df, p=9e-04
Wald test = 13.47 on 2 df, p=0.001
Score (logrank) test = 13.72 on 2 df, p=0.001
3.0.3 Interpretació dels coeficients
Els coeficients s’interpreten com a hazard ratios:
Si ( HR > 1 ): el factor augmenta el risc
Si ( HR < 1 ): el factor redueix el risc
Si ( HR = 1 ): no té efecte
Per veure els HR i els seus intervals de confiança:
Code
exp(coef(cox_model)) # HR
sexDona age
0.598566 1.017191
Code
exp(confint(cox_model)) # IC 95% dels HR
2.5 % 97.5 %
sexDona 0.4310936 0.8310985
age 0.9989686 1.0357467
3.0.3.1 Exemple d’interpretació
A partir de la sortida del nostre model (summary(cox_model)), podem interpretar clínicament els coeficients exponenciats:
Sexe (sexDona, \(\text{HR} = 0.60\), IC 95%: 0.43–0.83, \(p = 0.002\)):
Com que la categoria de referència són els homes (Home), un HR de 0.60 indica que les dones tenen un 40% menys de risc instantani de mort que els homes durant el seguiment, mantenint l’edat constant.
Nota pedagògica (inversió del risc): Si analitzem l’efecte a la inversa per conèixer el risc dels homes respecte a les dones (\(1 / 0.60 \approx 1.67\), que correspon al valor exp(-coef) de la sortida d’R), els homes presenten un 67% més de risc de morir que les dones.
Edat (age, \(\text{HR} = 1.017\), IC 95%: 0.999–1.036, \(p = 0.065\)):
Per cada any addicional d’edat, el risc instantani de mort s’incrementa un 1.7% (\(1.017 - 1 = 0.017\)), ajustant pel sexe. En aquesta cohort, aquest efecte es troba al límit de la significació estadística convencional (\(p > 0.05\)).
📌 El model de Cox és útil per:
Ajustar per múltiples variables
Interpretar l’efecte de factors en termes de risc
Afegir interaccions, variables categòriques i contínues
3.1 🧪 Verificació de l’assumpció de riscos proporcionals
El model de Cox assumeix que l’efecte de cada predictor és constant en el temps. És a dir:
El hazard ratio (HR) per a cada variable no canvia al llarg del seguiment.
Això s’anomena l’assumpció de riscos proporcionals.
3.1.1 Què passa si no es compleix?
Si l’HR canvia amb el temps (per exemple, un tractament només és efectiu al principi), el model pot ser inadequat o enganyós.
chisq df p
sex 2.608 1 0.11
age 0.209 1 0.65
GLOBAL 2.771 2 0.25
Comprova si hi ha interacció entre el predictor i el temps
Si el valor p és petit (< 0.05), es viola l’assumpció
3.1.3 Gràfic de residuals de Schoenfeld
Code
plot(test_proporcionalitat)
Si la línia del predictor s’allunya clarament de la línia horitzontal, pot haver-hi violació
Les línies de punts mostren el 95% IC del test
3.1.4 ✅ Interpretació
Resultat
Què vol dir
p > 0.05
L’assumpció de riscos proporcionals es compleix
p < 0.05
Podria haver-hi un efecte no constant en el temps
📌 Si es detecta violació, es poden considerar models amb efectes dependents del temps (nivell més avançat) o estratificar per aquella variable.
3.2 🧩 Resum del capítol de supervivència
En aquest capítol hem après a treballar amb dades de temps fins a esdeveniment, i hem vist com analitzar-les i representar-les correctament.
3.2.1 Què hem après?
Eina / concepte
Què permet fer
Surv()
Codificar temps i censura per a cada individu
survfit() + ggsurvplot()
Estimar i visualitzar corbes de supervivència
survdiff()
Comparar grups amb el test de log-rank
coxph()
Ajustar un model amb múltiples predictors
cox.zph()
Comprovar l’assumpció de riscos proporcionals
3.2.2 Bones pràctiques
Validar la censura: no oblidar que censurar no vol dir “no ha passat”, sinó “no sabem si ha passat”
Revisar supòsits: tant amb log-rank com amb Cox
Interpretar amb context clínic: un HR de 2 pot ser rellevant… o no, segons el cas
3.2.3 12. Exercicis pràctics del Capítol 5
Posa a prova els conceptes apresos sobre censura, estimació no paramètrica de Kaplan-Meier, contrast de log-rank i regressió de Cox amb el teu propi script o document Quarto[cite: 1]:
Codificació de censura i objecte Surv manual: Un assaig clínic segueix 6 pacients després d’una intervenció coronària percussió (temps en mesos):
Pacient 6: seguiment 24 mesos, sense esdeveniment (fi d’estudi). Construeix el dataframe, crea l’objecte Surv() i comprova quins casos queden marcats amb censura a la dreta (+)[cite: 1].
Exploració i neteja d’una cohort oncològica: A partir del dataset lung del paquet survival[cite: 1]:
Recodifica la variable status de manera que l’esdeveniment (mort) sigui 1 i la censura sigui 0 (recorda que originàriament ve codificat com 1=censura, 2=èxitus)[cite: 1].
Converteix sex en un factor amb les etiquetes "Home" i "Dona"[cite: 1].
Quants pacients han patit l’esdeveniment i quants han estat censurats[cite: 1]?
Funció de supervivència de Kaplan-Meier global:
Estima la corba de Kaplan-Meier per a tota la cohort lung[cite: 1].
Quina és la mediana de supervivència estimada i el seu interval de confiança del 95%[cite: 1]?
Quina és la probabilitat de continuar viu als 365 dies (1 any)[cite: 1]?
Comparació de supervivència segons l’estat funcional (ECOG) i Log-Rank: La variable ph.ecog mesura l’escala funcional de l’ECOG (0 = asimptomàtic fins a 3 = enllitat la major part del dia)[cite: 1].
Filtra els pacients amb dades vàlides a ph.ecog[cite: 1].
Estima i dibuixa les corbes de Kaplan-Meier segons ph.ecog incorporant la taula de pacients a risc amb survminer::ggsurvplot()[cite: 1].
Realitza el test de log-rank (survdiff) per contrastar si les diferències de supervivència entre graus d’ECOG són estadísticament significatives[cite: 1].
Model de riscos proporcionals de Cox:
Ajusta un model de regressió de Cox (coxph) per avaluar la mortalitat en funció de l’edat (age), el sexe (sex) i l’estat funcional (ph.ecog)[cite: 1].
Mostra els Hazard Ratios (HR) i els seus intervals de confiança del 95%[cite: 1].
Interpreta clínicament l’HR associat a ph.ecog mantenint constants l’edat i el sexe[cite: 1].
Diagnòstic del model: Verificació de l’assumpció de riscos proporcionals:
Executa el test de diagnòstic basat en els residus de Schoenfeld amb cox.zph()[cite: 1].
Mostra els gràfics de diagnòstic i conclou si el supòsit de riscos proporcionals es compleix tant individualment com globalment[cite: 1].
table(Sexe = lung_exercicis$sexe, Estat = lung_exercicis$status_net)
Estat
Sexe 0 1
Home 26 112
Dona 37 53
Code
# -------------------------------------------------------------# Exercici 3: Kaplan-Meier global i probabilitat a 1 any (365 dies)# -------------------------------------------------------------surv_obj_ex <-Surv(time = lung_exercicis$time, event = lung_exercicis$status_net)km_global <-survfit(surv_obj_ex ~1, data = lung_exercicis)summary(km_global)$table # Mediana i IC 95%
records n.max n.start events rmean se(rmean) median 0.95LCL
228.00000 228.00000 228.00000 165.00000 376.27475 19.70779 310.00000 285.00000
0.95UCL
363.00000
Code
# Estimació puntual de supervivència als 365 diessummary(km_global, times =365)
Call: survfit(formula = surv_obj_ex ~ 1, data = lung_exercicis)
time n.risk n.event survival std.err lower 95% CI upper 95% CI
365 65 121 0.409 0.0358 0.345 0.486
Code
# -------------------------------------------------------------# Exercici 4: Corbes per ECOG i contrast de Log-Rank# -------------------------------------------------------------lung_ecog_valids <- lung_exercicis %>%filter(!is.na(ecog))km_ecog <-survfit(Surv(time, status_net) ~ ecog, data = lung_ecog_valids)ggsurvplot( km_ecog,data = lung_ecog_valids,pval =TRUE,risk.table =TRUE,conf.int =FALSE,title ="Supervivència segons l'estatus ECOG (ph.ecog)",xlab ="Dies de seguiment",ylab ="Supervivència acumulada",risk.table.height =0.28)
Code
# Test de log-ranksurvdiff(Surv(time, status_net) ~ ecog, data = lung_ecog_valids)
# -------------------------------------------------------------# Exercici 5: Model multivariable de Cox# -------------------------------------------------------------mod_cox <-coxph(Surv(time, status_net) ~ age + sexe + ecog, data = lung_exercicis)summary(mod_cox)
Call:
coxph(formula = Surv(time, status_net) ~ age + sexe + ecog, data = lung_exercicis)
n= 227, number of events= 164
(1 observation deleted due to missingness)
coef exp(coef) se(coef) z Pr(>|z|)
age 0.010795 1.010853 0.009312 1.159 0.24637
sexeDona -0.545831 0.579360 0.168228 -3.245 0.00118 **
ecog1 0.410048 1.506890 0.199604 2.054 0.03995 *
ecog2 0.903303 2.467741 0.228078 3.960 7.48e-05 ***
ecog3 1.954543 7.060694 1.029701 1.898 0.05767 .
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
exp(coef) exp(-coef) lower .95 upper .95
age 1.0109 0.9893 0.9926 1.0295
sexeDona 0.5794 1.7260 0.4166 0.8056
ecog1 1.5069 0.6636 1.0190 2.2284
ecog2 2.4677 0.4052 1.5782 3.8587
ecog3 7.0607 0.1416 0.9383 53.1289
Concordance= 0.637 (se = 0.025 )
Likelihood ratio test= 30.87 on 5 df, p=1e-05
Wald test = 31.43 on 5 df, p=8e-06
Score (logrank) test = 33.56 on 5 df, p=3e-06
Code
# Hazard Ratios (HR) i intervals de confiança del 95%exp(cbind(HR =coef(mod_cox), confint(mod_cox)))
---title: "Curs d'Estadística amb R per a Metges - Capítol 5: Anàlisi de Supervivència"author: "P. Loma-Osorio"date: "`r Sys.Date()`"format: html: theme: cosmo toc: true toc-depth: 3 toc-title: "Índex de continguts" toc-location: left number-sections: true code-fold: show code-tools: true code-copy: true code-overflow: wrap embed-resources: true self-contained: true smooth-scroll: true df-print: pagedexecute: echo: true warning: false message: false---```{r, echo=F, warning=F, message=FALSE}library(tidyverse)```# Anàlisi de supervivènciaL'analisi de supervivència es una branca de l’estadística que s’utilitza per estudiar el **temps que passa fins que ocorre un esdeveniment**. També se'n diu *anàlisi de temps a l’esdeveniment*.Les aplicacions clíniques habituals son l'anàlisi del temps fins a la **mort, recidiva, ingres hospitalàri o perdua de seguiment** d'un pacient.## El concepte de censuraEn estudis reals, sovint no coneixem el temps exacte de l’esdeveniment per a tothom, ni tothom comença i acaba un estudi al mateix temps. Per fer calculs amb els seguiments dels que se disposa, existeix el concepte de censura.📌 **Censura per la dreta** vol dir que sabem que l’esdeveniment *no ha passat* fins a cert moment, però no sabem què passa després.------------------------------------------------------------------------### 🔍 Causes habituals de censura per la dretaUn subjecte pot estar censurat per diversos motius:- **Pèrdua de seguiment**- **Retirada voluntària** de l’estudi- **Final del període d’estudi** sense que hagi passat l’esdevenimentAquests són casos de **censura per la dreta**, que és la més habitual. Existeixen **censura per l’esquerra** o per intervals que no tractarem en aquest curs.------------------------------------------------------------------------## Exemple gràfic: temps fins a esdeveniment o censuraPer il·lustrar la censura i la supervivència, observem la següent figura:### Com interpretar aquest gràfic?**Pregunta**: quina proporció de pacients es mantenen lliures de l’esdeveniment al cap de 10 anys?- **Pacients 6 i 7**: **encara no han tingut l’esdeveniment** a l’any 10 → **lliures d’esdeveniment**- **Pacients 2, 9 i 10**: **han tingut l’esdeveniment abans** de l’any 10- **Pacients 1, 3, 4, 5 i 8**: **han estat censurats abans** de l’any 10 → no sabem si han tingut l’esdeveniment o no📌 Tot i que no sabem què ha passat amb els censurats, **sí sabem que han estat seguits durant un temps determinat** sense l’esdeveniment.------------------------------------------------------------------------## Com han de ser les dades?Per fer una anàlisi de supervivència en R, necessitem un conjunt de dades que inclogui com a mínim:| Variable | Descripció ||------------------------------------|------------------------------------|| `temps` | Temps de seguiment per a cada subjecte (pot estar en dies, mesos, anys...) || `event` | Indicador de si ha passat l’esdeveniment (1) o si és censurat (0) |📌 Aquestes dues variables s’utilitzen per construir un **objecte especial de tipus `Surv()`**, que encapsula tota la informació necessària per als mètodes de supervivència: el temps, l’estat de censura i (més endavant) grups comparatius.------------------------------------------------------------------------### ℹ️ Què és un objecte `Surv()`?Un objecte `Surv()` és un format especial que R utilitza per representar dades de supervivència.\Conté, per a cada cas:- el temps de seguiment- un indicador de si l’esdeveniment ha passat (1) o està censurat (0)Aquest objecte és **imprescindible** per poder fer servir funcions com `survfit()`, `coxph()` o `survdiff()` del paquet `survival`.------------------------------------------------------------------------### Preparació de les dadesAbans de calcular el temps, cal assegurar-se que les **dates estiguin en format correcte**. A R això pot ser complicat, però hi ha un paquet molt útil per treballar amb dates:::: tip`lubridate` és un paquet de la família tidyverse que **facilita molt la conversió de textos en dates**, així com càlculs, extracció d’anys, mesos, etc.Per exemple, `ymd("2020-01-01")` crea una data en format "any-mes-dia".:::------------------------------------------------------------------------### Exemple amb 5 pacients```{r}library(lubridate)library(survival)ingres <-ymd(c("2020-01-01", "2020-02-15", "2020-03-01", "2020-01-20", "2020-01-10"))alta <-ymd(c("2020-01-12", "2020-03-05", "2020-03-20", "2020-01-28", "2020-01-25"))event <-c(1, 0, 1, 1, 0)temps <-as.numeric(alta - ingres)dades <-data.frame(subjecte =1:5, ingres, alta, temps, event)dades```------------------------------------------------------------------------### Creació de l’objecte `Surv()````{r}surv_obj <-Surv(time = temps, event = event)surv_objplot(surv_obj)```Ara ja tenim un objecte `Surv` preparat per utilitzar-lo en l’estimació de corbes de supervivència amb `survfit()`.------------------------------------------------------------------------## 🏥 Exemple aplicat: dades de pacients amb càncer de pulmóTreballarem amb el conjunt de dades `lung`, que forma part del paquet `survival` de R.Aquest dataset prové d’un assaig clínic realitzat per la Veterans Administration i recull informació de pacients amb càncer de pulmó, incloent:- Temps de seguiment (en dies)- Estat vital (mort o censurat)- Sexe- Edat- Tractament📦 El dataset està disponible automàticament quan carreguem el paquet `survival`.------------------------------------------------------------------------### Càrrega de dades i inspecció inicial```{r}library(survival)head(lung) # Mostra les primeres files```------------------------------------------------------------------------### Neteja i recodificació bàsica- `status` indica si el pacient ha mort (`2`) o està censurat (`1`)- `sex` està codificat com `1 = home`, `2 = dona````{r}lung <- lung %>%mutate(status =ifelse(status ==2, 1, 0), # 1 = mort, 0 = censuratsex =factor(sex, levels =c(1, 2), labels =c("Home", "Dona")) )table(lung$status)table(lung$sex)```------------------------------------------------------------------------### Creació de l’objecte `Surv()````{r}surv_lung <-Surv(time = lung$time, event = lung$status)head(surv_lung)```Ara tenim preparades les dades per a estimar la funció de supervivència.\Continuarem amb la corba de Kaplan-Meier i la seva representació visual.# Estimació de la funció de supervivència (Kaplan-Meier)Ara que tenim el nostre objecte `Surv()` creat, podem estimar la **funció de supervivència** amb la funció `survfit()`.------------------------------------------------------------------------### Estimació global (tots els pacients)```{r}km_total <-survfit(surv_lung ~1, data = lung)summary(km_total)$table```La taula ens mostra que la **mediana de supervivència** és de **310 dies** (IC 95%: 285–363 dies), que correspon al moment en què la probabilitat de supervivència arriba al 50%.A la pràctica clínica, però, sovint necessitem respondre a una altra pregunta clau: *quina és la probabilitat de supervivència a un temps determinat (p. ex., als 6 mesos o a l'any)?* Per extreure aquestes xifres sense haver d'inspeccionar tota la taula d'esdeveniments, utilitzem l'argument `times` dins de `summary()`:```{r}# Supervivència acumulada estimada als 6 mesos (180 dies) i a 1 any (365 dies)summary(km_total, times =c(180, 365))```* **Als 180 dies (6 mesos):** La supervivència estimada és d'un **71.9%** (IC 95%: 66.2% – 78.1%).* **Als 365 dies (1 any):** La supervivència estimada baixa al **40.9%** (IC 95%: 34.8% – 48.1%).------------------------------------------------------------------------### 📈 Visualització de la corba de supervivènciaPer representar la corba utilitzarem el paquet `survminer`, que facilita gràfics clars i publicables.```{r}library(survminer)ggsurvplot(km_total,conf.int =TRUE,xlab ="Dies de seguiment",ylab ="Probabilitat de supervivència",title ="Corba de supervivència (Kaplan-Meier)",surv.median.line ="hv")```La línia de punts mostra la **mediana de supervivència estimada**.------------------------------------------------------------------------### Comparació per sexePodem estimar i visualitzar la supervivència **segons el sexe**:```{r}km_sexe <-survfit(surv_lung ~ sex, data = lung)ggsurvplot(km_sexe,data = lung,conf.int =TRUE,pval =TRUE,risk.table =TRUE,xlab ="Dies de seguiment",ylab ="Supervivència estimada",title ="Supervivència segons el sexe",legend.labs =c("Home", "Dona"))```Aquest gràfic ens permet observar si hi ha **diferències clíniques rellevants** i si són **estadísticament significatives** (valor p del test de log-rank).------------------------------------------------------------------------## ⚖️ Comparació de grups: test de log-rankUn cop hem representat les corbes de supervivència per grups (per exemple, per sexe), volem saber si la diferència observada **és estadísticament significativa**.El **test de log-rank** (o test de Mantel-Haenszel) és la prova estàndard per comparar dues o més corbes de Kaplan-Meier.### Què comprova el test?Comprova la **hipòtesi nul·la** que les funcions de supervivència són iguals entre grups. És a dir:$$H_0: S_1(t) = S_2(t) \quad \text{per a tot } t$$Si el **valor p és petit**, rebutgem ( H_0 ) → hi ha **diferències estadísticament significatives** entre grups- Si és gran, no podem afirmar que hi hagi diferències------------------------------------------------------------------------### Aplicació amb `survdiff()````{r}logrank <-survdiff(surv_lung ~ sex, data = lung)logrank```La sortida ens dona un estadístic de tipus $\chi^2$ amb els graus de llibertat i el p-valor associat.# Model de Cox (riscos proporcionals)El **model de Cox** ens permet estimar l’efecte de diverses variables (edat, sexe, tractament...) sobre el **risc instantani** de tenir l’esdeveniment (mort, recidiva, etc.).És l’equivalent de la **regressió logística** per a dades de supervivència, però **tenint en compte el temps de seguiment**.------------------------------------------------------------------------### Què fa el model?Modelitza el **hazard** (risc instantani de morir) en funció de predictors:$h(t) = h_0(t) \cdot \exp(\beta_1 X_1 + \beta_2 X_2 + \dots)$- h_0(t): risc basal no especificat (flexible)- $\exp(\beta_i)$ : **hazard ratio (HR)** associat a cada variable------------------------------------------------------------------------### ⚙️ Ajust del model amb `coxph()````{r}cox_model <-coxph(surv_lung ~ sex + age, data = lung)summary(cox_model)```------------------------------------------------------------------------### Interpretació dels coeficientsEls coeficients s’interpreten com a **hazard ratios**:- Si ( HR \> 1 ): el factor **augmenta el risc**- Si ( HR \< 1 ): el factor **redueix el risc**- Si ( HR = 1 ): no té efectePer veure els HR i els seus intervals de confiança:```{r}exp(coef(cox_model)) # HRexp(confint(cox_model)) # IC 95% dels HR```------------------------------------------------------------------------#### Exemple d'interpretacióA partir de la sortida del nostre model (`summary(cox_model)`), podem interpretar clínicament els coeficients exponenciats:* **Sexe (`sexDona`, $\text{HR} = 0.60$, IC 95%: 0.43–0.83, $p = 0.002$):** Com que la categoria de referència són els homes (`Home`), un HR de 0.60 indica que les dones tenen un **40% menys de risc instantani de mort** que els homes durant el seguiment, mantenint l'edat constant. * *Nota pedagògica (inversió del risc):* Si analitzem l'efecte a la inversa per conèixer el risc dels homes respecte a les dones ($1 / 0.60 \approx 1.67$, que correspon al valor `exp(-coef)` de la sortida d'R), els homes presenten un **67% més de risc de morir** que les dones.* **Edat (`age`, $\text{HR} = 1.017$, IC 95%: 0.999–1.036, $p = 0.065$):** Per cada any addicional d'edat, el risc instantani de mort s'incrementa un **1.7%** ($1.017 - 1 = 0.017$), ajustant pel sexe. En aquesta cohort, aquest efecte es troba al límit de la significació estadística convencional ($p > 0.05$).------------------------------------------------------------------------📌 El model de Cox és útil per:- Ajustar per múltiples variables- Interpretar l’efecte de factors en termes de **risc**- Afegir interaccions, variables categòriques i contínues------------------------------------------------------------------------## 🧪 Verificació de l’assumpció de riscos proporcionalsEl model de Cox **assumeix** que l’efecte de cada predictor és **constant en el temps**. És a dir:> El **hazard ratio (HR)** per a cada variable **no canvia al llarg del seguiment**.Això s’anomena l’**assumpció de riscos proporcionals**.------------------------------------------------------------------------### Què passa si no es compleix?Si l’HR canvia amb el temps (per exemple, un tractament només és efectiu al principi), el model pot ser **inadequat o enganyós**.------------------------------------------------------------------------### Test estadístic amb `cox.zph()````{r}test_proporcionalitat <-cox.zph(cox_model)test_proporcionalitat```- Comprova si hi ha **interacció entre el predictor i el temps**- Si el **valor p** és petit (\< 0.05), **es viola l’assumpció**------------------------------------------------------------------------### Gràfic de residuals de Schoenfeld```{r}plot(test_proporcionalitat)```- Si la línia del predictor **s’allunya clarament de la línia horitzontal**, pot haver-hi violació- Les línies de punts mostren el **95% IC** del test------------------------------------------------------------------------### ✅ Interpretació| Resultat | Què vol dir ||-----------|-------------------------------------------------------|| p \> 0.05 | L’assumpció de riscos proporcionals es compleix || p \< 0.05 | Podria haver-hi un efecte **no constant en el temps** |📌 Si es detecta violació, es poden considerar models amb **efectes dependents del temps** (nivell més avançat) o **estratificar** per aquella variable.------------------------------------------------------------------------## 🧩 Resum del capítol de supervivènciaEn aquest capítol hem après a treballar amb dades de temps fins a esdeveniment, i hem vist com analitzar-les i representar-les correctament.------------------------------------------------------------------------### Què hem après?| Eina / concepte | Què permet fer ||------------------------------------|------------------------------------|| `Surv()` | Codificar temps i censura per a cada individu || `survfit()` + `ggsurvplot()` | Estimar i visualitzar corbes de supervivència || `survdiff()` | Comparar grups amb el test de log-rank || `coxph()` | Ajustar un model amb múltiples predictors || `cox.zph()` | Comprovar l’assumpció de riscos proporcionals |------------------------------------------------------------------------### Bones pràctiques- **Validar la censura**: no oblidar que censurar no vol dir "no ha passat", sinó "no sabem si ha passat"- **Revisar supòsits**: tant amb log-rank com amb Cox- **Interpretar amb context clínic**: un HR de 2 pot ser rellevant... o no, segons el cas------------------------------------------------------------------------### 12. Exercicis pràctics del Capítol 5Posa a prova els conceptes apresos sobre censura, estimació no paramètrica de Kaplan-Meier, contrast de log-rank i regressió de Cox amb el teu propi script o document Quarto[cite: 1]:1. **Codificació de censura i objecte `Surv` manual:** Un assaig clínic segueix 6 pacients després d'una intervenció coronària percussió (temps en mesos): - Pacient 1: seguiment 6 mesos, esdeveniment (infart). - Pacient 2: seguiment 18 mesos, sense esdeveniment (pèrdua de seguiment). - Pacient 3: seguiment 24 mesos, sense esdeveniment (fi d'estudi). - Pacient 4: seguiment 12 mesos, esdeveniment (infart). - Pacient 5: seguiment 3 mesos, esdeveniment (infart). - Pacient 6: seguiment 24 mesos, sense esdeveniment (fi d'estudi). Construeix el *dataframe*, crea l'objecte `Surv()` i comprova quins casos queden marcats amb censura a la dreta (`+`)[cite: 1].2. **Exploració i neteja d'una cohort oncològica:** A partir del dataset `lung` del paquet `survival`[cite: 1]: - Recodifica la variable `status` de manera que l'esdeveniment (mort) sigui `1` i la censura sigui `0` (recorda que originàriament ve codificat com 1=censura, 2=èxitus)[cite: 1]. - Converteix `sex` en un factor amb les etiquetes `"Home"` i `"Dona"`[cite: 1]. - Quants pacients han patit l'esdeveniment i quants han estat censurats[cite: 1]?3. **Funció de supervivència de Kaplan-Meier global:** - Estima la corba de Kaplan-Meier per a tota la cohort `lung`[cite: 1]. - Quina és la **mediana de supervivència** estimada i el seu interval de confiança del 95%[cite: 1]? - Quina és la probabilitat de continuar viu als 365 dies (1 any)[cite: 1]?4. **Comparació de supervivència segons l'estat funcional (ECOG) i Log-Rank:** La variable `ph.ecog` mesura l'escala funcional de l'ECOG (0 = asimptomàtic fins a 3 = enllitat la major part del dia)[cite: 1]. - Filtra els pacients amb dades vàlides a `ph.ecog`[cite: 1]. - Estima i dibuixa les corbes de Kaplan-Meier segons `ph.ecog` incorporant la taula de pacients a risc amb `survminer::ggsurvplot()`[cite: 1]. - Realitza el test de log-rank (`survdiff`) per contrastar si les diferències de supervivència entre graus d'ECOG són estadísticament significatives[cite: 1].5. **Model de riscos proporcionals de Cox:** - Ajusta un model de regressió de Cox (`coxph`) per avaluar la mortalitat en funció de l'edat (`age`), el sexe (`sex`) i l'estat funcional (`ph.ecog`)[cite: 1]. - Mostra els Hazard Ratios (HR) i els seus intervals de confiança del 95%[cite: 1]. - Interpreta clínicament l'HR associat a `ph.ecog` mantenint constants l'edat i el sexe[cite: 1].6. **Diagnòstic del model: Verificació de l'assumpció de riscos proporcionals:** - Executa el test de diagnòstic basat en els residus de Schoenfeld amb `cox.zph()`[cite: 1]. - Mostra els gràfics de diagnòstic i conclou si el supòsit de riscos proporcionals es compleix tant individualment com globalment[cite: 1].---<details><summary>**Solucions als exercicis (fes clic per desplegar)**</summary>```{r}#| label: solucions-capitol-5#| code-fold: showlibrary(tidyverse)library(survival)library(survminer)# -------------------------------------------------------------# Exercici 1: Codificació de censura i objecte Surv manual# -------------------------------------------------------------cohort_manual <-tibble(pacient =1:6,temps_mesos =c(6, 18, 24, 12, 3, 24),esdeveniment =c(1, 0, 0, 1, 1, 0))surv_manual <-Surv(time = cohort_manual$temps_mesos, event = cohort_manual$esdeveniment)surv_manual# -------------------------------------------------------------# Exercici 2: Carrega neta de dades 'lung'# -------------------------------------------------------------# Carreguem directament les dades originals del paquet survival per evitar col·lisionsdf_lung_raw <-as_tibble(survival::lung)lung_exercicis <- df_lung_raw %>%mutate(status_net =ifelse(status ==2, 1, 0), # 1 = mort, 0 = censuratsexe =factor(sex, levels =c(1, 2), labels =c("Home", "Dona")),ecog =factor(ph.ecog) )# Recompte d'esdeveniments i censurestable(Estat = lung_exercicis$status_net)table(Sexe = lung_exercicis$sexe, Estat = lung_exercicis$status_net)# -------------------------------------------------------------# Exercici 3: Kaplan-Meier global i probabilitat a 1 any (365 dies)# -------------------------------------------------------------surv_obj_ex <-Surv(time = lung_exercicis$time, event = lung_exercicis$status_net)km_global <-survfit(surv_obj_ex ~1, data = lung_exercicis)summary(km_global)$table # Mediana i IC 95%# Estimació puntual de supervivència als 365 diessummary(km_global, times =365)# -------------------------------------------------------------# Exercici 4: Corbes per ECOG i contrast de Log-Rank# -------------------------------------------------------------lung_ecog_valids <- lung_exercicis %>%filter(!is.na(ecog))km_ecog <-survfit(Surv(time, status_net) ~ ecog, data = lung_ecog_valids)ggsurvplot( km_ecog,data = lung_ecog_valids,pval =TRUE,risk.table =TRUE,conf.int =FALSE,title ="Supervivència segons l'estatus ECOG (ph.ecog)",xlab ="Dies de seguiment",ylab ="Supervivència acumulada",risk.table.height =0.28)# Test de log-ranksurvdiff(Surv(time, status_net) ~ ecog, data = lung_ecog_valids)# -------------------------------------------------------------# Exercici 5: Model multivariable de Cox# -------------------------------------------------------------mod_cox <-coxph(Surv(time, status_net) ~ age + sexe + ecog, data = lung_exercicis)summary(mod_cox)# Hazard Ratios (HR) i intervals de confiança del 95%exp(cbind(HR =coef(mod_cox), confint(mod_cox)))# -------------------------------------------------------------# Exercici 6: Diagnòstic de riscos proporcionals (Schoenfeld)# -------------------------------------------------------------test_prop <-cox.zph(mod_cox)print(test_prop)# Diagnòstic visualpar(mfrow =c(2, 2))plot(test_prop)par(mfrow =c(1, 1))```