library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.1.4 ✔ readr 2.1.5
## ✔ forcats 1.0.0 ✔ stringr 1.5.1
## ✔ ggplot2 3.5.2 ✔ tibble 3.2.1
## ✔ lubridate 1.9.4 ✔ tidyr 1.3.1
## ✔ purrr 1.0.4
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(gtsummary)
library(MASS)
##
## Adjuntando el paquete: 'MASS'
##
## The following object is masked from 'package:gtsummary':
##
## select
##
## The following object is masked from 'package:dplyr':
##
## select
library(doParallel)
## Cargando paquete requerido: foreach
##
## Adjuntando el paquete: 'foreach'
##
## The following objects are masked from 'package:purrr':
##
## accumulate, when
##
## Cargando paquete requerido: iterators
## Cargando paquete requerido: parallel
library(missForest)
library(VIM)
## Cargando paquete requerido: colorspace
## Cargando paquete requerido: grid
## VIM is ready to use.
##
## Suggestions and bug-reports can be submitted at: https://github.com/statistikat/VIM/issues
##
## Adjuntando el paquete: 'VIM'
##
## The following object is masked from 'package:missForest':
##
## nrmse
##
## The following object is masked from 'package:datasets':
##
## sleep
library(ranger)
library(missForest)
ruta<-"D:/RosalindaSilveraT1/data_exam_ML_T1_2025_2.csv"
df <- read.csv(ruta) %>%
as_tibble()
print(df)
## # A tibble: 2,000 × 13
## age anaemia creatinine_phosphokinase diabetes ejection_fraction
## <int> <int> <int> <int> <int>
## 1 61 1 80 1 38
## 2 70 0 2695 1 NA
## 3 44 0 NA 1 40
## 4 NA 0 198 1 35
## 5 NA 0 897 NA 45
## 6 65 1 113 1 60
## 7 45 0 2442 1 30
## 8 53 0 NA 0 60
## 9 70 0 92 0 60
## 10 70 NA 81 NA 35
## # ℹ 1,990 more rows
## # ℹ 8 more variables: high_blood_pressure <int>, platelets <dbl>,
## # serum_creatinine <dbl>, serum_sodium <int>, sex <int>, smoking <int>,
## # time <int>, DEATH_EVENT <int>
set.seed(2025)
# Librería
glimpse(df)
## Rows: 2,000
## Columns: 13
## $ age <int> 61, 70, 44, NA, NA, 65, 45, 53, 70, 70, 54, 5…
## $ anaemia <int> 1, 0, 0, 0, 0, 1, 0, 0, 0, NA, 0, 0, 0, NA, N…
## $ creatinine_phosphokinase <int> 80, 2695, NA, 198, 897, 113, 2442, NA, 92, 81…
## $ diabetes <int> 1, 1, 1, 1, NA, 1, 1, 0, 0, NA, 1, 1, 0, 1, 0…
## $ ejection_fraction <int> 38, NA, 40, 35, 45, 60, 30, 60, 60, 35, NA, N…
## $ high_blood_pressure <int> 0, 0, 1, 1, 0, NA, 0, NA, 1, NA, 0, 0, 0, 0, …
## $ platelets <dbl> 282000, 241000, 235000, 281000, 297000, 20300…
## $ serum_creatinine <dbl> 1.40, 1.00, 0.70, 0.90, 1.00, 0.90, 1.10, 0.7…
## $ serum_sodium <int> 137, 137, 139, 137, 133, 140, 139, NA, 140, 1…
## $ sex <int> 1, 1, NA, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1,…
## $ smoking <int> NA, 0, 0, 1, 0, NA, 0, 1, 1, 0, 0, 0, 0, 0, 0…
## $ time <int> 213, 247, 79, 146, 80, 94, 129, NA, 74, 212, …
## $ DEATH_EVENT <int> 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, …
# Conversión de variables categóricas
df <- df %>% mutate(
anaemia = factor(anaemia),
diabetes = factor(diabetes),
high_blood_pressure = factor(high_blood_pressure),
sex = factor(sex),
smoking = factor(smoking),
DEATH_EVENT = factor(DEATH_EVENT, levels = c(0,1), labels = c("No", "Yes"))
)
# Instalación y carga de librería mice
if(!require(mice)) install.packages("mice")
## Cargando paquete requerido: mice
##
## Adjuntando el paquete: 'mice'
## The following object is masked from 'package:stats':
##
## filter
## The following objects are masked from 'package:base':
##
## cbind, rbind
library(mice)
# Resumen de los NA por columna
md.pattern(df)
## DEATH_EVENT sex creatinine_phosphokinase platelets diabetes
## 623 1 1 1 1 1
## 45 1 1 1 1 1
## 90 1 1 1 1 1
## 23 1 1 1 1 1
## 81 1 1 1 1 1
## 55 1 1 1 1 1
## 16 1 1 1 1 1
## 32 1 1 1 1 1
## 6 1 1 1 1 1
## 10 1 1 1 1 1
## 7 1 1 1 1 1
## 24 1 1 1 1 1
## 9 1 1 1 1 1
## 98 1 1 1 1 1
## 9 1 1 1 1 1
## 3 1 1 1 1 1
## 7 1 1 1 1 1
## 13 1 1 1 1 1
## 4 1 1 1 1 1
## 85 1 1 1 1 1
## 8 1 1 1 1 1
## 6 1 1 1 1 1
## 9 1 1 1 1 1
## 17 1 1 1 1 1
## 9 1 1 1 1 1
## 74 1 1 1 1 1
## 28 1 1 1 1 1
## 5 1 1 1 1 1
## 10 1 1 1 1 1
## 39 1 1 1 1 0
## 8 1 1 1 1 0
## 8 1 1 1 1 0
## 30 1 1 1 1 0
## 9 1 1 1 1 0
## 5 1 1 1 1 0
## 3 1 1 1 1 0
## 7 1 1 1 1 0
## 6 1 1 1 1 0
## 11 1 1 1 1 0
## 7 1 1 1 1 0
## 14 1 1 1 1 0
## 3 1 1 1 1 0
## 7 1 1 1 1 0
## 5 1 1 1 1 0
## 53 1 1 1 0 1
## 19 1 1 1 0 1
## 20 1 1 1 0 1
## 7 1 1 1 0 1
## 3 1 1 1 0 1
## 10 1 1 1 0 1
## 4 1 1 1 0 1
## 16 1 1 1 0 0
## 11 1 1 1 0 0
## 25 1 1 0 1 1
## 5 1 1 0 1 1
## 9 1 1 0 1 1
## 38 1 1 0 1 1
## 9 1 1 0 1 1
## 9 1 1 0 1 1
## 5 1 1 0 1 1
## 5 1 1 0 1 1
## 5 1 1 0 1 1
## 6 1 1 0 1 1
## 11 1 1 0 1 1
## 8 1 1 0 1 1
## 6 1 1 0 0 1
## 5 1 1 0 0 1
## 11 1 0 1 1 1
## 20 1 0 1 1 1
## 14 1 0 1 1 1
## 11 1 0 1 1 1
## 6 1 0 1 1 1
## 11 1 0 1 1 1
## 6 1 0 1 1 1
## 5 1 0 1 1 1
## 11 1 0 1 1 1
## 7 1 0 1 1 1
## 6 1 0 1 1 0
## 6 1 0 1 1 0
## 9 1 0 1 0 1
## 6 1 0 1 0 1
## 6 1 0 1 0 1
## 4 1 0 0 1 1
## 4 1 0 0 1 1
## 0 143 154 175 201
## serum_creatinine anaemia ejection_fraction time serum_sodium age smoking
## 623 1 1 1 1 1 1 1
## 45 1 1 1 1 1 1 1
## 90 1 1 1 1 1 1 0
## 23 1 1 1 1 1 1 0
## 81 1 1 1 1 1 0 1
## 55 1 1 1 1 0 1 1
## 16 1 1 1 1 0 0 0
## 32 1 1 1 0 1 1 1
## 6 1 1 1 0 1 1 1
## 10 1 1 1 0 1 1 0
## 7 1 1 1 0 1 0 1
## 24 1 1 1 0 0 1 1
## 9 1 1 1 0 0 1 0
## 98 1 1 0 1 1 1 1
## 9 1 1 0 1 1 1 1
## 3 1 1 0 1 1 0 1
## 7 1 1 0 1 0 1 1
## 13 1 1 0 0 1 1 1
## 4 1 1 0 0 1 1 1
## 85 1 0 1 1 1 1 1
## 8 1 0 1 1 1 0 1
## 6 1 0 1 0 1 1 1
## 9 1 0 1 0 1 1 1
## 17 1 0 1 0 1 1 0
## 9 1 0 0 1 1 1 1
## 74 0 1 1 1 1 1 1
## 28 0 1 1 1 1 1 1
## 5 0 1 1 1 1 0 1
## 10 0 1 0 1 0 1 1
## 39 1 1 1 1 1 1 1
## 8 1 1 1 1 1 1 1
## 8 1 1 1 1 1 1 0
## 30 1 1 1 1 1 0 1
## 9 1 1 1 1 0 1 1
## 5 1 1 1 1 0 1 1
## 3 1 1 0 1 1 1 1
## 7 1 1 0 1 0 0 1
## 6 1 1 0 0 1 1 0
## 11 1 0 1 1 1 1 1
## 7 1 0 1 1 1 1 1
## 14 0 1 1 1 1 1 1
## 3 0 1 1 1 0 1 1
## 7 0 1 1 0 0 1 1
## 5 0 1 0 1 1 1 1
## 53 1 1 1 1 1 1 1
## 19 1 1 1 1 1 1 1
## 20 1 1 1 1 1 1 0
## 7 1 1 1 1 1 1 0
## 3 1 1 1 1 1 0 1
## 10 1 1 1 0 1 1 1
## 4 0 1 1 1 1 1 1
## 16 1 1 1 1 1 1 1
## 11 1 0 1 1 1 1 1
## 25 1 1 1 1 1 1 1
## 5 1 1 1 1 1 0 1
## 9 1 1 1 1 1 0 0
## 38 1 1 1 1 0 1 1
## 9 1 1 1 0 1 1 1
## 9 1 1 0 1 1 1 1
## 5 1 1 0 1 0 1 1
## 5 1 0 1 1 1 1 1
## 5 1 0 1 0 1 0 1
## 6 0 1 1 1 1 1 1
## 11 0 0 1 1 1 0 1
## 8 0 0 1 0 1 1 1
## 6 1 1 1 1 1 1 1
## 5 1 1 1 0 0 1 1
## 11 1 1 1 1 1 1 1
## 20 1 1 1 1 1 0 1
## 14 1 1 1 0 1 1 1
## 11 1 1 1 0 0 1 1
## 6 1 1 0 1 0 1 0
## 11 1 0 1 1 1 1 1
## 6 1 0 1 1 1 0 1
## 5 0 1 1 1 1 1 1
## 11 0 1 1 1 1 0 1
## 7 0 1 0 1 1 1 1
## 6 1 1 1 1 1 1 1
## 6 1 1 1 1 1 1 0
## 9 1 1 1 1 1 1 1
## 6 1 1 0 1 0 1 1
## 6 0 1 1 1 1 1 1
## 4 1 1 1 1 1 1 1
## 4 0 1 0 1 1 1 1
## 208 209 211 212 223 227 227
## high_blood_pressure
## 623 1 0
## 45 0 1
## 90 1 1
## 23 0 2
## 81 1 1
## 55 1 1
## 16 1 3
## 32 1 1
## 6 0 2
## 10 0 3
## 7 1 2
## 24 1 2
## 9 0 4
## 98 1 1
## 9 0 2
## 3 1 2
## 7 1 2
## 13 1 2
## 4 0 3
## 85 1 1
## 8 1 2
## 6 1 2
## 9 0 3
## 17 1 3
## 9 1 2
## 74 1 1
## 28 0 2
## 5 1 2
## 10 1 3
## 39 1 1
## 8 0 2
## 8 1 2
## 30 1 2
## 9 1 2
## 5 0 3
## 3 1 2
## 7 1 4
## 6 1 4
## 11 1 2
## 7 0 3
## 14 1 2
## 3 0 4
## 7 1 4
## 5 1 3
## 53 1 1
## 19 0 2
## 20 1 2
## 7 0 3
## 3 1 2
## 10 1 2
## 4 1 2
## 16 1 2
## 11 1 3
## 25 1 1
## 5 1 2
## 9 0 4
## 38 1 2
## 9 1 2
## 9 0 3
## 5 0 4
## 5 1 2
## 5 0 5
## 6 1 2
## 11 1 4
## 8 1 4
## 6 1 2
## 5 0 5
## 11 1 1
## 20 0 3
## 14 1 2
## 11 1 3
## 6 1 4
## 11 1 2
## 6 1 3
## 5 1 2
## 11 1 3
## 7 1 3
## 6 1 2
## 6 1 3
## 9 1 2
## 6 1 4
## 6 1 3
## 4 1 2
## 4 1 4
## 245 2435
# Ejecutar imputación usando el método por defecto
set.seed(2025)
imputacion <- mice(df, m = 1, maxit = 5, seed = 2025)
##
## iter imp variable
## 1 1 age anaemia creatinine_phosphokinase diabetes ejection_fraction high_blood_pressure platelets serum_creatinine serum_sodium sex smoking time
## 2 1 age anaemia creatinine_phosphokinase diabetes ejection_fraction high_blood_pressure platelets serum_creatinine serum_sodium sex smoking time
## 3 1 age anaemia creatinine_phosphokinase diabetes ejection_fraction high_blood_pressure platelets serum_creatinine serum_sodium sex smoking time
## 4 1 age anaemia creatinine_phosphokinase diabetes ejection_fraction high_blood_pressure platelets serum_creatinine serum_sodium sex smoking time
## 5 1 age anaemia creatinine_phosphokinase diabetes ejection_fraction high_blood_pressure platelets serum_creatinine serum_sodium sex smoking time
# Revisar el resumen de la imputación
summary(imputacion)
## Class: mids
## Number of multiple imputations: 1
## Imputation methods:
## age anaemia creatinine_phosphokinase
## "pmm" "logreg" "pmm"
## diabetes ejection_fraction high_blood_pressure
## "logreg" "pmm" "logreg"
## platelets serum_creatinine serum_sodium
## "pmm" "pmm" "pmm"
## sex smoking time
## "logreg" "logreg" "pmm"
## DEATH_EVENT
## ""
## PredictorMatrix:
## age anaemia creatinine_phosphokinase diabetes
## age 0 1 1 1
## anaemia 1 0 1 1
## creatinine_phosphokinase 1 1 0 1
## diabetes 1 1 1 0
## ejection_fraction 1 1 1 1
## high_blood_pressure 1 1 1 1
## ejection_fraction high_blood_pressure platelets
## age 1 1 1
## anaemia 1 1 1
## creatinine_phosphokinase 1 1 1
## diabetes 1 1 1
## ejection_fraction 0 1 1
## high_blood_pressure 1 0 1
## serum_creatinine serum_sodium sex smoking time
## age 1 1 1 1 1
## anaemia 1 1 1 1 1
## creatinine_phosphokinase 1 1 1 1 1
## diabetes 1 1 1 1 1
## ejection_fraction 1 1 1 1 1
## high_blood_pressure 1 1 1 1 1
## DEATH_EVENT
## age 1
## anaemia 1
## creatinine_phosphokinase 1
## diabetes 1
## ejection_fraction 1
## high_blood_pressure 1
# Obtener el dataset imputado completo
df_imputado <- complete(imputacion)
# Verificar que no queden NA
colSums(is.na(df_imputado))
## age anaemia creatinine_phosphokinase
## 0 0 0
## diabetes ejection_fraction high_blood_pressure
## 0 0 0
## platelets serum_creatinine serum_sodium
## 0 0 0
## sex smoking time
## 0 0 0
## DEATH_EVENT
## 0
library(dplyr)
library(gtsummary)
# Variables continuas y categóricas sin DEATH_EVENT
continuas <- c("age", "creatinine_phosphokinase", "ejection_fraction", "platelets", "serum_creatinine", "serum_sodium", "time")
categoricas <- c("anaemia", "diabetes", "high_blood_pressure", "sex", "smoking")
# Tabla resumen agrupada por DEATH_EVENT
table1 <- tbl_summary(
df_imputado,
by = "DEATH_EVENT",
type = list(
all_of(continuas) ~ "continuous",
all_of(categoricas) ~ "categorical"
),
missing = "no"
)
table1
| Characteristic | No N = 1,3591 |
Yes N = 6411 |
|---|---|---|
| age | 60 (50, 67) | 60 (51, 75) |
| anaemia | ||
| 0 | 762 (56%) | 375 (59%) |
| 1 | 597 (44%) | 266 (41%) |
| creatinine_phosphokinase | 257 (109, 618) | 280 (128, 582) |
| diabetes | ||
| 0 | 792 (58%) | 359 (56%) |
| 1 | 567 (42%) | 282 (44%) |
| ejection_fraction | 38 (35, 45) | 35 (25, 38) |
| high_blood_pressure | ||
| 0 | 924 (68%) | 399 (62%) |
| 1 | 435 (32%) | 242 (38%) |
| platelets | 257,000 (219,000, 302,000) | 262,000 (200,000, 314,000) |
| serum_creatinine | 1.00 (0.90, 1.20) | 1.30 (1.10, 1.90) |
| serum_sodium | 137 (135, 140) | 134 (132, 137) |
| sex | ||
| 0 | 451 (33%) | 217 (34%) |
| 1 | 908 (67%) | 424 (66%) |
| smoking | ||
| 0 | 881 (65%) | 421 (66%) |
| 1 | 478 (35%) | 220 (34%) |
| time | 147 (95, 212) | 45 (20, 111) |
| 1 Median (Q1, Q3); n (%) | ||
Los pacientes fallecidos tienden a tener una mayor variabilidad hacia edades más avanzadas, esto sugiere que existe un mayor riesgo en edades mayores. La proporción de anemia es ligeramente menor en fallecidos (41%) que en no fallecidos (44%). No existe un diferencia marcada de mortalidad respecto a las personas que tiene diabetes de las que no lo tienen. En el caso de la creatinina serica los fallecidos presentan valores mas elevados indicando grave insuficiencia renal. No existe una diferencia marcada entre el sexo de los fallecidos.
library(gtsummary)
# Variables continuas y categóricas en el contexto de insuficiencia cardíaca
continuas <- c("age", "creatinine_phosphokinase", "ejection_fraction",
"platelets", "serum_creatinine", "serum_sodium", "time")
categoricas <- c("anaemia", "diabetes", "high_blood_pressure",
"sex", "smoking")
# Crear tabla resumen con gtsummary
table2 <- tbl_summary(
df_imputado,
by = DEATH_EVENT, # Agrupar por evento de muerte
type = list(
all_of(continuas) ~ "continuous",
all_of(categoricas) ~ "categorical"
),
missing = "no"
) %>%
add_n() %>% # Número total de observaciones por grupo
add_p() %>% # Test estadístico (t-test, chi-cuadrado, etc.)
modify_header(label = "**Variable**") %>% # Cambiar encabezado de la columna de variables
bold_labels() # Resaltar nombres de las variables
table2
| Variable | N | No N = 1,3591 |
Yes N = 6411 |
p-value2 |
|---|---|---|---|---|
| age | 2,000 | 60 (50, 67) | 60 (51, 75) | <0.001 |
| anaemia | 2,000 | 0.3 | ||
| 0 | 762 (56%) | 375 (59%) | ||
| 1 | 597 (44%) | 266 (41%) | ||
| creatinine_phosphokinase | 2,000 | 257 (109, 618) | 280 (128, 582) | 0.6 |
| diabetes | 2,000 | 0.3 | ||
| 0 | 792 (58%) | 359 (56%) | ||
| 1 | 567 (42%) | 282 (44%) | ||
| ejection_fraction | 2,000 | 38 (35, 45) | 35 (25, 38) | <0.001 |
| high_blood_pressure | 2,000 | 0.011 | ||
| 0 | 924 (68%) | 399 (62%) | ||
| 1 | 435 (32%) | 242 (38%) | ||
| platelets | 2,000 | 257,000 (219,000, 302,000) | 262,000 (200,000, 314,000) | 0.093 |
| serum_creatinine | 2,000 | 1.00 (0.90, 1.20) | 1.30 (1.10, 1.90) | <0.001 |
| serum_sodium | 2,000 | 137 (135, 140) | 134 (132, 137) | <0.001 |
| sex | 2,000 | 0.8 | ||
| 0 | 451 (33%) | 217 (34%) | ||
| 1 | 908 (67%) | 424 (66%) | ||
| smoking | 2,000 | 0.7 | ||
| 0 | 881 (65%) | 421 (66%) | ||
| 1 | 478 (35%) | 220 (34%) | ||
| time | 2,000 | 147 (95, 212) | 45 (20, 111) | <0.001 |
| 1 Median (Q1, Q3); n (%) | ||||
| 2 Wilcoxon rank sum test; Pearson’s Chi-squared test | ||||
Edad (age) La mediana de edad es 60 años en ambos grupos, aunque quienes fallecieron presentan un rango de edad más amplio (51–75 años frente a 50–67 años). Esto indica que, a pesar de que la mediana sea igual, la mortalidad tiende a aumentar con la edad (p < 0.001).
Anemia (anaemia) Las proporciones de anemia son similares entre los grupos (44% frente a 41%) y no se observa una diferencia estadísticamente significativa (p = 0.3), sugiriendo que la anemia no está claramente relacionada con la muerte.
CPK (creatinine_phosphokinase) Los niveles son comparables entre los grupos y no presentan diferencia significativa (p = 0.6), lo que implica que este marcador no tiene una fuerte asociación con la mortalidad en este análisis.
Diabetes Las proporciones son muy parecidas (42% frente a 44%) y no hay diferencias significativas (p = 0.3), por lo que la diabetes no parece estar vinculada directamente a la mortalidad en este conjunto de datos.
Fracción de eyección (ejection_fraction) Los fallecidos tienen una mediana más baja (35 vs. 38), con una diferencia significativa (p < 0.001), lo que indica una función cardíaca deteriorada asociada a un mayor riesgo de muerte.
Presión arterial alta (high_blood_pressure) Esta condición es más frecuente en el grupo de fallecidos (38% vs. 32%) y presenta una diferencia significativa (p = 0.011), sugiriendo que la hipertensión está asociada a un incremento ligero en la mortalidad.
Plaquetas (platelets) Los niveles son ligeramente más elevados en quienes murieron, aunque sin alcanzar significancia estadística (p = 0.093), por lo que no se asocia de forma clara con la muerte.
Creatinina sérica (serum_creatinine) Las concentraciones son mayores en el grupo de fallecidos (1.30 vs. 1.00) y muestran una asociación fuerte y significativa (p < 0.001), reflejando un posible deterioro de la función renal en estos pacientes.
Sodio sérico (serum_sodium) Los niveles son menores en los fallecidos (134 vs. 137), con una diferencia significativa (p < 0.001), indicando que la hiponatremia podría estar vinculada a un mayor riesgo de muerte.
Sexo (sex) La distribución es similar en ambos grupos (66% hombres), sin diferencias significativas (p = 0.8), por lo que el sexo no parece influir en la mortalidad.
Tabaquismo (smoking) Las proporciones son parecidas (35% frente a 34%) y no hay diferencias significativas (p = 0.7), indicando que fumar no se asocia directamente con la muerte en este análisis.
Tiempo de seguimiento (time) El tiempo de seguimiento es notablemente menor en quienes fallecieron (45 días vs. 147 días), con una fuerte asociación estadística (p < 0.001), lo que refleja que la muerte ocurrió en un periodo más corto, aspecto relevante para el análisis de supervivencia.
# Asegurarse de que DEATH_EVENT esté como factor (variable categórica)
df_imputado$DEATH_EVENT <- as.factor(df_imputado$DEATH_EVENT)
# Ajustar modelo completo con todas las variables
mod1 <- glm(DEATH_EVENT ~ ., data = df_imputado, family = binomial(link = "logit"))
# Aplicar el método de selección hacia atrás basado en AIC
mod_aic <- step(mod1)
## Start: AIC=1589.53
## DEATH_EVENT ~ age + anaemia + creatinine_phosphokinase + diabetes +
## ejection_fraction + high_blood_pressure + platelets + serum_creatinine +
## serum_sodium + sex + smoking + time
##
## Df Deviance AIC
## - diabetes 1 1564.0 1588.0
## - smoking 1 1564.2 1588.2
## - high_blood_pressure 1 1565.5 1589.5
## <none> 1563.5 1589.5
## - sex 1 1566.3 1590.3
## - platelets 1 1566.8 1590.8
## - age 1 1571.1 1595.1
## - creatinine_phosphokinase 1 1576.7 1600.7
## - anaemia 1 1581.7 1605.7
## - serum_sodium 1 1583.4 1607.4
## - serum_creatinine 1 1626.6 1650.6
## - ejection_fraction 1 1696.7 1720.7
## - time 1 2125.7 2149.7
##
## Step: AIC=1587.95
## DEATH_EVENT ~ age + anaemia + creatinine_phosphokinase + ejection_fraction +
## high_blood_pressure + platelets + serum_creatinine + serum_sodium +
## sex + smoking + time
##
## Df Deviance AIC
## - smoking 1 1564.7 1586.7
## - high_blood_pressure 1 1565.8 1587.8
## <none> 1564.0 1588.0
## - platelets 1 1566.9 1588.9
## - sex 1 1567.0 1589.0
## - age 1 1572.2 1594.2
## - creatinine_phosphokinase 1 1577.2 1599.2
## - anaemia 1 1582.2 1604.2
## - serum_sodium 1 1584.0 1606.0
## - serum_creatinine 1 1626.8 1648.8
## - ejection_fraction 1 1696.7 1718.7
## - time 1 2126.4 2148.4
##
## Step: AIC=1586.73
## DEATH_EVENT ~ age + anaemia + creatinine_phosphokinase + ejection_fraction +
## high_blood_pressure + platelets + serum_creatinine + serum_sodium +
## sex + time
##
## Df Deviance AIC
## - high_blood_pressure 1 1566.6 1586.6
## <none> 1564.7 1586.7
## - platelets 1 1567.9 1587.9
## - sex 1 1570.2 1590.2
## - age 1 1573.2 1593.2
## - creatinine_phosphokinase 1 1577.7 1597.7
## - anaemia 1 1582.2 1602.2
## - serum_sodium 1 1584.7 1604.7
## - serum_creatinine 1 1632.4 1652.4
## - ejection_fraction 1 1699.1 1719.1
## - time 1 2132.3 2152.3
##
## Step: AIC=1586.63
## DEATH_EVENT ~ age + anaemia + creatinine_phosphokinase + ejection_fraction +
## platelets + serum_creatinine + serum_sodium + sex + time
##
## Df Deviance AIC
## <none> 1566.6 1586.6
## - platelets 1 1569.7 1587.7
## - sex 1 1573.0 1591.0
## - age 1 1575.4 1593.4
## - creatinine_phosphokinase 1 1578.4 1596.4
## - anaemia 1 1584.1 1602.1
## - serum_sodium 1 1586.7 1604.7
## - serum_creatinine 1 1632.4 1650.4
## - ejection_fraction 1 1701.6 1719.6
## - time 1 2161.0 2179.0
# Mostrar resumen del modelo seleccionado
summary(mod_aic)
##
## Call:
## glm(formula = DEATH_EVENT ~ age + anaemia + creatinine_phosphokinase +
## ejection_fraction + platelets + serum_creatinine + serum_sodium +
## sex + time, family = binomial(link = "logit"), data = df_imputado)
##
## Coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) 1.301e+01 2.046e+00 6.362 2.00e-10 ***
## age 3.036e-05 9.331e-06 3.254 0.00114 **
## anaemia1 -5.683e-01 1.376e-01 -4.130 3.63e-05 ***
## creatinine_phosphokinase 2.171e-04 6.639e-05 3.270 0.00108 **
## ejection_fraction -6.783e-02 6.384e-03 -10.624 < 2e-16 ***
## platelets -1.187e-06 6.905e-07 -1.719 0.08561 .
## serum_creatinine 5.249e-01 7.683e-02 6.832 8.38e-12 ***
## serum_sodium -6.546e-02 1.464e-02 -4.471 7.79e-06 ***
## sex1 -3.515e-01 1.393e-01 -2.523 0.01165 *
## time -2.263e-02 1.189e-03 -19.029 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for binomial family taken to be 1)
##
## Null deviance: 2509.0 on 1999 degrees of freedom
## Residual deviance: 1566.6 on 1990 degrees of freedom
## AIC: 1586.6
##
## Number of Fisher Scoring iterations: 5
library(gtsummary)
# Mostrar tabla de regresión con odds ratios e IC al 95%
t1 <- tbl_regression(mod_aic, exponentiate = TRUE)
t1
| Characteristic | OR | 95% CI | p-value |
|---|---|---|---|
| age | 1.00 | 1.00, 1.00 | 0.001 |
| anaemia | |||
| 0 | — | — | |
| 1 | 0.57 | 0.43, 0.74 | <0.001 |
| creatinine_phosphokinase | 1.00 | 1.00, 1.00 | 0.001 |
| ejection_fraction | 0.93 | 0.92, 0.95 | <0.001 |
| platelets | 1.00 | 1.00, 1.00 | 0.086 |
| serum_creatinine | 1.69 | 1.46, 1.98 | <0.001 |
| serum_sodium | 0.94 | 0.91, 0.96 | <0.001 |
| sex | |||
| 0 | — | — | |
| 1 | 0.70 | 0.54, 0.92 | 0.012 |
| time | 0.98 | 0.98, 0.98 | <0.001 |
| Abbreviations: CI = Confidence Interval, OR = Odds Ratio | |||
En cuanto a la edad, el valor p es significativo, lo que indica que a mayor edad, el riesgo de fallecer también aumenta.
Los pacientes con anemia presentan un 43% menos de probabilidad de morir en comparación con aquellos que no tienen anemia.
La creatinina muestra un valor p significativo, sugiriendo que niveles más elevados se relacionan con un mayor riesgo de muerte.
Respecto a la fracción de eyección, el riesgo de mortalidad disminuye un 7% por cada incremento del 1% en esta medida.
Cada aumento en los niveles de creatinina se asocia con un 69% más de riesgo de fallecer, lo que refleja una función renal deteriorada que puede agravar el pronóstico.
Por cada unidad adicional de sodio en sangre, el riesgo de muerte se reduce en un 6%.
Los hombres tienen un riesgo un 30% menor de morir en comparación con las mujeres.
Los principales factores vinculados con un mayor riesgo de mortalidad son: Niveles elevados de creatinina (indicativo de mala función renal),
Fracción de eyección baja (indicativa de función cardíaca comprometida),
Niveles bajos de sodio (que reflejan desequilibrios electrolíticos).
# Extraer solo las variables seleccionadas por AIC, puedes ajustar esta lista según output de step()
data_model_sel2 <- df_imputado %>%
dplyr::select(DEATH_EVENT, age, ejection_fraction, serum_creatinine, serum_sodium, time)
# Exportar a CSV
write.csv(data_model_sel2, "data_select.csv", row.names = FALSE, quote = FALSE)
#%% Cargando librerias #
import pandas as pd
import numpy as np
from collections import defaultdict
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder, LabelEncoder, MinMaxScaler, OrdinalEncoder
from imblearn.over_sampling import RandomOverSampler, SMOTE
from imblearn.under_sampling import RandomUnderSampler
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, make_scorer
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from xgboost import XGBClassifier
import seaborn as sns
import matplotlib.pyplot as plt
import os
import random
from sklearn.base import BaseEstimator, TransformerMixin
import joblib
def softM(num1,LAMBDA=2):
PI = np.pi
AVG = np.mean(num1)
SD = np.std(num1)
#LAMBDA = 2
x1 = num1
vt = (x1- AVG)/(LAMBDA * (SD/(2 * PI)))
trans = 1/(1 + np.exp(-vt))
return(trans)
class softMax(BaseEstimator, TransformerMixin):
def __init__(self,LAMBDA=2):
self.LAMBDA = LAMBDA
#self.parametro2 = parametro2
pass
def fit(self, X, y=None):
return self
def transform(self, X):
res = X.apply(softM,LAMBDA=self.LAMBDA)
return res
df2 = pd.read_csv("data_select.csv")
print(df2.head())
## DEATH_EVENT age ejection_fraction serum_creatinine serum_sodium time
## 0 No 61 38 1.4 137 213
## 1 No 70 60 1.0 137 247
## 2 No 44 40 0.7 139 79
## 3 No 72 35 0.9 137 146
## 4 No 45 45 1.0 133 80
# Mapear variable objetivo DEATH_EVENT a 0 y 1 (si está en "No" y "Yes")
df2["DEATH_EVENT"] = df2["DEATH_EVENT"].map({"No": 0, "Yes": 1})
# Si ya está en 0 y 1 o números, asegúrate que sea tipo entero
df2["DEATH_EVENT"] = df2["DEATH_EVENT"].astype(int)
# Definir variables numéricas y variable objetivo
numerical_vars = ['age', 'ejection_fraction', 'serum_creatinine', 'serum_sodium', 'time']
target_var = ['DEATH_EVENT']
# Transformador de la data
full_transform = ColumnTransformer([
("num1", MinMaxScaler(), numerical_vars)
])
X = full_transform.fit_transform(df2)
# Guardar el transformador
joblib.dump(full_transform, 'transformador.pkl')
## ['transformador.pkl']
# Cargar y usar el transformador guardado
loaded_trans = joblib.load('transformador.pkl')
X_loaded = loaded_trans.transform(df2)
'''
# Ordinal encoder
label_encoder = OrdinalEncoder(categories=[["Yes","No"]])
y = label_encoder.fit_transform(df1[target]).ravel().astype(int)
'''
## '\n# Ordinal encoder\nlabel_encoder = OrdinalEncoder(categories=[["Yes","No"]])\ny = label_encoder.fit_transform(df1[target]).ravel().astype(int)\n'
label_encoder = LabelEncoder()
ordered_levels = ['No', 'Yes']
label_encoder.classes_ = np.array(ordered_levels) # Opcional para fijar el orden
y = label_encoder.fit_transform(df2[target_var].values.ravel())
print(label_encoder.classes_) # Verifica las clases
## [0 1]
print(y[:10]) # Muestra las primeras 10 etiquetas transformadas
## [0 0 0 0 0 0 1 0 0 0]
#Balanceo de datos
# Visualizar distribución original de la variable objetivo
df2[target_var].value_counts().plot.bar()
plt.title("Distribución original de la variable objetivo")
plt.show()
# Balanceo con RandomOverSampler
bal = RandomOverSampler(random_state=42)
X_resampled, y_resampled = bal.fit_resample(X, y)
## C:\Users\SUITE\CONDA~1\envs\DATA_S~1\lib\site-packages\sklearn\utils\deprecation.py:151: FutureWarning: 'force_all_finite' was renamed to 'ensure_all_finite' in 1.6 and will be removed in 1.8.
## warnings.warn(
## C:\Users\SUITE\CONDA~1\envs\DATA_S~1\lib\site-packages\sklearn\base.py:484: FutureWarning: `BaseEstimator._check_n_features` is deprecated in 1.6 and will be removed in 1.7. Use `sklearn.utils.validation._check_n_features` instead.
## warnings.warn(
## C:\Users\SUITE\CONDA~1\envs\DATA_S~1\lib\site-packages\sklearn\base.py:493: FutureWarning: `BaseEstimator._check_feature_names` is deprecated in 1.6 and will be removed in 1.7. Use `sklearn.utils.validation._check_feature_names` instead.
## warnings.warn(
# Verificar nueva distribución balanceada
counts = defaultdict(int)
for item in y_resampled:
counts[item] += 1
print("Distribución después del balanceo:", dict(counts))
## Distribución después del balanceo: {np.int64(0): 1359, np.int64(1): 1359}
# Separar en entrenamiento y prueba después del balanceo
X_train, X_test, y_train, y_test = train_test_split(
X_resampled, y_resampled,
test_size=0.2,
random_state=1442
)
print(f"Tamaño de X_train: {X_train.shape}")
## Tamaño de X_train: (2174, 5)
print(f"Tamaño de X_test: {X_test.shape}")
## Tamaño de X_test: (544, 5)
print(f"Tamaño de y_train: {y_train.shape}")
## Tamaño de y_train: (2174,)
print(f"Tamaño de y_test: {y_test.shape}")
## Tamaño de y_test: (544,)
model = XGBClassifier(random_state=153468)
# Definición del rango de valores de los parámetros del modelo
params = { 'max_depth': [4,6,8],#Numero de nodos
'learning_rate': [0.01, 0.03, 0.05],# eta
'n_estimators': [100, 200],
'colsample_bytree': [0.3, 0.7]}
grid = GridSearchCV(estimator=model,param_grid=params,refit=True,verbose=3,cv=5,n_jobs=1)
grid.fit(X_train,y_train)
GridSearchCV(cv=5,
estimator=XGBClassifier(base_score=None, booster=None,
callbacks=None, colsample_bylevel=None,
colsample_bynode=None,
colsample_bytree=None, device=None,
early_stopping_rounds=None,
enable_categorical=False, eval_metric=None,
feature_types=None, gamma=None,
grow_policy=None, importance_type=None,
interaction_constraints=None,
learning_rate=None,...
max_cat_to_onehot=None,
max_delta_step=None, max_depth=None,
max_leaves=None, min_child_weight=None,
missing=nan, monotone_constraints=None,
multi_strategy=None, n_estimators=None,
n_jobs=None, num_parallel_tree=None,
random_state=153468, ...),
n_jobs=1,
param_grid={'colsample_bytree': [0.3, 0.7],
'learning_rate': [0.01, 0.03, 0.05],
'max_depth': [4, 6, 8], 'n_estimators': [100, 200]},
verbose=3)In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. GridSearchCV(cv=5,
estimator=XGBClassifier(base_score=None, booster=None,
callbacks=None, colsample_bylevel=None,
colsample_bynode=None,
colsample_bytree=None, device=None,
early_stopping_rounds=None,
enable_categorical=False, eval_metric=None,
feature_types=None, gamma=None,
grow_policy=None, importance_type=None,
interaction_constraints=None,
learning_rate=None,...
max_cat_to_onehot=None,
max_delta_step=None, max_depth=None,
max_leaves=None, min_child_weight=None,
missing=nan, monotone_constraints=None,
multi_strategy=None, n_estimators=None,
n_jobs=None, num_parallel_tree=None,
random_state=153468, ...),
n_jobs=1,
param_grid={'colsample_bytree': [0.3, 0.7],
'learning_rate': [0.01, 0.03, 0.05],
'max_depth': [4, 6, 8], 'n_estimators': [100, 200]},
verbose=3)XGBClassifier(base_score=None, booster=None, callbacks=None,
colsample_bylevel=None, colsample_bynode=None,
colsample_bytree=0.7, device=None, early_stopping_rounds=None,
enable_categorical=False, eval_metric=None, feature_types=None,
gamma=None, grow_policy=None, importance_type=None,
interaction_constraints=None, learning_rate=0.03, max_bin=None,
max_cat_threshold=None, max_cat_to_onehot=None,
max_delta_step=None, max_depth=8, max_leaves=None,
min_child_weight=None, missing=nan, monotone_constraints=None,
multi_strategy=None, n_estimators=200, n_jobs=None,
num_parallel_tree=None, random_state=153468, ...)XGBClassifier(base_score=None, booster=None, callbacks=None,
colsample_bylevel=None, colsample_bynode=None,
colsample_bytree=0.7, device=None, early_stopping_rounds=None,
enable_categorical=False, eval_metric=None, feature_types=None,
gamma=None, grow_policy=None, importance_type=None,
interaction_constraints=None, learning_rate=0.03, max_bin=None,
max_cat_threshold=None, max_cat_to_onehot=None,
max_delta_step=None, max_depth=8, max_leaves=None,
min_child_weight=None, missing=nan, monotone_constraints=None,
multi_strategy=None, n_estimators=200, n_jobs=None,
num_parallel_tree=None, random_state=153468, ...)grid.best_params_
## {'colsample_bytree': 0.7, 'learning_rate': 0.03, 'max_depth': 8, 'n_estimators': 200}
grid.best_score_
## np.float64(0.994020869749457)
joblib.dump(grid, 'model.pkl')
## ['model.pkl']
model = joblib.load('model.pkl')
model.predict(X_train)
## array([1, 1, 1, ..., 0, 0, 1])

y_pred_train = grid.predict(X_train)
print(confusion_matrix(y_true = y_train, y_pred = y_pred_train))
## [[1093 1]
## [ 0 1080]]
print(classification_report(y_true = y_train, y_pred = y_pred_train))#Output
## precision recall f1-score support
##
## 0 1.00 1.00 1.00 1094
## 1 1.00 1.00 1.00 1080
##
## accuracy 1.00 2174
## macro avg 1.00 1.00 1.00 2174
## weighted avg 1.00 1.00 1.00 2174
El modelo logró predecir de manera prácticamente perfecta los datos de entrenamiento, cometiendo casi ningún error. Las métricas de precisión, recall y F1-score para ambas clases alcanzaron el valor ideal de 1.00, reflejando un rendimiento óptimo en el conjunto de entrenamiento. La exactitud (accuracy) también fue del 100%, confirmando la capacidad del modelo para clasificar correctamente todos los casos durante el entrenamiento.
y_pred = grid.predict(X_test)
print(confusion_matrix(y_true = y_test, y_pred = y_pred))
## [[263 2]
## [ 0 279]]
print(classification_report(y_true = y_test, y_pred = y_pred))#Output
## precision recall f1-score support
##
## 0 1.00 0.99 1.00 265
## 1 0.99 1.00 1.00 279
##
## accuracy 1.00 544
## macro avg 1.00 1.00 1.00 544
## weighted avg 1.00 1.00 1.00 544
El modelo mantiene un rendimiento sobresaliente con datos nuevos, presentando únicamente 2 errores en la clase 0 y ninguno en la clase 1. Tanto la precisión como el recall superan el 99% en ambas clases, reflejando una alta capacidad predictiva. El F1-score de 1.00 evidencia un equilibrio excelente entre precisión y recall. La exactitud se acerca al 100%, lo que demuestra un desempeño muy sólido y confiable en datos no vistos durante el entrenamiento.
La pequeña diferencia observada, con solo 2 falsos positivos en el conjunto de prueba, es mínima y no afecta de manera significativa la calidad general del modelo.
probabilities = grid.predict_proba(X_test)
df_pred = pd.DataFrame(probabilities,columns=label_encoder.classes_)
df_pred["target"] = y_test
df_pred["y_pred"] = y_pred
df_pred.to_csv("pred_model.csv",index=False)