title: “Modelo Analítico: Enfermedades Cardíacas” author: “Catalina Aguilera” date: “2026-10-18” output: html_document: toc: true toc_float: true toc_depth: 3 number_sections: true code_folding: hide theme: flatly df_print: paged —

1. Contexto, problema y objetivos

1.1. Contexto

Las enfermedades cardíacas representan un reto para la gestión de los servicios de salud debido a la necesidad de identificar oportunamente a los pacientes con mayor riesgo de complicaciones y mortalidad. El análisis de datos clínicos permite reconocer patrones, explorar factores asociados con los desenlaces y apoyar la planeación de los recursos sanitarios.

Este proyecto utiliza una base de registros clínicos de pacientes con insuficiencia cardíaca para desarrollar un modelo analítico que estime la probabilidad de mortalidad a partir de variables demográficas y clínicas.

1.2. Problema de gestión

La ausencia de herramientas analíticas que permitan identificar patrones asociados con la mortalidad puede dificultar la priorización del seguimiento clínico y la planeación de recursos. Por ello, se propone construir un modelo de regresión logística que explore la relación entre las características de los pacientes y el evento de muerte.

1.3. Objetivo general

Construir y evaluar un modelo analítico de regresión logística para estimar la probabilidad de mortalidad en pacientes con insuficiencia cardíaca, utilizando variables clínicas y demográficas.

1.4. Objetivos específicos

  • Describir las características de los pacientes incluidos en la base de datos.
  • Identificar variables clínicas potencialmente asociadas con la mortalidad.
  • Entrenar un modelo de regresión logística y evaluar su desempeño.
  • Proponer recomendaciones para apoyar la gestión del riesgo y la planeación de recursos sanitarios.

1.5. Métricas de evaluación

El modelo se evaluará mediante el área bajo la curva ROC (AUC), la sensibilidad, la especificidad y la exactitud de clasificación. Como referencia inicial, se buscará un AUC igual o superior a 0,70; este valor es un criterio exploratorio y no garantiza por sí solo que el modelo sea clínicamente útil.

2. Selección de variables y preparación de los datos

2.1. Librerías y carga de la base de datos

library(readxl)
library(dplyr)
library(janitor)
library(skimr)
library(rsample)
library(caret)
library(pROC)
library(broom)
library(ggplot2)
library(knitr)
library(tidyr)

# Seleccionar el archivo real desde el explorador
archivo <- file.choose()

# Leer la base según su formato
extension <- tolower(tools::file_ext(archivo))

if (extension == "csv") {
  datos <- read.csv(archivo, header = TRUE)
} else if (extension %in% c("xlsx", "xls")) {
  datos <- read_excel(archivo)
} else {
  stop("Formato no reconocido. Selecciona un archivo CSV o Excel válido.")
}

# Estandarizar nombres de columnas
datos <- clean_names(datos)

# Revisar la estructura
cat("Número de filas:", nrow(datos), "\n")
## Número de filas: 299
cat("Número de columnas:", ncol(datos), "\n")
## Número de columnas: 13
glimpse(datos)
## Rows: 299
## Columns: 13
## $ age                      <dbl> 75, 55, 65, 50, 65, 90, 75, 60, 65, 80, 75, 6…
## $ anaemia                  <int> 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, …
## $ creatinine_phosphokinase <int> 582, 7861, 146, 111, 160, 47, 246, 315, 157, …
## $ diabetes                 <int> 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, …
## $ ejection_fraction        <int> 20, 38, 20, 20, 20, 40, 15, 60, 65, 35, 38, 2…
## $ high_blood_pressure      <int> 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, …
## $ platelets                <dbl> 265000, 263358, 162000, 210000, 327000, 20400…
## $ serum_creatinine         <dbl> 1.90, 1.10, 1.30, 1.90, 2.70, 2.10, 1.20, 1.1…
## $ serum_sodium             <int> 130, 136, 129, 137, 116, 132, 137, 131, 138, …
## $ sex                      <int> 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, …
## $ smoking                  <int> 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, …
## $ time                     <int> 4, 6, 7, 7, 8, 8, 10, 10, 10, 10, 10, 10, 11,…
## $ death_event              <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, …

2.2. Exploración descriptiva

Estructura y calidad inicial de la base de datos
Variable Tipo Valores_faltantes
age age numeric 0
anaemia anaemia integer 0
creatinine_phosphokinase creatinine_phosphokinase integer 0
diabetes diabetes integer 0
ejection_fraction ejection_fraction integer 0
high_blood_pressure high_blood_pressure integer 0
platelets platelets numeric 0
serum_creatinine serum_creatinine numeric 0
serum_sodium serum_sodium integer 0
sex sex integer 0
smoking smoking integer 0
time time integer 0
death_event death_event integer 0
Data summary
Name datos
Number of rows 299
Number of columns 13
_______________________
Column type frequency:
numeric 13
________________________
Group variables None

Variable type: numeric

skim_variable n_missing complete_rate mean sd p0 p25 p50 p75 p100 hist
age 0 1 60.83 11.89 40.0 51.0 60.0 70.0 95.0 ▆▇▇▂▁
anaemia 0 1 0.43 0.50 0.0 0.0 0.0 1.0 1.0 ▇▁▁▁▆
creatinine_phosphokinase 0 1 581.84 970.29 23.0 116.5 250.0 582.0 7861.0 ▇▁▁▁▁
diabetes 0 1 0.42 0.49 0.0 0.0 0.0 1.0 1.0 ▇▁▁▁▆
ejection_fraction 0 1 38.08 11.83 14.0 30.0 38.0 45.0 80.0 ▃▇▂▂▁
high_blood_pressure 0 1 0.35 0.48 0.0 0.0 0.0 1.0 1.0 ▇▁▁▁▅
platelets 0 1 263358.03 97804.24 25100.0 212500.0 262000.0 303500.0 850000.0 ▂▇▂▁▁
serum_creatinine 0 1 1.39 1.03 0.5 0.9 1.1 1.4 9.4 ▇▁▁▁▁
serum_sodium 0 1 136.63 4.41 113.0 134.0 137.0 140.0 148.0 ▁▁▃▇▁
sex 0 1 0.65 0.48 0.0 0.0 1.0 1.0 1.0 ▅▁▁▁▇
smoking 0 1 0.32 0.47 0.0 0.0 0.0 1.0 1.0 ▇▁▁▁▃
time 0 1 130.26 77.61 4.0 73.0 115.0 203.0 285.0 ▆▇▃▆▃
death_event 0 1 0.32 0.47 0.0 0.0 0.0 1.0 1.0 ▇▁▁▁▃

2.3. Identificación de variables

La variable objetivo es death_event, que representa si ocurrió el evento de muerte durante el seguimiento registrado. Las demás variables se consideran candidatas para explicar o predecir este desenlace.

if (!"death_event" %in% names(datos)) {
  stop("No se encuentra la columna death_event. Verifica los nombres de la base.")
}

candidatas <- c(
  "age",
  "anaemia",
  "creatinine_phosphokinase",
  "diabetes",
  "ejection_fraction",
  "high_blood_pressure",
  "platelets",
  "serum_creatinine",
  "serum_sodium",
  "sex"
)

variables <- intersect(candidatas, names(datos))

if (length(variables) == 0) {
  stop("No se encontraron las variables clínicas esperadas.")
}

cat("Variables disponibles:", paste(variables, collapse = ", "))
## Variables disponibles: age, anaemia, creatinine_phosphokinase, diabetes, ejection_fraction, high_blood_pressure, platelets, serum_creatinine, serum_sodium, sex

2.4. Preparación de la base analítica

base_modelo <- datos %>%
  select(all_of(c(variables, "death_event"))) %>%
  mutate(
    death_event = as.character(death_event),
    death_event = trimws(death_event),
    death_event = case_when(
      death_event %in% c("0", "No", "no", "FALSE", "False") ~ "No",
      death_event %in% c("1", "Si", "Sí", "si", "sí",
                         "TRUE", "True") ~ "Si",
      TRUE ~ NA_character_
    ),
    across(
      all_of(variables),
      ~ suppressWarnings(as.numeric(as.character(.x)))
    )
  ) %>%
  filter(!is.na(death_event)) %>%
  drop_na() %>%
  mutate(
    death_event = factor(death_event, levels = c("No", "Si"))
  )

if (nrow(base_modelo) < 30) {
  stop("Hay muy pocos registros válidos para continuar con este análisis.")
}

if (n_distinct(base_modelo$death_event) < 2) {
  stop("La variable death_event debe contener las dos clases: No y Si.")
}

cat("Registros incluidos:", nrow(base_modelo), "\n")
## Registros incluidos: 299
cat("Registros excluidos:", nrow(datos) - nrow(base_modelo), "\n")
## Registros excluidos: 0
knitr::kable(
  as.data.frame(table(base_modelo$death_event)),
  col.names = c("Evento de mortalidad", "Número de pacientes"),
  caption = "Distribución de la variable objetivo"
)
Distribución de la variable objetivo
Evento de mortalidad Número de pacientes
No 203
Si 96

3. Modelo analítico

3.1. Análisis exploratorio y visualización

Distribución de edades

Distribución de mortalidad

Edad según el evento de mortalidad

3.2. Entrenamiento y prueba del modelo

Se separará la base en 75 % para entrenamiento y 25 % para prueba. La base de prueba se reservará para evaluar el desempeño con registros que no participaron en el ajuste.

set.seed(123)

division <- initial_split(
  base_modelo,
  prop = 0.75,
  strata = death_event
)

entrenamiento <- training(division)
prueba <- testing(division)

cat("Registros de entrenamiento:", nrow(entrenamiento), "\n")
## Registros de entrenamiento: 224
cat("Registros de prueba:", nrow(prueba), "\n")
## Registros de prueba: 75

3.3. Construcción del modelo de regresión logística

control <- trainControl(
  method = "cv",
  number = 5,
  classProbs = TRUE,
  summaryFunction = twoClassSummary
)

modelo <- train(
  death_event ~ .,
  data = entrenamiento,
  method = "glm",
  family = binomial(),
  metric = "ROC",
  trControl = control
)

print(modelo)
## Generalized Linear Model 
## 
## 224 samples
##  10 predictor
##   2 classes: 'No', 'Si' 
## 
## No pre-processing
## Resampling: Cross-Validated (5 fold) 
## Summary of sample sizes: 178, 180, 178, 180, 180 
## Resampling results:
## 
##   ROC        Sens       Spec     
##   0.7715515  0.8948387  0.4542857

Interpretación de los coeficientes

Los odds ratios (OR) muestran cómo se relacionan las variables con las probabilidades relativas del evento de muerte, manteniendo constantes las demás variables del modelo. Un OR superior a 1 indica una asociación positiva con el desenlace; un OR inferior a 1 indica una asociación negativa. Estas asociaciones no demuestran causalidad.

Odds ratios e intervalos de confianza del modelo
Variable OR IC_inferior IC_superior Valor_p
age 1.060 1.030 1.093 1.27e-04
anaemia 1.065 0.531 2.129 8.58e-01
creatinine_phosphokinase 1.000 1.000 1.000 4.71e-01
diabetes 0.985 0.496 1.955 9.66e-01
ejection_fraction 0.923 0.889 0.955 8.80e-06
high_blood_pressure 2.003 0.993 4.078 5.30e-02
platelets 1.000 1.000 1.000 1.64e-01
serum_creatinine 1.878 1.330 2.888 9.32e-04
serum_sodium 0.958 0.877 1.045 3.29e-01
sex 0.741 0.354 1.549 4.24e-01

3.4. Evaluación del desempeño

Matriz de confusión

probabilidades <- predict(
  modelo,
  newdata = prueba,
  type = "prob"
)

predicciones <- predict(
  modelo,
  newdata = prueba
)

matriz <- confusionMatrix(
  data = predicciones,
  reference = prueba$death_event,
  positive = "Si"
)

print(matriz)
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction No Si
##         No 42 13
##         Si  9 11
##                                           
##                Accuracy : 0.7067          
##                  95% CI : (0.5902, 0.8062)
##     No Information Rate : 0.68            
##     P-Value [Acc > NIR] : 0.3602          
##                                           
##                   Kappa : 0.2949          
##                                           
##  Mcnemar's Test P-Value : 0.5224          
##                                           
##             Sensitivity : 0.4583          
##             Specificity : 0.8235          
##          Pos Pred Value : 0.5500          
##          Neg Pred Value : 0.7636          
##              Prevalence : 0.3200          
##          Detection Rate : 0.1467          
##    Detection Prevalence : 0.2667          
##       Balanced Accuracy : 0.6409          
##                                           
##        'Positive' Class : Si              
## 

Curva ROC y área bajo la curva (AUC)

Indicadores de desempeño del modelo en la base de prueba
Indicador Resultado
AUC 0.735
Exactitud 0.707
Sensibilidad 0.458
Especificidad 0.824

4. Conclusiones y recomendaciones para la gestión sanitaria

El modelo de regresión logística permite explorar la relación entre las características clínicas y demográficas de los pacientes y la ocurrencia de mortalidad. El desempeño debe analizarse con las métricas obtenidas en la base de prueba, especialmente el AUC, la sensibilidad y la especificidad.

Desde la gestión sanitaria, los resultados pueden orientar la identificación de grupos que podrían requerir seguimiento prioritario, la planeación de controles clínicos y la asignación de recursos para la atención integral. Las recomendaciones deben basarse en los resultados reales del análisis y en la valoración de los profesionales de salud.

El modelo tiene carácter exploratorio. Antes de utilizarlo para apoyar decisiones clínicas, se requiere validación externa, revisión de la calidad de los datos y evaluación de su utilidad en la población objetivo. La predicción estadística no sustituye el criterio clínico.

5. Limitaciones del análisis