## New names:
## • `` -> `...44`
table1=table(sob1$cicat)
table2=table(sob1$AMPU)
table3=table(sob1$fallecio)
table1
##
## 0 1
## 189 216
table2
##
## 0 1
## 348 57
table3
##
## 0 1
## 391 14
prop.table(table1)
##
## 0 1
## 0.467 0.533
prop.table(table2)
##
## 0 1
## 0.859 0.141
prop.table(table3)
##
## 0 1
## 0.9654 0.0346
#pasar variables categóricas a factor y chr a factor
names(sob1)<- tolower(names (sob1))
sob1 <- sob1 %>%
mutate(across(19:35, as.factor))
#crear cicat_factor
sob1$cicat_factor <- factor(sob1$cicat, levels = c(0, 1), labels = c( "No Cicatrizo", "Cicatrizo"))
table(sob1$cicat_factor)
##
## No Cicatrizo Cicatrizo
## 189 216
#crear ampu_factor
sob1$ampu_factor <- factor(sob1$ampu, levels = c(0, 1), labels = c( "No Amputado", "Amputado"))
table(sob1$ampu_factor)
##
## No Amputado Amputado
## 348 57
#crear fallecio_factor
sob1$fallecio_factor <- factor(sob1$fallecio, levels = c(0, 1), labels = c( "No Fallecido", "Fallecido"))
table(sob1$fallecio_factor)
##
## No Fallecido Fallecido
## 391 14
#paso variables chr a factor
sob1 <- sob1 %>% mutate_if(is.character, as.factor)
#recategorizacion de variables
#ISQuEMIA
#isq_factor
sob1 <- sob1 %>%
mutate(isq_factor = factor(
if_else(isq %in% c(2, 3), "SI", "NO/leve")
))
#ISQuEMIA2
#isq_factor2
library(dplyr)
sob1 <- sob1 %>%
mutate(isq_factor2 = factor(
case_when(
isq == 0 ~ "NO",
isq == 1 ~ "LEVE",
isq == 2 ~ "MODERADA",
isq == 3 ~ "GRAVE",
TRUE ~ NA_character_
),
levels = c("NO", "LEVE", "MODERADA", "GRAVE")
))
table(sob1$isq)
##
## 0 1 2 3
## 205 31 47 122
table(sob1$isq_factor)
##
## NO/leve SI
## 236 169
table(sob1$isq_factor2)
##
## NO LEVE MODERADA GRAVE
## 205 31 47 122
#localizacion
sob1 <- sob1 %>%
mutate(loca_factor = factor(if_else(local %in% c(2, 3), "Met/Tar", "Fal")),
loca_factor = factor(loca_factor, levels = rev(levels(loca_factor))))
table(sob1$local)
##
## 1 2 3
## 195 134 76
table(sob1$loca_factor)
##
## Met/Tar Fal
## 210 195
#topog
sob1<- sob1 %>%
mutate(topo_factor = factor(if_else(topo %in% c(1,2), "dor/plan/lat", "Mas de dos")))
sob1 <- sob1 %>%
mutate(topo_factor = factor(if_else(topo %in% c(1, 2), "dor/plan/lat", "Mas de dos")),
topo_factor = factor(topo_factor, levels = rev(levels(topo_factor))))
table(sob1$topo)
##
## 1 2 3
## 152 103 150
table(sob1$topo_factor)
##
## Mas de dos dor/plan/lat
## 150 255
# zon
sob1 <- sob1 %>%
mutate(zon_factor = factor(if_else(zon %in% c(2, 3), "Dos o mas", "Una")), zon_factor = factor(zon_factor, levels = rev(levels(zon_factor))))
table(sob1$zon)
##
## 1 2 3
## 242 89 74
table(sob1$zon_factor)
##
## Una Dos o mas
## 242 163
#infe
sob1 <- sob1 %>%
mutate(infe_factor = case_when(
inf == 0 ~ "No",
inf %in% c(1, 2) ~ "Leve/Mod",
inf == 3 ~ "Grave",
TRUE ~ NA_character_ # conserva los NA originales
)) %>%
mutate(infe_factor = factor(infe_factor, levels = c("No", "Leve/Mod", "Grave")))
sob1$infe_factor <- factor (sob1$infe_factor, levels= c ("Grave","Leve/Mod","No"))
table(sob1$infe)
## Warning: Unknown or uninitialised column: `infe`.
## < table of extent 0 >
table(sob1$infe_factor)
##
## Grave Leve/Mod No
## 64 255 85
#edema
sob1 <- sob1 %>%
mutate(ede_factor = case_when(
ede == 2 ~ "Unilateral",
ede %in% c(0, 1) ~ "Sin/local",
ede == 3 ~ "Bilateral",
TRUE ~ NA_character_ # conserva los NA originales
)) %>%
mutate(ede_factor = factor(ede_factor, levels = c("Sin/local", "Unilateral", "Bilateral")))
sob1$ede_factor <- factor (sob1$ede_factor, levels= c ("Bilateral","Unilateral","Sin/local"))
table(sob1$ede)
##
## 0 1 2 3
## 142 98 124 41
table(sob1$ede_factor)
##
## Bilateral Unilateral Sin/local
## 41 124 240
#neur
sob1 <- sob1 %>%
mutate(neur_factor = factor(
if_else(neu %in% c(1, 2, 3), "Leve/mod/charcot", "No"),
levels = c("No", "Leve/mod/charcot")
))
table(sob1$neur)
## Warning: Unknown or uninitialised column: `neur`.
## < table of extent 0 >
table(sob1$neur_factor)
##
## No Leve/mod/charcot
## 4 401
#tisu (prof)
sob1 <- sob1 %>%
mutate(prof_factor = factor(if_else(prof %in% c(2, 3), "parcial/total", "Piel")))
table(sob1$prof)
##
## 1 2 3
## 31 133 241
table(sob1$prof_factor)
##
## parcial/total Piel
## 374 31
#area
sob1$area_factor <- with(sob1, ifelse(area == 1, "Menor a 10",
ifelse(area %in% c(1, 2), "10 a 40",
ifelse(area == 3, "Mas de 40", NA))))
sob1$area_factor <- factor(sob1$area_factor, levels = c("Menor a 10", "10 a 40", "Mas de 40"))
table(sob1$area)
##
## 1 2 3
## 187 157 61
table(sob1$area_factor)
##
## Menor a 10 10 a 40 Mas de 40
## 187 157 61
#cica(fase)
sob1 <- sob1 %>%
mutate(fase_factor = factor(if_else(fase %in% c(2, 3), "Granu/infla", "Epit")))
sob1$fase_factor <- factor (sob1$fase_factor, levels= c ("Granu/infla","Epit"))
table(sob1$fase)
##
## 1 2 3
## 18 51 336
table(sob1$fase_factor)
##
## Granu/infla Epit
## 387 18
hist(sob1$dias_out)
vis_miss(sob1)
#Tabla 1
library(tableone)
#Las variables van directamente sin el nombre del dataframe
catvars = c( "sexo","tbq", "iam", "hta", "dial", "local","topo", "inf", "ede", "neu", "prof", "area", "zon", "fase")
sob1 <- sob1 %>% mutate_if(is.character, as.factor)
#edad y enojo son continuas
vars = c("dias_ev","san_elian", "sexo","tbq", "iam", "hta", "dial", "local","topo", "inf", "ede", "neu", "prof", "area", "zon", "fase" )
tabla_1 <- CreateTableOne(vars = vars, strata = "isq_factor", factorVars = catvars, data = sob1)
print(tabla_1)
## Stratified by isq_factor
## NO/leve SI p test
## n 236 169
## dias_ev (mean (SD)) 47.59 (118.96) 54.08 (104.37) 0.570
## san_elian (mean (SD)) 16.92 (3.97) 20.19 (3.41) <0.001
## sexo = M (%) 190 (80.5) 133 (78.7) 0.748
## tbq (%) 0.185
## 0 106 (45.1) 62 (36.9)
## 1 41 (17.4) 39 (23.2)
## 2 88 (37.4) 67 (39.9)
## iam = 1 (%) 32 (13.6) 51 (30.4) <0.001
## hta = 1 (%) 143 (60.6) 129 (76.8) 0.001
## dial = 1 (%) 10 ( 4.2) 9 ( 5.3) 0.785
## local (%) <0.001
## 1 101 (42.8) 94 (55.6)
## 2 99 (41.9) 35 (20.7)
## 3 36 (15.3) 40 (23.7)
## topo (%) 0.002
## 1 104 (44.1) 48 (28.4)
## 2 60 (25.4) 43 (25.4)
## 3 72 (30.5) 78 (46.2)
## inf (%) 0.700
## 0 54 (22.9) 31 (18.3)
## 1 19 ( 8.1) 13 ( 7.7)
## 2 127 (53.8) 96 (56.8)
## 3 35 (14.8) 29 (17.2)
## 4 1 ( 0.4) 0 ( 0.0)
## ede (%) 0.050
## 0 70 (29.7) 72 (42.6)
## 1 64 (27.1) 34 (20.1)
## 2 78 (33.1) 46 (27.2)
## 3 24 (10.2) 17 (10.1)
## neu (%) <0.001
## 0 1 ( 0.4) 3 ( 1.8)
## 1 6 ( 2.5) 19 (11.2)
## 2 200 (84.7) 140 (82.8)
## 3 29 (12.3) 7 ( 4.1)
## prof (%) 0.022
## 1 25 (10.6) 6 ( 3.6)
## 2 79 (33.5) 54 (32.0)
## 3 132 (55.9) 109 (64.5)
## area (%) 0.063
## 1 120 (50.8) 67 (39.6)
## 2 86 (36.4) 71 (42.0)
## 3 30 (12.7) 31 (18.3)
## zon (%) 0.026
## 1 154 (65.3) 88 (52.1)
## 2 46 (19.5) 43 (25.4)
## 3 36 (15.3) 38 (22.5)
## fase (%) 0.010
## 1 15 ( 6.4) 3 ( 1.8)
## 2 36 (15.3) 15 ( 8.9)
## 3 185 (78.4) 151 (89.3)
kableone(tabla_1)
| NO/leve | SI | p | test | |
|---|---|---|---|---|
| n | 236 | 169 | ||
| dias_ev (mean (SD)) | 47.59 (118.96) | 54.08 (104.37) | 0.570 | |
| san_elian (mean (SD)) | 16.92 (3.97) | 20.19 (3.41) | <0.001 | |
| sexo = M (%) | 190 (80.5) | 133 (78.7) | 0.748 | |
| tbq (%) | 0.185 | |||
| 0 | 106 (45.1) | 62 (36.9) | ||
| 1 | 41 (17.4) | 39 (23.2) | ||
| 2 | 88 (37.4) | 67 (39.9) | ||
| iam = 1 (%) | 32 (13.6) | 51 (30.4) | <0.001 | |
| hta = 1 (%) | 143 (60.6) | 129 (76.8) | 0.001 | |
| dial = 1 (%) | 10 ( 4.2) | 9 ( 5.3) | 0.785 | |
| local (%) | <0.001 | |||
| 1 | 101 (42.8) | 94 (55.6) | ||
| 2 | 99 (41.9) | 35 (20.7) | ||
| 3 | 36 (15.3) | 40 (23.7) | ||
| topo (%) | 0.002 | |||
| 1 | 104 (44.1) | 48 (28.4) | ||
| 2 | 60 (25.4) | 43 (25.4) | ||
| 3 | 72 (30.5) | 78 (46.2) | ||
| inf (%) | 0.700 | |||
| 0 | 54 (22.9) | 31 (18.3) | ||
| 1 | 19 ( 8.1) | 13 ( 7.7) | ||
| 2 | 127 (53.8) | 96 (56.8) | ||
| 3 | 35 (14.8) | 29 (17.2) | ||
| 4 | 1 ( 0.4) | 0 ( 0.0) | ||
| ede (%) | 0.050 | |||
| 0 | 70 (29.7) | 72 (42.6) | ||
| 1 | 64 (27.1) | 34 (20.1) | ||
| 2 | 78 (33.1) | 46 (27.2) | ||
| 3 | 24 (10.2) | 17 (10.1) | ||
| neu (%) | <0.001 | |||
| 0 | 1 ( 0.4) | 3 ( 1.8) | ||
| 1 | 6 ( 2.5) | 19 (11.2) | ||
| 2 | 200 (84.7) | 140 (82.8) | ||
| 3 | 29 (12.3) | 7 ( 4.1) | ||
| prof (%) | 0.022 | |||
| 1 | 25 (10.6) | 6 ( 3.6) | ||
| 2 | 79 (33.5) | 54 (32.0) | ||
| 3 | 132 (55.9) | 109 (64.5) | ||
| area (%) | 0.063 | |||
| 1 | 120 (50.8) | 67 (39.6) | ||
| 2 | 86 (36.4) | 71 (42.0) | ||
| 3 | 30 (12.7) | 31 (18.3) | ||
| zon (%) | 0.026 | |||
| 1 | 154 (65.3) | 88 (52.1) | ||
| 2 | 46 (19.5) | 43 (25.4) | ||
| 3 | 36 (15.3) | 38 (22.5) | ||
| fase (%) | 0.010 | |||
| 1 | 15 ( 6.4) | 3 ( 1.8) | ||
| 2 | 36 (15.3) | 15 ( 8.9) | ||
| 3 | 185 (78.4) | 151 (89.3) |
#variables categorizadas
#Las variables van directamente sin el nombre del dataframe
catvars2 = c( "sexo","tbq", "iam", "hta", "dial", "loca_factor","topo_factor", "infe_factor", "ede_factor", "neur_factor", "prof_factor", "area_factor", "zon_factor", "fase_factor")
sob1 <- sob1 %>% mutate_if(is.character, as.factor)
#edad y enojo son continuas
#vars2 = c("dias_ev","san_elian", "sexo","tbq", "iam", "hta", "dial", "loca_factor","topo_factor", "infe_factor", "ede_factor", "neur_factor", "prof_factor", "area_factor", "zon_factor", "fase_factor" )
#tabla_2 <- CreateTableOne(vars = vars2, strata = "isq_factor", factorVars = catvars2, data = sob1)
#print(tabla_2)
#kableone(tabla_2)
#Elemento surv tiempo al evento cicatrización
tiempo_evento <- Surv(sob1$dias_out,sob1$cicat)
#evaluacion de la cantidad de eventos por tiempo de seguimiento a intervalos de a 30 dias
sob1 %>%
filter(cicat == 1) %>%
mutate(intervalo = cut(dias_out,
breaks = seq(0, max(dias_out), by = 30))) %>%
count(intervalo) %>%
ggplot(aes(x = intervalo, y = n)) +
geom_col(fill = "darkorange") +
labs(x = "Intervalos de tiempo (30 dias)",
y = "Eventos",
title = "Eventos por intervalo de seguimiento") +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
#Modelo de cox
#Efecto de la isquemia sobre el tiempo a la cicatrización sin eventos competitivos ajustado por edad y edema
cox_model <- coxph(tiempo_evento ~ isq_factor+ edad + ede_factor , data = sob1)
summary(cox_model)
## Call:
## coxph(formula = tiempo_evento ~ isq_factor + edad + ede_factor,
## data = sob1)
##
## n= 405, number of events= 216
##
## coef exp(coef) se(coef) z Pr(>|z|)
## isq_factorSI -0.74425 0.47509 0.17021 -4.37 0.000012 ***
## edad -0.01267 0.98741 0.00781 -1.62 0.10
## ede_factorUnilateral -0.06738 0.93484 0.25814 -0.26 0.79
## ede_factorSin/local 0.13589 1.14555 0.24062 0.56 0.57
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## exp(coef) exp(-coef) lower .95 upper .95
## isq_factorSI 0.475 2.105 0.340 0.663
## edad 0.987 1.013 0.972 1.003
## ede_factorUnilateral 0.935 1.070 0.564 1.550
## ede_factorSin/local 1.146 0.873 0.715 1.836
##
## Concordance= 0.628 (se = 0.022 )
## Likelihood ratio test= 36.8 on 4 df, p=0.0000002
## Wald test = 33.6 on 4 df, p=0.0000009
## Score (logrank) test = 35.3 on 4 df, p=0.0000004
tab_model(cox_model,
show.ci = TRUE, # Mostrar intervalo de confianza
show.se = TRUE, # Mostrar error estándar
transform = "exp", # Para mostrar HR en lugar de log(HR)
dv.labels = "Modelo de Cox - Datos drugs")
| Modelo de Cox - Datos drugs | ||||
|---|---|---|---|---|
| Predictors | Estimates | std. Error | CI | p |
| isq factorSI | 0.48 | 0.08 | 0.00 – Inf | <0.001 |
| edad | 0.99 | 0.01 | 0.00 – Inf | 0.105 |
| ede factor [Unilateral] | 0.93 | 0.24 | 0.00 – Inf | 0.794 |
| ede factorSin/local | 1.15 | 0.28 | 0.00 – Inf | 0.572 |
| Observations | 405 | |||
| R2 Nagelkerke | 0.087 | |||
confint(cox_model)
## 2.5 % 97.5 %
## isq_factorSI -1.078 -0.41065
## edad -0.028 0.00263
## ede_factorUnilateral -0.573 0.43857
## ede_factorSin/local -0.336 0.60749
#Supuesto de proporcionalidad
#Test de proporcionalidad de riesgos de Schoenfeld
test_ph <- cox.zph(cox_model)
test_ph
## chisq df p
## isq_factor 2.038 1 0.15
## edad 0.397 1 0.53
## ede_factor 1.610 2 0.45
## GLOBAL 3.921 4 0.42
#Gráficos por variables de residuos de Schoenfeld en el tiempo
ggcoxzph(test_ph)
## Warning: ggtheme is not a valid theme.
## Please use `theme()` to construct themes.
## ggtheme is not a valid theme.
## Please use `theme()` to construct themes.
## ggtheme is not a valid theme.
## Please use `theme()` to construct themes.
#gráficos loglog
##loglog isq_factor
ggsurvplot(
survfit(tiempo_evento ~ isq_factor, data = sob1),
fun = "cloglog",
xlab = "Tiempo (dias)",
ylab = "Log(-Log(S(t)))",
palette = "simpsons"
)
## 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.
## Warning: ggtheme is not a valid theme.
## Please use `theme()` to construct themes.
## Ignoring unknown labels:
## • fill : "Strata"
## • linetype : "1"
## Ignoring unknown labels:
## • fill : "Strata"
## • linetype : "1"
#ggsurvplot(
# survfit(tiempo_evento ~ infe_factor, data = sob1),
# fun = "cloglog",
# xlab = "Tiempo (dias)",
# ylab = "Log(-Log(S(t)))",
# palette = "simpsons"
#)
ggsurvplot(
survfit(tiempo_evento ~ ede_factor, data = sob1),
fun = "cloglog",
xlab = "Tiempo (dias)",
ylab = "Log(-Log(S(t)))",
palette = "simpsons"
)
## Warning: ggtheme is not a valid theme.
## Please use `theme()` to construct themes.
## Ignoring unknown labels:
## • fill : "Strata"
## • linetype : "1"
## Ignoring unknown labels:
## • fill : "Strata"
## • linetype : "1"
#Gráfico de Kaplan Meier
# Graficar las curvas de Kaplan-Meier estratificadas por isq
km_global <- survfit(tiempo_evento ~ 1, data = sob1)#ponemos 1 para marcar que es crudo, sin estratificar por ninguna variable
#6b. Ajustar el modelo Kaplan-Meier estratificado por "beck_cat"
km_fit <- survfit(tiempo_evento ~ isq_factor,data = sob1)
ggsurvplot(
km_fit,
data = sob1,
fun = "event", # 👈 ESTO hace la curva ascendente
risk.table = TRUE,
pval = TRUE,
conf.int = TRUE,
cumcensor = TRUE,
ggtheme = theme_minimal(),
palette = "simpsons",
xlab = "Tiempo de seguimiento (dias)",
ylab = "Probabilidad acumulada del evento"
)
## Ignoring unknown labels:
## • linetype : "1"
## Ignoring unknown labels:
## • linetype : "1"
## Ignoring unknown labels:
## • linetype : "1"
## Ignoring unknown labels:
## • linetype : "1"
## Ignoring unknown labels:
## • colour : "Strata"
## Ignoring unknown labels:
## • colour : "Strata"
#logrank
logrank <- survdiff(tiempo_evento ~ isq_factor, data = sob1)
logrank
## Call:
## survdiff(formula = tiempo_evento ~ isq_factor, data = sob1)
##
## N Observed Expected (O-E)^2/E (O-E)^2/V
## isq_factor=NO/leve 236 157 117 13.7 30.6
## isq_factor=SI 169 59 99 16.2 30.6
##
## Chisq= 30.6 on 1 degrees of freedom, p= 0.00000003
#Residuos de Martingale y Deviance: Adecuacion del modelo
# Martingale y Deviance
ggcoxdiagnostics(cox_model, type = "martingale", linear.predictions = FALSE)
## `geom_smooth()` using formula = 'y ~ x'
ggcoxdiagnostics(cox_model, type = "deviance", linear.predictions = FALSE)
## `geom_smooth()` using formula = 'y ~ x'
library(psych)
## Warning: package 'psych' was built under R version 4.4.3
##
## Adjuntando el paquete: 'psych'
## The following objects are masked from 'package:scales':
##
## alpha, rescale
## The following object is masked from 'package:ggsurvfit':
##
## %+%
## The following object is masked from 'package:Hmisc':
##
## describe
## The following objects are masked from 'package:ggplot2':
##
## %+%, alpha
table(sob1$estado)
##
## ampu AMPU cerro CERRO fallecio FALLECIO persiste persisTE
## 1 56 6 210 8 6 10 1
## perSISTE PERsiste PERSISTE
## 15 7 85
sob1 <- sob1 %>%
mutate(
estado = tolower(trimws(estado))
)
sob1 <- sob1 %>%
mutate(
estado_unif = case_when(
estado %in% c("ampu") ~ "amputacion",
estado %in% c("cerro") ~ "cerro_herida",
estado %in% c("murio", "fallecio") ~ "fallecio",
estado %in% c("persiste") ~ "persiste",
estado %in% c("perdido") ~ "perdido_seguimiento",
TRUE ~ NA_character_
)
)
table(sob1$estado_unif)
##
## amputacion cerro_herida fallecio persiste
## 57 216 14 118
describeBy(
x = sob1$edad,
group = sob1$estado_unif,
mat = TRUE
)
## item group1 vars n mean sd median trimmed mad min max range
## X11 1 amputacion 1 57 62.9 8.38 63.0 62.7 7.41 42 88 46
## X12 2 cerro_herida 1 216 55.1 10.90 55.5 55.1 11.12 22 89 67
## X13 3 fallecio 1 14 62.5 19.74 64.0 63.1 19.27 30 88 58
## X14 4 persiste 1 118 58.5 10.01 57.5 58.5 9.64 32 89 57
## skew kurtosis se
## X11 0.3347 0.632 1.110
## X12 -0.0242 0.378 0.741
## X13 -0.2246 -1.416 5.277
## X14 0.0962 -0.043 0.922
#Discriminación y calibración
#C de harrell
summary(cox_model)$concordance
## C se(C)
## 0.6283 0.0219
#bootstrap
#Indice D de Somer (Dxy), Metricas Q D U, slope y R2 con paquete `rms`
dd <- datadist(sob1); options(datadist = "dd")
## Warning in datadist(sob1): ...44 is constant
cox_metrics <- cph(tiempo_evento ~ edad + isq_factor + ede_factor,
data = sob1, x = TRUE, y = TRUE, surv = TRUE)
# Validamos métricas con bootstrap
invisible(capture.output({ #esta linea es para evitar que se imprima el output del bootstrap)
cox_rms <- validate(cox_metrics, method = "boot", B = 200)
}))
# Revisamos las metricas validadas (en la columna index.corrected)
cox_rms
## index.orig training test optimism index.corrected n
## Dxy 0.2566 0.2655 0.2440 0.0215 0.2351 200
## R2 0.0872 0.0968 0.0808 0.0160 0.0712 200
## Slope 1.0000 1.0000 0.9200 0.0800 0.9200 200
## D 0.0167 0.0188 0.0153 0.0035 0.0132 200
## U -0.0009 -0.0009 0.0007 -0.0016 0.0007 200
## Q 0.0176 0.0197 0.0147 0.0051 0.0125 200
## g 0.5073 0.5380 0.4817 0.0562 0.4510 200
round(cox_rms[,5],2)
## Dxy R2 Slope D U Q g
## 0.24 0.07 0.92 0.01 0.00 0.01 0.45
# Calibración. Evaluación Gráfica
invisible(capture.output({cal <- calibrate(cox_metrics, method = "boot", B = 200, u = 120)
}))
plot(cal,
subtitles = FALSE,
lwd = 2,
lty = 2,
main = "Grafico de Calibracion",
cex.main = 1.2,
cex.axis = 0.7,
cex.lab = 0.9,
xlab = "Probabilidad Predicha de Cicatrizacion",
ylab = "Cicatrizacion Observada")
La curva azul está sistemáticamente por encima de la diagonal.
Eso sugiere que el modelo está sobreestimando la probabilidad de cicatrización.
Porque ignora que:
muchos pacientes mueren
muchos se amputan
Y por lo tanto, inflan la probabilidad teórica de cicatrizar.
#evento cica tomando evento competitivo am y muerte
sob1 <- sob1 %>%
mutate(
evento_cica = case_when(
estado_unif == "cerro_herida" ~ 1,
estado_unif %in% c( "fallecio", "amputacion") ~ 2,
estado_unif %in% c("perdido_seguimiento", "persiste") ~ 0,
TRUE ~ NA_real_
)
)
table(sob1$evento_cica)
##
## 0 1 2
## 118 216 71
#Opcion con objeto creado
#si evento es 1, es porque se amputo o persiste
#si evento es 2, es porque se murió o se perdió
tiempo_cicat <- Surv(sob1$dias_out, sob1$evento_cica==1)
tiempo_compet <- Surv(sob1$dias_out, sob1$evento_cica==2)
cox_cicat <- coxph(tiempo_cicat ~
isq_factor + edad+
ede_factor , data = sob1)
summary(cox_cicat)
## Call:
## coxph(formula = tiempo_cicat ~ isq_factor + edad + ede_factor,
## data = sob1)
##
## n= 405, number of events= 216
##
## coef exp(coef) se(coef) z Pr(>|z|)
## isq_factorSI -0.74425 0.47509 0.17021 -4.37 0.000012 ***
## edad -0.01267 0.98741 0.00781 -1.62 0.10
## ede_factorUnilateral -0.06738 0.93484 0.25814 -0.26 0.79
## ede_factorSin/local 0.13589 1.14555 0.24062 0.56 0.57
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## exp(coef) exp(-coef) lower .95 upper .95
## isq_factorSI 0.475 2.105 0.340 0.663
## edad 0.987 1.013 0.972 1.003
## ede_factorUnilateral 0.935 1.070 0.564 1.550
## ede_factorSin/local 1.146 0.873 0.715 1.836
##
## Concordance= 0.628 (se = 0.022 )
## Likelihood ratio test= 36.8 on 4 df, p=0.0000002
## Wald test = 33.6 on 4 df, p=0.0000009
## Score (logrank) test = 35.3 on 4 df, p=0.0000004
cox_compet <- coxph(tiempo_compet ~
isq_factor + edad+
ede_factor , data = sob1)
summary(cox_compet)
## Call:
## coxph(formula = tiempo_compet ~ isq_factor + edad + ede_factor,
## data = sob1)
##
## n= 405, number of events= 71
##
## coef exp(coef) se(coef) z Pr(>|z|)
## isq_factorSI 1.2783 3.5907 0.3007 4.25 0.000021 ***
## edad 0.0270 1.0274 0.0124 2.18 0.029 *
## ede_factorUnilateral 0.3001 1.3500 0.4248 0.71 0.480
## ede_factorSin/local -0.2945 0.7449 0.4145 -0.71 0.477
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## exp(coef) exp(-coef) lower .95 upper .95
## isq_factorSI 3.591 0.279 1.992 6.47
## edad 1.027 0.973 1.003 1.05
## ede_factorUnilateral 1.350 0.741 0.587 3.10
## ede_factorSin/local 0.745 1.342 0.331 1.68
##
## Concordance= 0.709 (se = 0.031 )
## Likelihood ratio test= 42.8 on 4 df, p=0.00000001
## Wald test = 36.8 on 4 df, p=0.0000002
## Score (logrank) test = 42.2 on 4 df, p=0.00000002
#CIF
cifivhx <- cuminc(Surv(dias_out, factor(evento_cica)) ~ isq_factor, data=sob1)
cifivhx
##
## ── cuminc() ────────────────────────────────────────────────────────────────────
## • Failure type "1"
## strata time n.risk estimate std.error 95% CI
## NO/leve 100 82 0.501 0.035 0.432, 0.567
## NO/leve 200 22 0.742 0.034 0.669, 0.802
## NO/leve 300 3 0.893 0.029 0.819, 0.938
## SI 100 68 0.186 0.032 0.128, 0.252
## SI 200 23 0.398 0.044 0.311, 0.483
## SI 300 5 0.495 0.053 0.386, 0.594
## • Failure type "2"
## strata time n.risk estimate std.error 95% CI
## NO/leve 100 82 0.076 0.018 0.046, 0.116
## NO/leve 200 22 0.076 0.018 0.046, 0.116
## NO/leve 300 3 0.076 0.018 0.046, 0.116
## SI 100 68 0.283 0.036 0.216, 0.355
## SI 200 23 0.341 0.039 0.266, 0.417
## SI 300 5 0.355 0.041 0.276, 0.434
## • Tests
## outcome statistic df p.value
## 1 55.0 1.00 <0.001
## 2 40.0 1.00 <0.001
#Gráfico CIF
library(tidycmprsk)
library(ggsurvfit)
ggcuminc(
cifivhx,
outcome = c(1, 2),
linetype_aes = TRUE
) +
scale_color_manual(
name = "Isquemia",
values = c("NO/leve" = "#56B4E9", "SI" = "#E69F00")
) +
scale_linetype_manual(
name = "Tipo de evento",
values = c("solid", "dashed"),
labels = c(
"Evento de interes (cicatrizacion)",
"Evento competitivo (muerte / amputacion mayor)"
)
) +
labs(
x = "Tiempo (dias)",
y = "Incidencia acumulada"
) +
theme_minimal() +
theme(
legend.position = "bottom"
)
## Warning: Removed 2 rows containing missing values or values outside the scale range
## (`geom_step()`).
#Modelo de causa específica Cuando hacés predicción con este modelo, lo que obtenés es:
“Probabilidad de cicatrizar si los eventos competitivos fueran censura”
Es decir:
Trata muerte y amputación como si fueran pérdidas no informativas.
La probabilidad predicha no es la CIF real.
Entonces tu gráfico 1 está calibrando:
Riesgo derivado del modelo causa-específica (no la incidencia acumulada real).
#modelo de causa específica
modelo_CSC <- CSC(
formula = Hist(dias_out, evento_cica) ~ isq_factor+edad + ede_factor, data = sob1,
cause = 1 # Aclara que CHD es el evento de interés
)
# Ver resultados. Ojo! No funciona si hacés summary(modelo)
# Vamos a tener el modelo de interés y debajo el modelo competitivo
modelo_CSC
## CSC(formula = Hist(dias_out, evento_cica) ~ isq_factor + edad +
## ede_factor, data = sob1, cause = 1)
##
## Right-censored response of a competing.risks model
##
## No.Observations: 405
##
## Pattern:
##
## Cause event right.censored
## 1 216 0
## 2 71 0
## unknown 0 118
##
##
## ----------> Cause: 1
##
## Call:
## coxph(formula = survival::Surv(time, status) ~ isq_factor + edad +
## ede_factor, x = TRUE, y = TRUE)
##
## n= 405, number of events= 216
##
## coef exp(coef) se(coef) z Pr(>|z|)
## isq_factorSI -0.74425 0.47509 0.17021 -4.37 0.000012 ***
## edad -0.01267 0.98741 0.00781 -1.62 0.10
## ede_factorUnilateral -0.06738 0.93484 0.25814 -0.26 0.79
## ede_factorSin/local 0.13589 1.14555 0.24062 0.56 0.57
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## exp(coef) exp(-coef) lower .95 upper .95
## isq_factorSI 0.475 2.105 0.340 0.663
## edad 0.987 1.013 0.972 1.003
## ede_factorUnilateral 0.935 1.070 0.564 1.550
## ede_factorSin/local 1.146 0.873 0.715 1.836
##
## Concordance= 0.628 (se = 0.022 )
## Likelihood ratio test= 36.8 on 4 df, p=0.0000002
## Wald test = 33.6 on 4 df, p=0.0000009
## Score (logrank) test = 35.3 on 4 df, p=0.0000004
##
##
##
## ----------> Cause: 2
##
## Call:
## coxph(formula = survival::Surv(time, status) ~ isq_factor + edad +
## ede_factor, x = TRUE, y = TRUE)
##
## n= 405, number of events= 71
##
## coef exp(coef) se(coef) z Pr(>|z|)
## isq_factorSI 1.2783 3.5907 0.3007 4.25 0.000021 ***
## edad 0.0270 1.0274 0.0124 2.18 0.029 *
## ede_factorUnilateral 0.3001 1.3500 0.4248 0.71 0.480
## ede_factorSin/local -0.2945 0.7449 0.4145 -0.71 0.477
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## exp(coef) exp(-coef) lower .95 upper .95
## isq_factorSI 3.591 0.279 1.992 6.47
## edad 1.027 0.973 1.003 1.05
## ede_factorUnilateral 1.350 0.741 0.587 3.10
## ede_factorSin/local 0.745 1.342 0.331 1.68
##
## Concordance= 0.709 (se = 0.031 )
## Likelihood ratio test= 42.8 on 4 df, p=0.00000001
## Wald test = 36.8 on 4 df, p=0.0000002
## Score (logrank) test = 42.2 on 4 df, p=0.00000002
#Modelo de Fine and Gray Probabilidad real acumulada de cicatrización considerando muerte y amputación como eventos competitivos.
Tu segundo gráfico está calibrando:
Probabilidad real acumulada observada vs predicha (CIF).
#modelo de Fine and gray
modelo_fg <- FGR(
formula = Hist(dias_out, evento_cica) ~ isq_factor + edad+ede_factor
, data = sob1,
cause = 1 # Aclara que Cicatrizacion es el evento de interés
)
modelo_fg
##
## Right-censored response of a competing.risks model
##
## No.Observations: 405
##
## Pattern:
##
## Cause event right.censored
## 1 216 0
## 2 71 0
## unknown 0 118
##
##
## Fine-Gray model: analysis of cause 1
##
## Competing Risks Regression
##
## Call:
## FGR(formula = Hist(dias_out, evento_cica) ~ isq_factor + edad +
## ede_factor, data = sob1, cause = 1)
##
## coef exp(coef) se(coef) z p-value
## isq_factorSI -0.995 0.370 0.1699 -5.858 0.0000000047
## edad -0.015 0.985 0.0077 -1.952 0.0510000000
## ede_factorUnilateral -0.167 0.846 0.2521 -0.663 0.5100000000
## ede_factorSin/local 0.176 1.193 0.2349 0.751 0.4500000000
##
## exp(coef) exp(-coef) 2.5% 97.5%
## isq_factorSI 0.370 2.706 0.265 0.516
## edad 0.985 1.015 0.970 1.000
## ede_factorUnilateral 0.846 1.182 0.516 1.387
## ede_factorSin/local 1.193 0.838 0.753 1.890
##
## Num. cases = 405
## Pseudo Log-likelihood = -1111
## Pseudo likelihood ratio test = 67.1 on 4 df,
##
## Convergence: TRUE
#Comparación Causa específica y FyG
#score comparado con ipa
score_comp <-Score(
list("FG model" = modelo_fg, "CSC model" = modelo_CSC),
formula = Hist(dias_out, evento_cica) ~ 1,
data = sob1,
times = c(17:200),
cause = 1,
metrics ="auc",
summary = "IPA"
)
IPA = Index of Prediction Accuracy (Índice de Precisión Predictiva)
Es una medida que evalúa qué tan bien un modelo predice el riesgo en el tiempo, y se basa en el Brier score. Brier score: Mide el error cuadrático medio entre:lo que el modelo predice (probabilidad de evento a tiempo t) y lo que realmente ocurrió (valor esperado 0 a 0.12) IPA = 0 → el modelo no mejora al modelo nulo
IPA > 0 → mejora predictiva
IPA = 1 → predicción perfecta
IPA < 0 → peor que no usar modelo 😅
En la práctica:
0.05–0.10 ya puede ser clínicamente relevante
0.15 suele ser buen desempeño
#Extraigo los AUC por tiempo y modelo
score <- as.data.frame(score_comp$AUC$score)
# Gráfico
ggplot(score, aes(x = times, y = AUC, color = model)) +
geom_line(linewidth = 1.2) +
geom_point(size = 3) +
geom_hline(yintercept = 0.5, linetype = "dashed", alpha = 0.2) +
geom_hline(yintercept = 0.7, linetype = "dotted", alpha = 0.2) +
scale_color_manual(values = c("FG model" = "#E74C3C", "CSC model" = "#3498DB")) +
scale_y_continuous(limits = c(0.45, 0.85), breaks = seq(0.5, 0.8, 0.1)) +
labs(
title = "Discriminacion de Modelos en el Tiempo",
subtitle = "Linea punteada = Azar (0.5), Linea punteada = Adecuada (0.7)",
x = "Dias de seguimiento",
y = "AUC (Area bajo la curva ROC)",
color = "Modelo"
) +
theme_minimal() +
theme(legend.position = "bottom")
#Interpretamos que año a año la capacidad discriminativa de los modelos se mantiene alta, tanto en CSC como en FGR
# Calibracion global
s <- Score(list("FGR" = modelo_fg),
formula = Hist(dias_out, evento_cica) ~ 1,
data = sob1,
plots = "calibration",
summary = "IPA",
cause = 1)
plotCalibration(s, models = "FGR" )
La curva negra está mucho más cerca de la diagonal.
Eso es esperable porque:
Fine–Gray modela directamente la CIF
Está alineado con lo que realmente ocurre clínicamente
Además te muestra:
AUC = 70
Brier ≈ 0.20
Eso es desempeño predictivo razonable.