📖 Inicio
- Introducción
- Árbol de Decisión
📊 Estudios Descriptivos
- Prevalencia (Transversal)
🔬 Estudios Analíticos
- Cohorte / Casos-Controles
⏱️ Análisis de Supervivencia
- Supervivencia (Kaplan-Meier / Cox)
🔬 Precisión Diagnóstica
- Estudios de Precisión Diagnóstica
📚 Recursos
- Guía STROBE
💡 TU IDEA CLÍNICA ES VALIOSA
Cada día en tu práctica observas patrones, preguntas sin respuesta, oportunidades de mejora.
Esas observaciones son INVESTIGACIÓN esperando suceder.
Esta app transforma tu idea en:
✅ Metodología rigurosa paso a paso
✅ Análisis estadístico correcto y completo
✅ Código R funcional listo para usar
✅ Manuscrito publicable en revistas internacionales
🎯 Bienvenido a EpiSalud
Esta es tu guía definitiva para diseñar, ejecutar y analizar estudios observacionales en salud.
✅ Médicos de cualquier especialidad
✅ Enfermeras investigadoras
✅ Nutricionistas clínicos
✅ Fisioterapeutas
✅ Terapeutas respiratorios
✅ Cualquier profesional de salud con una idea de investigación
No necesitas ser epidemiólogo. Solo necesitas curiosidad y una pregunta clínica.
En tu práctica diaria, la investigación que puedes hacer AHORA MISMO es observacional:
✅ No requiere intervención (solo observar y registrar)
✅ Éticamente viable
✅ Responde preguntas clínicas urgentes
✅ Genera evidencia valiosa
✅ Fundamento para estudios futuros
📊 Descriptivos
Prevalencia
Fotografía de un momento.
¿Cuántos tienen X?
🔬 Analíticos
Cohorte / Casos-Controles
Buscan causalidad.
¿X causa Y?
⏱️ Supervivencia
Tiempo al evento
Cuando importa CUÁNDO.
Mortalidad, complicaciones
✨ Lo que encontrarás en cada sección:
Protocolos paso a paso con ejemplos UCI Colombia
Fórmulas explicadas + código R funcional
Desde descriptivos hasta multivariados
Tablas Word + Figuras 300 DPI + Estilo The Lancet
1. Si no sabes qué diseño usar → Ve al Árbol de Decisión
2. Ya sabes tu diseño → Ve directo a esa sección
3. Necesitas código R → Cada sección tiene código completo descargable
4. Vas a escribir el manuscrito → Consulta Guía STROBE
🌳 Árbol de Decisión - ¿Qué diseño usar?
Responde estas preguntas para identificar el diseño observacional más apropiado para tu pregunta de investigación.
A. Solo quiero DESCRIBIR qué tan común es algo
• "¿Cuántos pacientes con sepsis tienen LRA?"
• "¿Cuál es la prevalencia de delirium en nuestra UCI?"
• "¿Qué características tienen los pacientes con SDRA?"
📊 Ir a Prevalencia →
B. Quiero saber si X CAUSA Y (relación causal)
• "¿La sedación profunda aumenta riesgo de delirium?"
• "¿Los IBP causan neumonía asociada a ventilador?"
• "¿El balance hídrico positivo empeora la mortalidad?"
✅ Si el desenlace es FRECUENTE (>10%)
Ejemplos: Delirium, LRA, VM prolongada, mortalidad
🔬 Ir a Cohorte →
✅ Si el desenlace es RARO (<5%)
Ejemplos: Neumonía por aspiración, TEP, muerte súbita
🔬 Ir a Casos-Controles →
C. Me interesa el TIEMPO hasta que ocurre el evento
• "¿Cuánto tiempo tardan en desarrollar NAV?"
• "Supervivencia a 28 días en sepsis"
• "Tiempo libre de ventilador"
• "Factores que predicen mortalidad temprana"
⏱️ Ir a Supervivencia →
💡 Consejos adicionales para elegir diseño:
⏰ Según el TIEMPO disponible:
- Rápido (1-3 meses): Prevalencia o Casos-Controles retrospectivo
- Medio (6-12 meses): Cohorte retrospectiva
- Largo (>1 año): Cohorte prospectiva
💰 Según RECURSOS disponibles:
- Presupuesto bajo: Prevalencia, Casos-Controles
- Presupuesto medio: Cohorte retrospectiva
- Presupuesto alto: Cohorte prospectiva multicéntrica
🎯 Según tu HIPÓTESIS:
- No tengo hipótesis clara: Empieza con Prevalencia (genera hipótesis)
- Tengo hipótesis específica: Usa Cohorte o Casos-Controles
- Hipótesis sobre supervivencia: Análisis de Supervivencia
Revisa cada sección del menú lateral. Todas tienen:
• Ejemplo específico UCI Colombia
• Indicaciones claras de cuándo usar ese diseño
• Ventajas y limitaciones
Estudios Observacionales Descriptivos
Objetivo: Describir la distribución de enfermedades o características en una población sin establecer relaciones causales. Estos estudios generan hipótesis que posteriormente se confirman con estudios analíticos.
Nivel de evidencia: Bajo (útil para prevalencia, tendencias, series de casos)
• No pueden establecer temporalidad (exposición y desenlace simultáneos)
• Generan hipótesis, NO las confirman
• Susceptibles a sesgo de causalidad reversa
• No calculan riesgo verdadero, solo razones de prevalencia
Por lo tanto, en esta clasificación rigurosa, los transversales están en Descriptivos.
📊 Estudio de Prevalencia (Transversal)
Ejemplo - UCI Colombia
Pregunta: ¿Cuál es la prevalencia de lesión renal aguda (LRA) en pacientes con sepsis admitidos a UCI en el Hospital Federico Lleras Acosta de Ibagué, y cuáles son los factores asociados?
Contexto: La LRA es común en sepsis pero su prevalencia en UCIs colombianas está poco documentada.
Protocolo Completo de Metodología
1. Diseño del estudio
Estudio observacional descriptivo de corte transversal.
2. Población y muestra
Población objetivo: Pacientes adultos con diagnóstico de sepsis o choque séptico (Sepsis-3) admitidos a UCI del Hospital Federico Lleras Acosta.
Criterios de inclusión:
- Edad ≥18 años
- Diagnóstico de sepsis o choque séptico según criterios Sepsis-3 (SOFA ≥2 puntos + sospecha o confirmación de infección)
- Admisión a UCI
- Disponibilidad de datos de creatinina sérica y/o gasto urinario
Criterios de exclusión:
- Enfermedad renal crónica en estadio 5 (TFG <15 ml/min) o en terapia de reemplazo renal crónica
- Trasplante renal previo
- Obstrucción urinaria conocida
- Readmisión a UCI en la misma hospitalización (solo se incluye primera admisión)
Tamaño de muestra:
Para estimar prevalencia con precisión adecuada:
- Prevalencia esperada: 50% (asumiendo máxima variabilidad)
- Precisión deseada: ±5%
- Nivel de confianza: 95%
- Fórmula: n = (Z²×p×q)/d² = (1.96²×0.5×0.5)/0.05² = 384 pacientes
Muestreo: Consecutivo (todos los pacientes elegibles durante el período de estudio)
Período de reclutamiento: 12 meses (enero-diciembre 2024)
3. Variables
Variable principal (desenlace):
- Lesión Renal Aguda (LRA): Definida según criterios KDIGO 2012:
- Aumento de creatinina sérica ≥0.3 mg/dl en 48 horas, O
- Aumento de creatinina ≥1.5 veces el valor basal en los últimos 7 días, O
- Gasto urinario <0.5 ml/kg/h por 6 horas
- Estadios de LRA (KDIGO):
- Estadio 1: Creatinina 1.5-1.9× basal o ↑≥0.3 mg/dl; GU <0.5 ml/kg/h por 6-12h
- Estadio 2: Creatinina 2.0-2.9× basal; GU <0.5 ml/kg/h por ≥12h
- Estadio 3: Creatinina 3.0× basal o ≥4.0 mg/dl o TRR; GU <0.3 ml/kg/h por ≥24h o anuria ≥12h
Variables demográficas:
- Edad (años - continua)
- Sexo (hombre/mujer)
- Procedencia (urbana/rural)
- Régimen de salud (contributivo/subsidiado)
Variables clínicas al ingreso:
- Índice de comorbilidad de Charlson (puntos)
- Comorbilidades específicas: diabetes, hipertensión arterial, enfermedad cardiovascular, EPOC, cirrosis, cáncer
- Tipo de sepsis: pulmonar, abdominal, urinaria, piel/tejidos blandos, bacteriemia primaria, otra
- Choque séptico al ingreso (sí/no)
- APACHE II al ingreso (puntos)
- SOFA al ingreso (puntos)
- Presión arterial media (mmHg)
- Lactato sérico (mmol/L)
Variables de laboratorio basal:
- Creatinina basal (mg/dl) - definida como creatinina mínima en 3 meses previos o estimada por MDRD inverso si no disponible
- TFG basal estimada (ml/min/1.73m² - CKD-EPI)
- Hemoglobina, leucocitos, plaquetas
- Bilirrubina, albúmina
- PCR, procalcitonina
Intervenciones/exposiciones potencialmente nefrotóxicas:
- Uso de vasopresores (norepinefrina, vasopresina - sí/no y dosis máxima)
- Antibióticos nefrotóxicos: vancomicina, aminoglucósidos, colistina (sí/no)
- AINEs en últimas 72h (sí/no)
- Contraste yodado en últimos 7 días (sí/no)
- Volumen de cristaloides en primeras 24h (ml)
- Balance hídrico a 24, 48 y 72h (ml)
Desenlaces:
- Necesidad de terapia de reemplazo renal (TRR)
- Mortalidad en UCI
- Mortalidad hospitalaria
- Días de estancia en UCI
- Días de ventilación mecánica
- Recuperación de función renal al alta (creatinina <1.5× basal)
4. Recolección de datos
Fuentes de información:
- Historia clínica electrónica (HCE)
- Base de datos de laboratorio clínico
- Registros de UCI (hojas de enfermería, balance hídrico)
- Sistema de información RIPS
Procedimiento:
- Identificación diaria de pacientes elegibles por médico investigador
- Recolección de datos en formulario electrónico estandarizado (REDCap)
- Doble digitación del 10% de datos para control de calidad
- Monitoreo semanal de completitud de datos
Definiciones operacionales:
- Creatinina basal: menor valor en 3 meses previos. Si no disponible, usar MDRD inverso asumiendo TFG 75 ml/min
- Gasto urinario: promedio por kg de peso por hora en el período especificado
- Peso: peso seco estimado (peso de ingreso menos balance hídrico acumulado)
5. Control de calidad
- Capacitación de investigadores en definiciones y recolección de datos
- Manual de operaciones detallado
- Revisión semanal de datos faltantes
- Auditoría de 10% de casos por investigador senior
6. Aspectos éticos
- Aprobación por comité de ética institucional del Hospital Federico Lleras Acosta
- Exención de consentimiento informado (estudio observacional sin intervención, análisis de datos clínicos rutinarios)
- Manejo confidencial de datos según Ley 1581 de 2012
- Base de datos anonimizada para análisis
- Registro del protocolo en plataforma pública (ej: ClinicalTrials.gov)
Plan de Análisis Estadístico Detallado
1. Análisis descriptivo
Variables continuas:
- Evaluación de normalidad con test de Shapiro-Wilk (si n<50) o Kolmogorov-Smirnov (si n≥50)
- Inspección visual con histogramas y Q-Q plots
- Si distribución normal: media ± desviación estándar (DE)
- Si distribución no normal: mediana [rango intercuartílico RIQ]
Variables categóricas:
- Frecuencias absolutas (n) y relativas (%)
- Tablas de contingencia para variables cruzadas
Tabla 1 - Características basales: Todas las variables demográficas, clínicas y de laboratorio al ingreso
2. Estimación de prevalencia
Prevalencia de LRA:
Intervalo de confianza 95%: Método de Wilson (más preciso que Wald, especialmente si prevalencia cercana a 0% o 100%)
Prevalencias estratificadas:
- Por estadio de LRA (KDIGO 1, 2, 3)
- Por tipo de sepsis (pulmonar, abdominal, etc.)
- Por grupos etarios (<65 años vs ≥65 años)
- Por presencia de choque séptico
- Por régimen de salud
Todas las prevalencias reportadas con IC95%
3. Análisis exploratorio de asociaciones (generación de hipótesis)
Análisis bivariado:
Comparación de pacientes CON LRA vs SIN LRA:
Para variables categóricas:
- Chi-cuadrado de Pearson si todas las frecuencias esperadas ≥5
- Test exacto de Fisher si alguna frecuencia esperada <5
- Calcular Razón de Prevalencia (RP) con IC95%
Interpretación: RP>1 indica mayor prevalencia en expuestos; RP<1 indica menor prevalencia
Para variables continuas:
- Si distribución normal en AMBOS grupos: t-test de Student para muestras independientes
- Si distribución NO normal en al menos un grupo: U de Mann-Whitney
- Reportar diferencia de medias o medianas con IC95%
📌 Interpretación de pruebas de hipótesis:
Valor p <0.05: Evidencia de asociación estadísticamente significativa (pero NO prueba causalidad)
Valor p ≥0.05: No hay evidencia suficiente de asociación
Importante: El valor p NO indica magnitud de la asociación. Siempre reportar tamaños de efecto (RP, diferencias de medias) con IC95%
4. Análisis multivariado exploratorio
Regresión logística múltiple para identificar factores independientemente asociados con LRA:
Selección de variables:
- Incluir variables con p<0.20 en análisis bivariado
- Incluir variables clínicamente importantes a priori (ej: edad, choque séptico, APACHE II)
- Evaluar colinealidad con VIF (Variance Inflation Factor): VIF>5 indica colinealidad problemática
Construcción del modelo:
- Método stepwise backward o forward basado en AIC
- Evaluar ajuste del modelo con test de Hosmer-Lemeshow (p>0.05 = buen ajuste)
- Evaluar capacidad discriminativa con área bajo curva ROC (AUC): >0.7 aceptable, >0.8 buena, >0.9 excelente
Resultados a reportar:
- Odds Ratio (OR) ajustado con IC95% para cada variable
- Valor p de cada variable
- AUC del modelo final
5. Análisis de subgrupos
- Prevalencia de LRA en diferentes tipos de sepsis
- Prevalencia por estadios de gravedad (APACHE II: <15, 15-25, >25)
- Comparación de prevalencia entre pacientes con/sin diabetes
- Análisis estratificado por grupos etarios
6. Manejo de datos faltantes
- Análisis de patrones de datos faltantes (MCAR vs MAR vs MNAR)
- Reportar porcentaje de datos faltantes para cada variable
- Si datos faltantes <5%: análisis de casos completos
- Si datos faltantes 5-20%: considerar imputación múltiple (paquete mice en R)
- Si datos faltantes >20%: reportar como limitación importante
7. Software y nivel de significancia
- Software: R versión 4.3+ (paquetes: tidyverse, tableone, epiR, DescTools, survival, pROC)
- Nivel de significancia: α = 0.05 (bilateral)
- Intervalos de confianza: 95% para todas las estimaciones
- Reproducibilidad: Código R completo disponible en repositorio público
Código R Completo - Nivel Publicación
Incluye: importación interactiva de datos, limpieza automática, análisis completo, gráficos publicables estilo The Lancet, y tablas formato APA.
Compatible con: CSV, Excel (.xlsx, .xls), SPSS, Stata
# ============================================================================ # ANÁLISIS DE PREVALENCIA DE LESIÓN RENAL AGUDA EN SEPSIS - UCI # Hospital Federico Lleras Acosta, Ibagué, Colombia # # Autor: [Tu nombre] # Fecha: [Fecha] # Versión: 1.0 # # Este código realiza: # 1. Importación interactiva de datos (CSV, Excel, SPSS, Stata) # 2. Limpieza y validación de datos # 3. Análisis descriptivo completo # 4. Estimación de prevalencias con IC95% # 5. Análisis bivariado exploratorio # 6. Gráficos listos para publicación (estilo The Lancet) # 7. Tablas formato APA/Vancouver # ============================================================================ # ============================================================================ # SECCIÓN 1: INSTALACIÓN Y CARGA DE PAQUETES # ============================================================================ # Lista de paquetes necesarios paquetes_necesarios <- c( "tidyverse", # Manipulación de datos (dplyr, ggplot2, tidyr, etc.) "readxl", # Leer archivos Excel (.xlsx, .xls) "haven", # Leer archivos SPSS (.sav) y Stata (.dta) "janitor", # Limpieza de nombres de variables "tableone", # Tabla 1 de características (clásica en artículos médicos) "gtsummary", # Tablas elegantes listas para publicación "flextable", # Tablas exportables a Word/PowerPoint "epiR", # Funciones epidemiológicas (prevalencias, IC95%) "DescTools", # Herramientas descriptivas adicionales "ggplot2", # Gráficos de alta calidad "ggpubr", # Publicación: p-values, comparaciones en gráficos "patchwork", # Combinar múltiples gráficos "scales", # Formateo de ejes (porcentajes, números) "RColorBrewer", # Paletas de colores profesionales "viridis", # Paletas accesibles para daltónicos "naniar", # Análisis de datos faltantes "skimr", # Resumen rápido y completo de datos "here" # Manejo de rutas de archivos ) # Función para instalar paquetes faltantes instalar_si_falta <- function(paquete) { if (!require(paquete, character.only = TRUE)) { install.packages(paquete, dependencies = TRUE) library(paquete, character.only = TRUE) } } # Instalar y cargar todos los paquetes cat("Instalando/cargando paquetes necesarios...\n") invisible(lapply(paquetes_necesarios, instalar_si_falta)) cat("✓ Todos los paquetes cargados exitosamente.\n\n") # ============================================================================ # SECCIÓN 2: IMPORTACIÓN INTERACTIVA DE DATOS # ============================================================================ # Función para importar datos de múltiples formatos importar_datos <- function() { cat("\n========================================\n") cat("IMPORTACIÓN DE DATOS\n") cat("========================================\n\n") # Abrir cuadro de diálogo para seleccionar archivo cat("Por favor, seleccione su archivo de datos...\n") ruta_archivo <- file.choose() # Detectar extensión del archivo extension <- tolower(tools::file_ext(ruta_archivo)) # Importar según el tipo de archivo datos <- switch(extension, "csv" = read.csv(ruta_archivo, stringsAsFactors = FALSE), "xlsx" = read_excel(ruta_archivo), "xls" = read_excel(ruta_archivo), "sav" = read_sav(ruta_archivo), "dta" = read_dta(ruta_archivo), stop("Formato no soportado. Use: .csv, .xlsx, .xls, .sav, o .dta") ) cat(sprintf("✓ Archivo importado: %s\n", basename(ruta_archivo))) cat(sprintf("✓ Dimensiones: %d filas × %d columnas\n\n", nrow(datos), ncol(datos))) return(datos) } # Ejecutar importación datos_raw <- importar_datos() # ============================================================================ # SECCIÓN 3: LIMPIEZA Y VALIDACIÓN DE DATOS # ============================================================================ cat("\n========================================\n") cat("LIMPIEZA DE DATOS\n") cat("========================================\n\n") # 3.1 Limpiar nombres de variables (sin espacios, minúsculas, sin caracteres especiales) cat("Paso 1: Estandarizando nombres de variables...\n") datos <- datos_raw %>% clean_names() # Convierte a snake_case: edad_anos, apache_ii, etc. cat("✓ Nombres estandarizados a formato snake_case\n") cat("Nombres de variables:\n") print(names(datos)) # 3.2 Inspección inicial de datos cat("\n\nPaso 2: Inspección inicial de datos...\n") cat("Resumen de la estructura de datos:\n") glimpse(datos) # 3.3 Análisis de datos faltantes cat("\n\nPaso 3: Análisis de datos faltantes...\n") datos_faltantes <- datos %>% summarise(across(everything(), ~sum(is.na(.)) / length(.) * 100)) %>% pivot_longer(everything(), names_to = "variable", values_to = "porcentaje_faltante") %>% filter(porcentaje_faltante > 0) %>% arrange(desc(porcentaje_faltante)) if (nrow(datos_faltantes) > 0) { cat("\n⚠ Variables con datos faltantes:\n") print(datos_faltantes, n = Inf) } else { cat("✓ No hay datos faltantes\n") } # Gráfico de patrón de datos faltantes (si hay alguno) if (any(is.na(datos))) { cat("\nGenerando gráfico de datos faltantes...\n") gg_miss_var(datos) + labs(title = "Missing Data Pattern", subtitle = sprintf("Dataset: %d observations", nrow(datos)), x = "Variable", y = "Number of Missing Values") + theme_minimal(base_size = 12) + theme(axis.text.x = element_text(angle = 45, hjust = 1)) } # 3.4 Conversión de tipos de variables y etiquetado cat("\n\nPaso 4: Conversión de variables categóricas a factores...\n") # IMPORTANTE: Adaptar estos nombres a los de TU base de datos # Esta es una plantilla - debes modificar según tus variables datos <- datos %>% mutate( # Variables demográficas sexo = factor(sexo, levels = c(1, 2), labels = c("Male", "Female")), # Variable de desenlace principal lra = factor(lra, levels = c(0, 1), labels = c("No AKI", "AKI")), estadio_lra = factor(estadio_lra, levels = c(0, 1, 2, 3), labels = c("No AKI", "Stage 1", "Stage 2", "Stage 3"), ordered = TRUE), # Variables clínicas choque_septico = factor(choque_septico, levels = c(0, 1), labels = c("No Septic Shock", "Septic Shock")), tipo_sepsis = factor(tipo_sepsis, levels = c(1, 2, 3, 4, 5), labels = c("Pulmonary", "Abdominal", "Urinary", "Skin/Soft Tissue", "Primary Bacteremia")), # Comorbilidades diabetes = factor(diabetes, levels = c(0, 1), labels = c("No Diabetes", "Diabetes")), hipertension = factor(hipertension, levels = c(0, 1), labels = c("No Hypertension", "Hypertension")), # Desenlaces mortalidad_uci = factor(mortalidad_uci, levels = c(0, 1), labels = c("Survivor", "Non-survivor")), # Asegurar que variables continuas sean numéricas edad = as.numeric(edad), apache_ii = as.numeric(apache_ii), sofa = as.numeric(sofa), creatinina_basal = as.numeric(creatinina_basal), creatinina_max = as.numeric(creatinina_max), lactato = as.numeric(lactato) ) cat("✓ Variables categóricas convertidas a factores con etiquetas\n") cat("✓ Variables continuas verificadas como numéricas\n") # 3.5 Detección de valores atípicos (outliers) en variables continuas cat("\n\nPaso 5: Detección de valores atípicos...\n") detectar_outliers <- function(datos, variable) { q1 <- quantile(datos[[variable]], 0.25, na.rm = TRUE) q3 <- quantile(datos[[variable]], 0.75, na.rm = TRUE) iqr <- q3 - q1 limite_inferior <- q1 - 3 * iqr limite_superior <- q3 + 3 * iqr outliers <- datos[[variable]] < limite_inferior | datos[[variable]] > limite_superior n_outliers <- sum(outliers, na.rm = TRUE) if (n_outliers > 0) { cat(sprintf(" %s: %d outliers extremos (%.1f%%)\n", variable, n_outliers, n_outliers/length(datos[[variable]])*100)) } } variables_continuas <- c("edad", "apache_ii", "sofa", "creatinina_basal", "creatinina_max", "lactato") invisible(lapply(variables_continuas, function(x) detectar_outliers(datos, x))) cat("\n✓ Limpieza de datos completada\n") # ============================================================================ # SECCIÓN 4: ANÁLISIS DESCRIPTIVO # ============================================================================ cat("\n\n========================================\n") cat("ANÁLISIS DESCRIPTIVO\n") cat("========================================\n\n") # 4.1 Resumen general de datos cat("Resumen estadístico de todas las variables:\n\n") skim(datos) # 4.2 Evaluación de normalidad para variables continuas cat("\n\nEvaluación de normalidad (Test de Shapiro-Wilk):\n") cat("(Valores p < 0.05 indican distribución NO normal)\n\n") test_normalidad <- sapply(variables_continuas, function(var) { if (sum(!is.na(datos[[var]])) < 5000) { test <- shapiro.test(datos[[var]][!is.na(datos[[var]])]) c(Statistic = test$statistic, P_value = test$p.value) } else { c(Statistic = NA, P_value = NA) # Shapiro no funciona bien con n>5000 } }) print(t(test_normalidad)) # 4.3 Crear Tabla 1 de características basales (estilo publicación) cat("\n\nGenerando Tabla 1 de características...\n") variables_tabla1 <- c("edad", "sexo", "apache_ii", "sofa", "choque_septico", "tipo_sepsis", "diabetes", "hipertension", "creatinina_basal", "lactato") tabla1 <- datos %>% select(all_of(variables_tabla1)) %>% tbl_summary( statistic = list( all_continuous() ~ "{median} ({p25}, {p75})", # Mediana (RIQ) para continuas all_categorical() ~ "{n} ({p}%)" # n (%) para categóricas ), digits = list( all_continuous() ~ 1, all_categorical() ~ c(0, 1) ), label = list( edad ~ "Age, years", sexo ~ "Sex", apache_ii ~ "APACHE II score", sofa ~ "SOFA score", choque_septico ~ "Septic shock", tipo_sepsis ~ "Source of sepsis", diabetes ~ "Diabetes mellitus", hipertension ~ "Hypertension", creatinina_basal ~ "Baseline creatinine, mg/dL", lactato ~ "Lactate, mmol/L" ), missing_text = "Missing data" ) %>% modify_header(label = "**Variable**") %>% modify_caption("Table 1. Baseline Characteristics") %>% bold_labels() print(tabla1) # Exportar Tabla 1 a Word (opcional) # tabla1 %>% as_flex_table() %>% save_as_docx(path = "Tabla1.docx") # ============================================================================ # SECCIÓN 5: ESTIMACIÓN DE PREVALENCIA # ============================================================================ cat("\n\n========================================\n") cat("ESTIMACIÓN DE PREVALENCIA DE LRA\n") cat("========================================\n\n") # 5.1 Prevalencia global con IC95% (método de Wilson) n_total <- sum(!is.na(datos$lra)) n_lra <- sum(datos$lra == "AKI", na.rm = TRUE) prev_global <- epi.conf( dat = matrix(c(n_lra, n_total - n_lra), nrow = 1), ctype = "prevalence", method = "wilson", conf.level = 0.95 ) cat("PREVALENCIA GLOBAL DE LESIÓN RENAL AGUDA:\n") cat(sprintf(" Casos: %d / %d\n", n_lra, n_total)) cat(sprintf(" Prevalencia: %.1f%% (95%% CI: %.1f%% - %.1f%%)\n\n", prev_global$est * 100, prev_global$lower * 100, prev_global$upper * 100)) cat("📝 CÓMO REDACTAR EN EL ARTÍCULO:\n") cat(sprintf("'The prevalence of AKI in patients with sepsis was %.1f%% (95%% CI %.1f%%-%.1f%%, n=%d/%d).'\n\n", prev_global$est * 100, prev_global$lower * 100, prev_global$upper * 100, n_lra, n_total)) # 5.2 Prevalencia por estadio KDIGO cat("\nPREVALENCIA POR ESTADIO KDIGO:\n") prev_kdigo <- datos %>% count(estadio_lra) %>% mutate( prevalencia = n / sum(n) * 100, # Calcular IC95% para cada estadio ic_lower = mapply(function(casos, total) { epi.conf(matrix(c(casos, total - casos), nrow = 1), ctype = "prevalence", method = "wilson")$lower * 100 }, n, sum(n)), ic_upper = mapply(function(casos, total) { epi.conf(matrix(c(casos, total - casos), nrow = 1), ctype = "prevalence", method = "wilson")$upper * 100 }, n, sum(n)) ) print(prev_kdigo) # ============================================================================ # SECCIÓN 6: GRÁFICOS LISTOS PARA PUBLICACIÓN # ============================================================================ cat("\n\n========================================\n") cat("GENERANDO GRÁFICOS (ESTILO THE LANCET)\n") cat("========================================\n\n") # Tema personalizado para publicación tema_publicacion <- theme_minimal(base_size = 14, base_family = "sans") + theme( plot.title = element_text(size = 16, face = "bold", hjust = 0), plot.subtitle = element_text(size = 12, color = "grey30", hjust = 0), axis.title = element_text(size = 12, face = "bold"), axis.text = element_text(size = 11, color = "black"), legend.title = element_text(size = 12, face = "bold"), legend.text = element_text(size = 11), legend.position = "bottom", panel.grid.minor = element_blank(), panel.grid.major = element_line(color = "grey90", size = 0.3), plot.caption = element_text(size = 9, color = "grey50", hjust = 0) ) # Paleta de colores profesional (The Lancet style) colores_lancet <- c("#00468B", "#ED0000", "#42B540", "#0099B4", "#925E9F", "#FDAF91", "#AD002A") # 6.1 Gráfico de barras: Prevalencia por estadio KDIGO cat("Generando Figure 1: Prevalencia por estadio KDIGO...\n") fig1_prevalencia_kdigo <- ggplot(prev_kdigo, aes(x = estadio_lra, y = prevalencia, fill = estadio_lra)) + geom_col(alpha = 0.9, width = 0.7) + geom_errorbar(aes(ymin = ic_lower, ymax = ic_upper), width = 0.2, size = 0.8) + geom_text(aes(label = sprintf("%.1f%%\n(n=%d)", prevalencia, n)), vjust = -0.5, size = 4, fontface = "bold") + scale_fill_manual(values = colores_lancet) + scale_y_continuous(limits = c(0, max(prev_kdigo$ic_upper) * 1.2), expand = expansion(mult = c(0, 0.05))) + labs( title = "Figure 1. Prevalence of Acute Kidney Injury by KDIGO Stage", subtitle = sprintf("Patients with sepsis admitted to ICU (N=%d)", n_total), x = "KDIGO Stage", y = "Prevalence (%)", caption = "Error bars represent 95% confidence intervals (Wilson method).\nAKI = Acute Kidney Injury; KDIGO = Kidney Disease: Improving Global Outcomes" ) + tema_publicacion + theme(legend.position = "none") print(fig1_prevalencia_kdigo) # Guardar en alta resolución para publicación ggsave("Figure1_AKI_Prevalence_by_KDIGO.png", fig1_prevalencia_kdigo, width = 10, height = 7, dpi = 300, bg = "white") ggsave("Figure1_AKI_Prevalence_by_KDIGO.pdf", fig1_prevalencia_kdigo, width = 10, height = 7, device = "pdf") cat("✓ Figura 1 guardada: Figure1_AKI_Prevalence_by_KDIGO.png (300 DPI)\n") cat("✓ Figura 1 guardada: Figure1_AKI_Prevalence_by_KDIGO.pdf (vectorial)\n\n")
• Importación interactiva de CSV, Excel, SPSS, Stata
• Limpieza automática de datos con reportes
• Detección de outliers y datos faltantes
• Tabla 1 lista para publicación
• Gráficos en alta resolución (PNG 300 DPI + PDF vectorial)
• Nombres y títulos en inglés (estándar internacional)
• Formato The Lancet/BMJ/NEJM
# ============================================================================ # SECCIÓN 7: GRÁFICOS ADICIONALES PARA PUBLICACIÓN # ============================================================================ # 7.1 Distribución de edad por presencia de LRA (boxplot + violin) cat("Generando Figure 2: Edad por estado de LRA...\n") fig2_edad_lra <- ggplot(datos, aes(x = lra, y = edad, fill = lra)) + geom_violin(alpha = 0.5, trim = FALSE) + geom_boxplot(width = 0.2, alpha = 0.8, outlier.shape = 21, outlier.size = 2, outlier.alpha = 0.5) + stat_summary(fun = mean, geom = "point", shape = 23, size = 3, fill = "white", color = "black") + scale_fill_manual(values = c("No AKI" = colores_lancet[3], "AKI" = colores_lancet[2])) + stat_compare_means(method = "wilcox.test", label = "p.format", label.x = 1.5, label.y = max(datos$edad, na.rm = TRUE) * 0.95) + labs( title = "Figure 2. Age Distribution by AKI Status", subtitle = sprintf("N=%d patients with sepsis", n_total), x = "", y = "Age (years)", caption = "Box represents IQR with median line. Diamond represents mean.\nWilcoxon rank-sum test for group comparison." ) + tema_publicacion + theme(legend.position = "none") print(fig2_edad_lra) ggsave("Figure2_Age_by_AKI.png", fig2_edad_lra, width = 8, height = 7, dpi = 300, bg = "white") # 7.2 APACHE II y SOFA por presencia de LRA (comparación múltiple) cat("Generando Figure 3: Scores de severidad por LRA...\n") # Preparar datos en formato largo datos_severidad <- datos %>% select(lra, apache_ii, sofa) %>% pivot_longer(cols = c(apache_ii, sofa), names_to = "score", values_to = "value") %>% mutate(score = factor(score, levels = c("apache_ii", "sofa"), labels = c("APACHE II", "SOFA"))) fig3_scores <- ggplot(datos_severidad, aes(x = lra, y = value, fill = lra)) + geom_boxplot(alpha = 0.7, outlier.alpha = 0.3) + stat_summary(fun = mean, geom = "point", shape = 23, size = 2.5, fill = "white") + facet_wrap(~score, scales = "free_y", ncol = 2) + scale_fill_manual(values = c("No AKI" = colores_lancet[3], "AKI" = colores_lancet[2])) + stat_compare_means(method = "wilcox.test", label = "p.format", label.y.npc = 0.95) + labs( title = "Figure 3. Illness Severity Scores by AKI Status", subtitle = "Comparison of APACHE II and SOFA scores", x = "", y = "Score", caption = "Diamond represents mean. P-values from Wilcoxon rank-sum test." ) + tema_publicacion + theme(legend.position = "none") print(fig3_scores) ggsave("Figure3_Severity_Scores.png", fig3_scores, width = 10, height = 6, dpi = 300, bg = "white") # ============================================================================ # SECCIÓN 8: ANÁLISIS BIVARIADO COMPLETO # ============================================================================ cat("\n\n========================================\n") cat("ANÁLISIS BIVARIADO AVANZADO\n") cat("========================================\n\n") # 8.1 Tabla comparativa estratificada (Tabla 2 del artículo) cat("Generando Tabla 2: Características según presencia de LRA...\n") tabla2_bivariado <- datos %>% select(all_of(variables_tabla1), lra) %>% tbl_summary( by = lra, statistic = list( all_continuous() ~ "{median} ({p25}, {p75})", all_categorical() ~ "{n} ({p}%)" ), digits = list( all_continuous() ~ 1, all_categorical() ~ c(0, 1) ), label = list( edad ~ "Age, years", sexo ~ "Sex", apache_ii ~ "APACHE II score", sofa ~ "SOFA score", choque_septico ~ "Septic shock", tipo_sepsis ~ "Source of sepsis", diabetes ~ "Diabetes mellitus", hipertension ~ "Hypertension", creatinina_basal ~ "Baseline creatinine, mg/dL", lactato ~ "Lactate, mmol/L" ), missing_text = "Missing" ) %>% add_p( test = list( all_continuous() ~ "wilcox.test", # Mann-Whitney U para continuas all_categorical() ~ "chisq.test" # Chi-cuadrado para categóricas ), pvalue_fun = function(x) style_pvalue(x, digits = 3) ) %>% add_overall() %>% modify_header( label = "**Variable**", stat_0 = "**Overall**\nN = {N}", stat_1 = "**No AKI**\nN = {n}", stat_2 = "**AKI**\nN = {n}" ) %>% modify_caption("Table 2. Baseline Characteristics Stratified by AKI Status") %>% bold_labels() %>% italicize_levels() print(tabla2_bivariado) # Exportar a Word tabla2_bivariado %>% as_flex_table() %>% save_as_docx(path = "Table2_Bivariate_Analysis.docx") cat("✓ Tabla 2 exportada: Table2_Bivariate_Analysis.docx\n\n") # 8.2 Calcular Razones de Prevalencia (RP) para factores de riesgo cat("Calculando Razones de Prevalencia con IC95%...\n\n") # Función mejorada para calcular RP calcular_rp_completo <- function(datos, variable_exposicion, variable_desenlace = "lra") { # Crear tabla 2x2 tabla <- table(datos[[variable_exposicion]], datos[[variable_desenlace]]) # Verificar que sea 2x2 if (nrow(tabla) != 2 | ncol(tabla) != 2) { return(list(error = "Variable no es dicotómica")) } # Calcular con epiR resultado <- epi.2by2(tabla, method = "cross.sectional", conf.level = 0.95) # Extraer RP e IC95% rp <- resultado$massoc.detail$PR.strata.wald return(list( variable = variable_exposicion, rp = rp["est"], ic_lower = rp["lower"], ic_upper = rp["upper"], p_valor = resultado$massoc.detail$chi2.strata.uncor["p.value"] )) } # Calcular RP para variables categóricas dicotómicas variables_categoricas_dicho <- c("sexo", "choque_septico", "diabetes", "hipertension") resultados_rp <- lapply(variables_categoricas_dicho, function(var) { calcular_rp_completo(datos, var) }) # Crear tabla de resultados RP tabla_rp <- do.call(rbind, lapply(resultados_rp, function(x) { if (is.null(x$error)) { data.frame( Variable = x$variable, PR = sprintf("%.2f", x$rp), CI95 = sprintf("%.2f - %.2f", x$ic_lower, x$ic_upper), P_value = sprintf("%.3f", x$p_valor) ) } })) cat("\nRAZONES DE PREVALENCIA (RP) CRUDAS:\n") print(tabla_rp) cat("\n📝 INTERPRETACIÓN:\n") cat("- RP > 1: Mayor prevalencia de LRA en expuestos\n") cat("- RP < 1: Menor prevalencia de LRA en expuestos\n") cat("- IC95% que no incluye 1.0: Asociación estadísticamente significativa\n\n") cat("📝 EJEMPLO DE REDACCIÓN:\n") cat("'Patients with septic shock had a XX% higher prevalence of AKI\n") cat("compared to those without septic shock (PR X.XX, 95% CI X.XX-X.XX, P=X.XXX).'\n\n") # ============================================================================ # SECCIÓN 9: ANÁLISIS MULTIVARIADO - REGRESIÓN DE POISSON # ============================================================================ cat("\n\n========================================\n") cat("ANÁLISIS MULTIVARIADO\n") cat("========================================\n\n") cat("IMPORTANTE: Usaremos REGRESIÓN DE POISSON con varianza robusta\n") cat("para estimar RP ajustados (NO regresión logística que daría OR).\n\n") # 9.1 Modelo de Regresión de Poisson con varianza robusta # Esto es MEJOR que regresión logística para desenlaces frecuentes (>10%) # porque OR sobreestima el verdadero riesgo relativo library(sandwich) # Para errores estándar robustos library(lmtest) # Para coeftest # Convertir LRA a numérico para Poisson datos <- datos %>% mutate(lra_num = as.numeric(lra == "AKI")) # Modelo completo (ajustar variables según tu estudio) modelo_poisson <- glm( lra_num ~ edad + sexo + apache_ii + choque_septico + diabetes + hipertension + creatinina_basal, data = datos, family = poisson(link = "log") ) # Obtener RR ajustados con errores robustos coef_robustos <- coeftest(modelo_poisson, vcov = vcovHC(modelo_poisson, type = "HC0")) # Crear tabla de resultados resultados_multivariado <- data.frame( Variable = rownames(coef_robustos), Coef = coef_robustos[, "Estimate"], SE = coef_robustos[, "Std. Error"], Z = coef_robustos[, "z value"], P = coef_robustos[, "Pr(>|z|)"] ) %>% mutate( PR = exp(Coef), IC_lower = exp(Coef - 1.96 * SE), IC_upper = exp(Coef + 1.96 * SE) ) %>% filter(Variable != "(Intercept)") %>% mutate( PR_formatted = sprintf("%.2f (%.2f-%.2f)", PR, IC_lower, IC_upper), P_formatted = ifelse(P < 0.001, "<0.001", sprintf("%.3f", P)) ) cat("\nRESULTADOS DEL MODELO MULTIVARIADO:\n") cat("Razones de Prevalencia (PR) Ajustadas con IC95%:\n\n") print(resultados_multivariado[, c("Variable", "PR_formatted", "P_formatted")]) # Crear tabla publicable con gtsummary modelo_tabla <- tbl_regression( modelo_poisson, exponentiate = TRUE, # Convertir a PR (exp de coeficientes) label = list( edad ~ "Age (per year)", sexo ~ "Sex (Female vs Male)", apache_ii ~ "APACHE II (per point)", choque_septico ~ "Septic Shock", diabetes ~ "Diabetes Mellitus", hipertension ~ "Hypertension", creatinina_basal ~ "Baseline Creatinine (per mg/dL)" ) ) %>% modify_header( estimate = "**PR**", ci = "**95% CI**", p.value = "**P-value**" ) %>% modify_caption("Table 3. Multivariable Analysis - Adjusted Prevalence Ratios for AKI") %>% bold_labels() %>% bold_p(t = 0.05) print(modelo_tabla) # Exportar modelo_tabla %>% as_flex_table() %>% save_as_docx(path = "Table3_Multivariable_Model.docx") cat("\n✓ Tabla 3 exportada: Table3_Multivariable_Model.docx\n\n") cat("📝 CÓMO REDACTAR:\n") cat("'In multivariable analysis adjusting for age, sex, illness severity,\n") cat("and comorbidities, septic shock remained independently associated with\n") cat("higher prevalence of AKI (adjusted PR X.XX, 95% CI X.XX-X.XX, P=X.XXX).'\n\n") # 9.2 Evaluación del modelo cat("Evaluación del ajuste del modelo:\n") # Pseudo R-squared (McFadden) modelo_nulo <- glm(lra_num ~ 1, data = datos, family = poisson()) pseudo_r2 <- 1 - (logLik(modelo_poisson) / logLik(modelo_nulo)) cat(sprintf("Pseudo R² (McFadden): %.3f\n", pseudo_r2)) # AIC cat(sprintf("AIC: %.1f\n\n", AIC(modelo_poisson))) # ============================================================================ # SECCIÓN 10: FOREST PLOT DE RAZONES DE PREVALENCIA # ============================================================================ cat("\n\n========================================\n") cat("GENERANDO FOREST PLOT\n") cat("========================================\n\n") # Preparar datos para forest plot forest_data <- resultados_multivariado %>% mutate( Variable_label = case_when( Variable == "edad" ~ "Age (per year)", Variable == "sexoFemale" ~ "Female sex", Variable == "apache_ii" ~ "APACHE II (per point)", Variable == "choque_septicoSeptic Shock" ~ "Septic shock", Variable == "diabetesDiabetes" ~ "Diabetes mellitus", Variable == "hipertensionHypertension" ~ "Hypertension", Variable == "creatinina_basal" ~ "Baseline creatinine (per mg/dL)", TRUE ~ Variable ) ) %>% arrange(desc(PR)) fig4_forest <- ggplot(forest_data, aes(x = PR, y = reorder(Variable_label, PR))) + geom_vline(xintercept = 1, linetype = "dashed", color = "gray50", size = 0.8) + geom_errorbarh(aes(xmin = IC_lower, xmax = IC_upper), height = 0.3, size = 0.8, color = colores_lancet[1]) + geom_point(size = 4, color = colores_lancet[1], shape = 18) + geom_text(aes(label = sprintf("%.2f (%.2f-%.2f)", PR, IC_lower, IC_upper)), hjust = -0.1, size = 3.5, fontface = "bold") + scale_x_continuous( trans = "log10", breaks = c(0.5, 0.75, 1, 1.5, 2, 3, 4), limits = c(0.4, max(forest_data$IC_upper) * 1.5) ) + labs( title = "Figure 4. Adjusted Prevalence Ratios for Acute Kidney Injury", subtitle = "Multivariable Poisson regression with robust variance", x = "Adjusted Prevalence Ratio (95% CI)", y = "", caption = "Vertical dashed line represents PR = 1.0 (no association).\nError bars represent 95% confidence intervals." ) + tema_publicacion + theme( axis.text.y = element_text(size = 11, hjust = 1), panel.grid.major.y = element_blank(), panel.grid.major.x = element_line(color = "grey90") ) print(fig4_forest) ggsave("Figure4_Forest_Plot.png", fig4_forest, width = 12, height = 7, dpi = 300, bg = "white") ggsave("Figure4_Forest_Plot.pdf", fig4_forest, width = 12, height = 7, device = "pdf") cat("✓ Figure 4 guardada en PNG y PDF\n\n") # ============================================================================ # SECCIÓN 11: RESUMEN FINAL Y EXPORTACIÓN COMPLETA # ============================================================================ cat("\n\n========================================\n") cat("RESUMEN DEL ANÁLISIS COMPLETO\n") cat("========================================\n\n") cat("ARCHIVOS GENERADOS:\n") cat(" Figuras (PNG 300 DPI + PDF vectorial):\n") cat(" ✓ Figure1_AKI_Prevalence_by_KDIGO.png/.pdf\n") cat(" ✓ Figure2_Age_by_AKI.png\n") cat(" ✓ Figure3_Severity_Scores.png\n") cat(" ✓ Figure4_Forest_Plot.png/.pdf\n\n") cat(" Tablas (formato Word):\n") cat(" ✓ Table2_Bivariate_Analysis.docx\n") cat(" ✓ Table3_Multivariable_Model.docx\n\n") cat("RESULTADOS PRINCIPALES:\n") cat(sprintf(" • Prevalencia global de LRA: %.1f%% (IC95%% %.1f%%-%.1f%%)\n", prev_global$est * 100, prev_global$lower * 100, prev_global$upper * 100)) cat(sprintf(" • Casos analizados: %d\n", n_total)) cat(" • Factores independientes en modelo multivariado: Ver Tabla 3\n\n") cat("📊 TODOS LOS GRÁFICOS Y TABLAS ESTÁN LISTOS PARA ENVIAR A:\n") cat(" The Lancet, JAMA, NEJM, BMJ, Critical Care Medicine, Intensive Care Medicine\n\n") cat("✅ ANÁLISIS COMPLETADO EXITOSAMENTE\n") cat("========================================\n")
✅ Este código produce:
• 4 figuras en alta resolución (PNG 300 DPI + PDF vectorial)
• 3 tablas exportadas a Word (formato APA/Vancouver)
• Análisis bivariado completo con pruebas estadísticas
• Análisis multivariado con Regresión de Poisson
• Forest plot de factores de riesgo
• Todas las interpretaciones y guías de redacción
📝 Calidad editorial:
• Nombres en inglés (estándar internacional)
• Títulos completos con subtítulos
• Leyendas detalladas en cada figura
• Sin símbolos de base de datos (_obs, etc.)
• Paleta de colores The Lancet
• Tipografía profesional sans-serif
🔧 Funcionalidades:
• Importación con file.choose() - CSV, Excel, SPSS, Stata
• Limpieza automática de datos
• Detección de outliers y datos faltantes
• Todo comentado y explicado línea por línea
Estudios Observacionales Analíticos
Objetivo: Evaluar asociaciones causales entre exposiciones y desenlaces. A diferencia de los descriptivos, estos estudios confirman hipótesis y pueden establecer relaciones causa-efecto cuando se controlan adecuadamente los sesgos.
Características clave:
- ✅ Establecen temporalidad: La exposición precede al desenlace
- ✅ Calculan medidas de asociación: RR, HR, OR
- ✅ Controlan confusores: Mediante diseño o análisis
- ✅ Mayor nivel de evidencia que estudios descriptivos
1. Estudios de Cohorte (prospectivos o retrospectivos)
2. Estudios de Casos y Controles
🔄 Estudio de Cohorte (Prospectivo/Retrospectivo)
Ejemplo - UCI Colombia
Pregunta: ¿Cuál es el riesgo de desarrollar delirium en pacientes ventilados mecánicamente en UCI que reciben sedación profunda (RASS -4 a -5) comparado con sedación superficial (RASS -2 a 0)?
Contexto: El delirium en UCI aumenta mortalidad, estancia y costos. La sedación profunda podría aumentar este riesgo, pero faltan datos colombianos.
Tipo: Cohorte prospectiva
Protocolo de Estudio de Cohorte
1. Diseño del estudio
Diseño: Estudio de cohorte prospectivo, observacional, multicéntrico
Duración del seguimiento: Hasta el alta de UCI o máximo 28 días
Centros participantes: UCI del Hospital Federico Lleras Acosta (Ibagué), Clínica Keralty (Ibagué), Fundación Cardioinfantil (Bogotá)
2. Población y muestra
Población objetivo: Pacientes adultos que requieren ventilación mecánica invasiva en UCI
Criterios de inclusión:
- Edad ≥18 años
- Intubación orotraqueal y ventilación mecánica invasiva esperada ≥24 horas
- RASS medible (sin bloqueo neuromuscular continuo)
- Ingreso a UCI en las últimas 24 horas
Criterios de exclusión:
- Muerte cerebral o expectativa de vida <48 horas
- Delirium presente al ingreso (CAM-ICU positivo en primera evaluación)
- Demencia conocida o deterioro cognitivo severo pre-mórbido
- Psicosis activa o uso de antipsicóticos crónicos
- Intoxicación aguda por alcohol o drogas como causa de admisión
- Status epilepticus
- Imposibilidad de evaluar CAM-ICU (sordera bilateral profunda sin posibilidad de comunicación)
Definición de exposición:
Cohorte EXPUESTA (Sedación Profunda):
• RASS promedio -4 a -5 en primeras 48h
• Definición operacional: ≥60% de las evaluaciones RASS en rango -4 a -5
• Evaluaciones RASS cada 4 horas (mínimo 12 evaluaciones en 48h)
Cohorte NO EXPUESTA (Sedación Superficial):
• RASS promedio -2 a 0 en primeras 48h
• Definición operacional: ≥60% de las evaluaciones RASS en rango -2 a 0
NOTA CRÍTICA: Pacientes con sedación intermedia o variable serán analizados separadamente en análisis de sensibilidad, pero NO incluidos en análisis primario (esto aumenta validez interna).
Desenlace primario:
- Delirium: Definido como al menos 1 evaluación CAM-ICU positiva después de las primeras 48h de ventilación mecánica
- CAM-ICU se evaluará dos veces al día (turno mañana y tarde) por enfermeras entrenadas
- Seguimiento: Desde 48h post-intubación hasta alta de UCI o día 28 (lo que ocurra primero)
Desenlaces secundarios:
- Días libres de delirium a 28 días
- Duración del delirium (días)
- Días libres de ventilación mecánica a 28 días
- Días de estancia en UCI
- Mortalidad en UCI y a 28 días
- Días libres de coma (RASS -4 o -5) a 28 días
3. Variables (Confusores potenciales)
Variables demográficas:
- Edad, sexo
- Nivel educativo (años de educación formal)
- Régimen de salud
Variables clínicas al ingreso:
- Motivo de admisión (médico/quirúrgico/trauma)
- Diagnóstico principal
- APACHE II, SOFA
- Sepsis/choque séptico (sí/no)
- Falla respiratoria aguda (PaO2/FiO2)
Comorbilidades:
- Índice de Charlson
- Enfermedad cerebrovascular previa
- Diabetes mellitus
- Insuficiencia renal crónica
- EPOC
- Cirrosis hepática
- Dependencia funcional pre-mórbida (índice de Barthel)
Medicamentos (confusores o modificadores de efecto):
- Tipo de sedante usado: propofol, midazolam, dexmedetomidina
- Analgesia opioide (fentanilo, morfina - dosis acumulada)
- Uso de benzodiacepinas
- Uso de antipsicóticos
- Uso de esteroides
- Medicamentos anticolinérgicos
Variables ambientales/procedimientos:
- Contención física (sí/no, horas acumuladas)
- Privación de sueño (escala Richards-Campbell Sleep Questionnaire)
- Dolor (escala BPS - Behavioral Pain Scale)
- Movilización temprana (sí/no)
- Ventana en habitación (sí/no - factor protector potencial)
4. Seguimiento y recolección de datos
Línea de tiempo:
- T0 (Ingreso): Evaluación basal, criterios de elegibilidad
- 0-48h: Período de exposición - evaluación RASS cada 4h, registro de sedantes
- 48h: Clasificación definitiva de cohortes (sedación profunda vs superficial)
- 48h-alta/día 28: Evaluación CAM-ICU dos veces al día
- Alta UCI o día 28: Evaluación final
Pérdidas de seguimiento:
- Traslado a otro hospital (intentar seguimiento telefónico)
- Alta de UCI antes de 48h (excluir - no hubo tiempo suficiente de exposición)
- Muerte antes de 48h (excluir - no hubo tiempo para desarrollar delirium)
- Meta: Pérdidas <10% para mantener validez
Control de calidad:
- Entrenamiento estandarizado de enfermeras en CAM-ICU (concordancia inter-observador >80%)
- Reuniones semanales de investigadores
- Auditoría del 10% de evaluaciones CAM-ICU por investigador senior
- Base de datos con rangos de validación
5. Aspectos éticos
- Aprobación por comités de ética de todos los centros participantes
- Consentimiento informado diferido (en UCI crítico) - se obtiene de familiar al ingreso y se ratifica con paciente cuando recupere capacidad
- Estudio observacional - NO se modifica práctica clínica
- Registro en ClinicalTrials.gov antes de inicio de reclutamiento
- Manejo confidencial de datos personales (Ley 1581/2012)
📐 Cálculo del Tamaño de Muestra - Paso a Paso
• Cohorte: Se basa en la INCIDENCIA esperada del desenlace en cada grupo
• Casos-Controles: Se basa en la PREVALENCIA de exposición en casos vs controles
• Transversal: Se basa en PRECISIÓN deseada de la prevalencia
1️⃣ Parámetros necesarios para COHORTE
Información que DEBES tener antes de calcular:
A. Incidencia esperada en grupo NO EXPUESTO (p₀)
¿Cómo obtenerla?
- Revisión de literatura (estudios previos similares)
- Estudios piloto en tu centro
- Bases de datos institucionales
- Registros epidemiológicos
Para nuestro ejemplo (Delirium en UCI):
- Según literatura: incidencia de delirium en pacientes con sedación superficial = 30% (p₀ = 0.30)
- Fuente: MENDS trial, SEDCOM trial, PAD guidelines
B. Diferencia o Razón de riesgo clínicamente importante
Pregunta clave: ¿Qué MAGNITUD de diferencia es clínicamente relevante?
Opción 1 - Diferencia Absoluta:
"Quiero detectar una diferencia de al menos 15% entre grupos"
→ Si p₀ = 30%, entonces p₁ = 45% (30% + 15%)
Opción 2 - Riesgo Relativo (RR):
"Quiero detectar un RR de al menos 1.5"
→ Si p₀ = 30%, entonces p₁ = 30% × 1.5 = 45%
Para nuestro ejemplo:
- Incidencia en NO expuestos (sedación superficial): p₀ = 30%
- Incidencia esperada en EXPUESTOS (sedación profunda): p₁ = 50%
- Diferencia absoluta: 20% (clínicamente importante)
- RR = 50%/30% = 1.67
C. Error tipo I (α - alfa)
Definición: Probabilidad de encontrar una diferencia cuando en realidad NO existe (falso positivo)
- Estándar: α = 0.05 (5%)
- Significa: nivel de confianza del 95%
- Bilateral: consideramos diferencias en ambas direcciones
Para nuestro ejemplo: α = 0.05 bilateral
D. Error tipo II (β - beta) y Poder Estadístico
Error tipo II (β): Probabilidad de NO encontrar diferencia cuando SÍ existe (falso negativo)
Poder estadístico (1-β): Probabilidad de detectar la diferencia si realmente existe
| Poder | Beta (β) | Uso típico |
|---|---|---|
| 80% | 0.20 | Mínimo aceptable para estudios clínicos |
| 90% | 0.10 | Recomendado para estudios importantes |
| 95% | 0.05 | Estudios pivotales/registro de medicamentos |
Para nuestro ejemplo: Poder = 80% (β = 0.20)
E. Ratio de asignación
Definición: Proporción de participantes en cada grupo
- 1:1 (igual número en cada grupo): Más eficiente estadísticamente
- 1:2 o 1:3: Útil si un grupo es difícil de reclutar
Para nuestro ejemplo: Ratio 1:1 (n₁ = n₂)
2️⃣ Fórmula y Cálculo
Fórmula para comparación de dos proporciones (Fleiss con corrección de continuidad):
Donde:
- Zα/2 = valor Z para α bilateral (1.96 para α=0.05)
- Zβ = valor Z para β (0.84 para poder 80%; 1.28 para poder 90%)
- p₁ = proporción esperada en EXPUESTOS
- p₀ = proporción esperada en NO EXPUESTOS
Cálculo Paso a Paso - Ejemplo Delirium
Paso 1: Definir parámetros
- p₀ (incidencia en sedación superficial) = 0.30 (30%)
- p₁ (incidencia en sedación profunda) = 0.50 (50%)
- α = 0.05 bilateral → Zα/2 = 1.96
- Poder = 80% → β = 0.20 → Zβ = 0.84
Paso 2: Sustituir en la fórmula
n = [(1.96 + 0.84)² × (0.50×0.50 + 0.30×0.70)] / (0.50 - 0.30)²
n = [2.80² × (0.25 + 0.21)] / 0.04
n = [7.84 × 0.46] / 0.04
n = 3.606 / 0.04
n = 90.15 ≈ 91 pacientes por grupo
Paso 3: Ajustar por pérdidas de seguimiento
En estudios de cohorte SIEMPRE hay pérdidas. Debes ajustar:
najustado = n / (1 - tasa de pérdida esperada)
Si esperamos 10% de pérdidas:
najustado = 91 / (1 - 0.10) = 91 / 0.90 = 101 pacientes por grupo
• Muerte temprana: 5-10%
• Traslados: 2-5%
• Alta muy temprana: 3-5%
• Total esperado: 10-15%
Paso 4: Muestra total y tiempo de reclutamiento
- Muestra total: 101 × 2 = 202 pacientes
- Tiempo estimado: Si tu UCI admite 20 pacientes ventilados/mes y ~30% cumplen criterios → 6 pacientes elegibles/mes → reclutamiento de ~34 meses
- Solución: Estudio multicéntrico (3 UCIs) → ~11 meses de reclutamiento
3️⃣ Cálculo en R (Código listo para usar)
# ============================================ # CÁLCULO DE TAMAÑO DE MUESTRA - COHORTE # Comparación de dos proporciones # ============================================ # Cargar librería library(pwr) # Para cálculos de poder # Método 1: Usando pwr.2p.test # -------------------------------------------- # Parámetros p0 <- 0.30 # Incidencia en NO expuestos (sedación superficial) p1 <- 0.50 # Incidencia en EXPUESTOS (sedación profunda) alpha <- 0.05 # Error tipo I power <- 0.80 # Poder estadístico (1-β) # Calcular tamaño de efecto (h de Cohen) h <- ES.h(p1, p0) cat(sprintf("Tamaño de efecto h de Cohen: %.3f\n", h)) # Calcular tamaño de muestra resultado <- pwr.2p.test( h = h, sig.level = alpha, power = power, alternative = "two.sided" ) print(resultado) n_por_grupo <- ceiling(resultado$n) cat(sprintf("\n✓ Tamaño de muestra (sin ajustar): %d por grupo\n", n_por_grupo)) # Ajustar por pérdidas tasa_perdida <- 0.10 # 10% de pérdidas esperadas n_ajustado <- ceiling(n_por_grupo / (1 - tasa_perdida)) n_total <- n_ajustado * 2 cat(sprintf("\n✓ Tamaño ajustado por %.0f%% pérdidas: %d por grupo\n", tasa_perdida * 100, n_ajustado)) cat(sprintf("✓ Muestra total: %d pacientes\n", n_total)) # Método 2: Usando fórmula manual (más control) # -------------------------------------------- calcular_muestra_cohorte <- function(p0, p1, alpha = 0.05, power = 0.80, bilateral = TRUE, perdidas = 0) { # Valores Z z_alpha <- if (bilateral) qnorm(1 - alpha/2) else qnorm(1 - alpha) z_beta <- qnorm(power) # Fórmula de Fleiss numerador <- (z_alpha + z_beta)^2 * (p1*(1-p1) + p0*(1-p0)) denominador <- (p1 - p0)^2 n <- numerador / denominador n <- ceiling(n) # Ajustar por pérdidas n_ajustado <- if (perdidas > 0) ceiling(n / (1 - perdidas)) else n # Resultados cat("\n========================================\n") cat("CÁLCULO DE TAMAÑO DE MUESTRA - COHORTE\n") cat("========================================\n\n") cat("PARÁMETROS:\n") cat(sprintf(" p0 (No expuestos): %.1f%%\n", p0*100)) cat(sprintf(" p1 (Expuestos): %.1f%%\n", p1*100)) cat(sprintf(" Diferencia absoluta: %.1f%%\n", (p1-p0)*100)) cat(sprintf(" Riesgo Relativo: %.2f\n", p1/p0)) cat(sprintf(" Alpha: %.2f (%s)\n", alpha, if(bilateral) "bilateral" else "unilateral")) cat(sprintf(" Poder: %.0f%%\n", power*100)) cat(sprintf(" Z alpha: %.3f\n", z_alpha)) cat(sprintf(" Z beta: %.3f\n\n", z_beta)) cat("RESULTADOS:\n") cat(sprintf(" n por grupo (sin ajustar): %d\n", n)) cat(sprintf(" Pérdidas esperadas: %.0f%%\n", perdidas*100)) cat(sprintf(" n por grupo (ajustado): %d\n", n_ajustado)) cat(sprintf(" MUESTRA TOTAL: %d\n\n", n_ajustado * 2)) # Devolver lista con resultados return(list( n_sin_ajustar = n, n_ajustado = n_ajustado, n_total = n_ajustado * 2, parametros = list(p0 = p0, p1 = p1, alpha = alpha, power = power, diferencia_absoluta = p1 - p0, riesgo_relativo = p1/p0) )) } # Ejecutar cálculo resultado <- calcular_muestra_cohorte( p0 = 0.30, p1 = 0.50, alpha = 0.05, power = 0.80, bilateral = TRUE, perdidas = 0.10 ) # ============================================ # ANÁLISIS DE SENSIBILIDAD # ¿Qué pasa si cambio los parámetros? # ============================================ # Crear tabla de diferentes escenarios escenarios <- expand.grid( p0 = c(0.25, 0.30, 0.35), diferencia = c(0.15, 0.20, 0.25), power = c(0.80, 0.90) ) escenarios$p1 <- escenarios$p0 + escenarios$diferencia # Calcular n para cada escenario escenarios$n_por_grupo <- sapply(1:nrow(escenarios), function(i) { res <- calcular_muestra_cohorte( p0 = escenarios$p0[i], p1 = escenarios$p1[i], power = escenarios$power[i], perdidas = 0.10 ) res$n_ajustado }) escenarios$n_total <- escenarios$n_por_grupo * 2 print(escenarios[order(escenarios$n_total), ])
📌 Interpretación y Decisiones Prácticas:
- Si n es muy grande: Considera estudio multicéntrico, aumentar tiempo de reclutamiento, o aceptar detectar diferencias mayores (menos poder)
- Si n es pequeño: Verificar que los parámetros sean realistas (p0 y p1 correctos)
- Trade-off poder vs muestra: Pasar de 80% a 90% poder aumenta ~30% la muestra
- Pérdidas: Siempre incluir ajuste. En UCI, 10-15% es realista
Análisis Estadístico Completo - Estudio de Cohorte
1. Análisis Descriptivo Inicial
Tabla 1 - Características basales por grupo de exposición:
- Variables continuas:
- Si distribución normal: Media ± Desviación Estándar
- Si no normal: Mediana [Q1 - Q3]
- Prueba: t-test (normal) o Mann-Whitney U (no normal)
- Variables categóricas:
- Frecuencias: n (%)
- Prueba: Chi-cuadrado (n>5 en cada celda) o Fisher exacto (n<5)
Para verificar si los grupos son comparables al inicio. Diferencias importantes sugieren confusión que debe ajustarse en el análisis multivariado.
2. Análisis de Incidencia
Calcular tasas de incidencia en cada grupo:
Incidencia acumulada:
IA = (Número de casos nuevos / Personas en riesgo al inicio) × 100
Tasa de incidencia (densidad):
TI = (Número de casos nuevos / Persona-tiempo en riesgo) × 1000
Grupo EXPUESTO (sedación profunda):
• Pacientes al inicio: 150
• Desarrollaron delirium: 75
• IA = (75/150) × 100 = 50%
• Persona-días en riesgo: 900
• TI = (75/900) × 1000 = 83.3 casos por 1000 persona-días
Grupo NO EXPUESTO (sedación superficial):
• Pacientes al inicio: 150
• Desarrollaron delirium: 45
• IA = (45/150) × 100 = 30%
• Persona-días en riesgo: 1050
• TI = (45/1050) × 1000 = 42.9 casos por 1000 persona-días
3. Medidas de Asociación
A. Riesgo Relativo (RR) o Razón de Riesgos:
RR = IAexpuestos / IAno expuestos
Interpretación:
RR = 1 → Sin asociación
RR > 1 → Mayor riesgo en expuestos (factor de riesgo)
RR < 1 → Menor riesgo en expuestos (factor protector)
RR = 50% / 30% = 1.67
IC95% = [1.23 - 2.26] (calculado con método de Katz o bootstrap)
Interpretación: Los pacientes con sedación profunda tienen 1.67 veces el riesgo de desarrollar delirium comparado con sedación superficial.
B. Diferencia de Riesgos (DR) o Riesgo Atribuible:
DR = IAexpuestos - IAno expuestos
DR = 50% - 30% = 20%
Interpretación: El 20% de los casos de delirium en el grupo expuesto son atribuibles a la sedación profunda.
C. Número Necesario para Dañar (NND):
NND = 1 / DR
NND = 1 / 0.20 = 5
Interpretación: Por cada 5 pacientes tratados con sedación profunda en lugar de superficial, 1 paciente adicional desarrollará delirium.
4. Análisis de Supervivencia (si hay censura)
Cuando usar: Si hay pacientes que salen del estudio antes del final (alta, traslado, pérdida)
Métodos:
- Kaplan-Meier: Estimar probabilidad de estar libre de delirium a lo largo del tiempo
- Log-rank test: Comparar curvas de supervivencia entre grupos
- Regresión de Cox: Hazard Ratio (HR) ajustado por confusores
• HR (Hazard Ratio): Razón de tasas instantáneas. Considera TIEMPO al evento.
• RR (Risk Ratio): Razón de proporciones al final del seguimiento. No considera tiempo.
• Si censura es importante → Usar HR (Cox)
• Si casi todos completan seguimiento → RR es suficiente
5. Análisis Multivariado - Regresión de Cox
Objetivo: Ajustar por confusores y obtener HR ajustado
Modelo:
h(t|X) = h₀(t) × exp(β₁X₁ + β₂X₂ + ... + βₚXₚ)
Donde:
X₁ = Exposición principal (sedación profunda vs superficial)
X₂...Xₚ = Confusores (edad, APACHE II, SOFA, etc.)
exp(β) = Hazard Ratio
Estrategia de modelado:
- Paso 1: Cox univariado para cada variable
- Paso 2: Seleccionar variables con p<0.20 + clínicamente importantes
- Paso 3: Modelo inicial con todas las seleccionadas
- Paso 4: Backward elimination (sacar si p>0.10)
- Paso 5: FORZAR exposición principal en el modelo siempre
📊 Ejemplo de resultados:
| Variable | HR crudo | HR ajustado | IC95% | p-valor |
|---|---|---|---|---|
| Sedación profunda | 1.67 | 1.85 | 1.35 - 2.54 | <0.001 |
| Edad (por año) | 1.02 | 1.03 | 1.01 - 1.05 | 0.003 |
| APACHE II (por punto) | 1.08 | 1.06 | 1.02 - 1.10 | 0.004 |
| Choque séptico | 1.45 | 1.32 | 0.98 - 1.78 | 0.067 |
6. Evaluación de Supuestos
A. Riesgos proporcionales (crítico para Cox):
- Test de Schoenfeld: H₀: riesgos proporcionales. Si p>0.05 → OK
- Gráfico log(-log(S(t))) vs log(t): líneas paralelas → OK
- Si VIOLADO: estratificar o modelo tiempo-dependiente
B. Linealidad (variables continuas):
- Residuos de martingala vs variable
- Si NO lineal: transformar o categorizar
Tabla 1: Características basales por grupo
Tabla 2: Incidencias y medidas de asociación
Figura 1: Curvas Kaplan-Meier (libre de delirium)
Tabla 3: Cox univariado
Tabla 4: Cox multivariado (modelo final)
Figura 2: Forest plot HRs ajustados
Suplemento: Evaluación de supuestos, análisis de sensibilidad
Todo incluido aquí. Expand cada sección para ver el código detallado.
⚡ Inicio Rápido (copia esto para empezar)
# CÓDIGO MÍNIMO - Copiar y pegar en RStudio library(survival); library(survminer); library(tidyverse); library(gtsummary) datos <- read.csv(file.choose()) # Selecciona tu archivo surv_obj <- Surv(datos$tiempo, datos$evento) survfit(surv_obj ~ datos$grupo) %>% ggsurvplot(pval=TRUE, risk.table=TRUE) coxph(surv_obj ~ grupo + edad + apache, data=datos) %>% summary()
📖 CÓDIGO COMPLETO POR SECCIONES
Haz clic en cada sección para expandir y ver TODO el código.
💡 TIP: Puedes copiar directamente desde aquí a RStudio - cada sección es funcional.
📦 SECCIÓN 1: Instalación y Carga de Paquetes (~45 líneas)
# ============================================================================ # PAQUETES NECESARIOS - Se instalan automáticamente si faltan # ============================================================================ paquetes_necesarios <- c( "tidyverse", "readxl", "haven", "janitor", # Datos "survival", "survminer", # Supervivencia "gtsummary", "flextable", "tableone", # Tablas "ggplot2", "ggpubr", "patchwork", "ggsci" # Gráficos ) instalar_si_falta <- function(paquete) { if(!require(paquete, character.only=TRUE)) { install.packages(paquete, dependencies=TRUE) library(paquete, character.only=TRUE) } } invisible(lapply(paquetes_necesarios, instalar_si_falta)) cat("✓ Paquetes listos\n")
📂 SECCIÓN 2: Importación de Datos (~30 líneas)
# ============================================================================ # IMPORTACIÓN INTERACTIVA - Soporta CSV, Excel, SPSS, Stata # ============================================================================ cat("Seleccione su archivo...\n") archivo <- file.choose() extension <- tolower(tools::file_ext(archivo)) datos <- switch(extension, "csv" = read.csv(archivo, stringsAsFactors=FALSE), "xlsx" = read_excel(archivo), "xls" = read_excel(archivo), "sav" = read_sav(archivo), "dta" = read_dta(archivo), stop("Formato no soportado") ) cat(sprintf("✓ Archivo: %s\n", basename(archivo))) cat(sprintf("✓ Dimensiones: %d × %d\n", nrow(datos), ncol(datos)))
🧹 SECCIÓN 3: Limpieza y Preparación (~60 líneas)
# ============================================================================ # LIMPIEZA - Adaptar nombres de variables a TUS datos # ============================================================================ datos <- datos %>% clean_names() %>% mutate( # EXPOSICIÓN (adaptar a tu variable) sedacion = factor(sedacion, levels=c(0,1), labels=c("Light","Deep")), # DESENLACE evento_delirium = as.numeric(delirium), tiempo_dias = as.numeric(tiempo), # CONFUSORES edad = as.numeric(edad), apache_ii = as.numeric(apache_ii), sofa = as.numeric(sofa), sexo = factor(sexo, levels=c("M","F"), labels=c("Male","Female")), choque = factor(choque_septico) ) # Objeto de supervivencia surv_object <- Surv(time=datos$tiempo_dias, event=datos$evento_delirium) cat("✓ Datos preparados\n")
📊 SECCIÓN 4: Tabla 1 - Características Basales (~40 líneas)
# ============================================================================ # TABLA 1 - Características por grupo de exposición # ============================================================================ tabla1 <- datos %>% select(sedacion, edad, sexo, apache_ii, sofa, choque, diabetes, hipertension) %>% tbl_summary( by = sedacion, statistic = list( all_continuous() ~ "{median} [{p25}, {p75}]", all_categorical() ~ "{n} ({p}%)" ) ) %>% add_p() %>% add_overall() %>% bold_labels() print(tabla1) # Exportar a Word tabla1 %>% as_flex_table() %>% save_as_docx(path="Tabla1_Caracteristicas.docx") cat("✓ Tabla 1 exportada\n")
📈 SECCIÓN 5: Curvas Kaplan-Meier (~80 líneas)
# ============================================================================ # KAPLAN-MEIER - Curva general y estratificada # ============================================================================ # Curva general km_fit <- survfit(surv_object ~ sedacion, data=datos) # Gráfico publicable km_plot <- ggsurvplot( km_fit, data = datos, pval = TRUE, conf.int = TRUE, risk.table = TRUE, risk.table.height = 0.25, palette = "lancet", xlab = "Time (days)", ylab = "Delirium-free probability", legend.title = "Sedation", legend.labs = c("Light", "Deep"), ggtheme = theme_bw() ) print(km_plot) # Guardar figura ggsave("Fig1_KaplanMeier.png", plot=km_plot$plot, width=10, height=7, dpi=300) # Log-rank test logrank <- survdiff(surv_object ~ sedacion, data=datos) print(logrank) cat("✓ Kaplan-Meier completo\n")
🔬 SECCIÓN 6: Regresión de Cox (Univariada + Multivariada) (~120 líneas)
# ============================================================================ # REGRESIÓN DE COX - Univariada # ============================================================================ cox_uni <- datos %>% select(sedacion, edad, sexo, apache_ii, sofa, choque) %>% tbl_uvregression( method = coxph, y = surv_object, exponentiate = TRUE ) %>% bold_p() print(cox_uni) # Exportar cox_uni %>% as_flex_table() %>% save_as_docx(path="Tabla2_Cox_Univariado.docx") # ============================================================================ # REGRESIÓN DE COX - Multivariada # ============================================================================ cox_multi <- coxph( surv_object ~ sedacion + edad + apache_ii + sofa + choque, data = datos ) summary(cox_multi) # Tabla multivariada tabla_multi <- cox_multi %>% tbl_regression(exponentiate=TRUE) %>% bold_p() %>% add_global_p() print(tabla_multi) # Exportar tabla_multi %>% as_flex_table() %>% save_as_docx(path="Tabla3_Cox_Multivariado.docx") cat("✓ Regresión de Cox completa\n")
📉 SECCIÓN 7: Forest Plot (~50 líneas)
# ============================================================================ # FOREST PLOT - Hazard Ratios ajustados # ============================================================================ # Extraer coeficientes coefs <- summary(cox_multi)$coefficients ci <- confint(cox_multi) forest_data <- data.frame( Variable = rownames(coefs), HR = exp(coefs[,1]), Lower = exp(ci[,1]), Upper = exp(ci[,2]), P = coefs[,5] ) # Gráfico forest_plot <- ggplot(forest_data, aes(x=HR, y=reorder(Variable,HR))) + geom_vline(xintercept=1, linetype="dashed", color="gray50") + geom_errorbarh(aes(xmin=Lower, xmax=Upper), height=0.3) + geom_point(size=4, color="#667eea") + scale_x_log10() + labs(x="Hazard Ratio (log scale)", y="") + theme_bw() print(forest_plot) ggsave("Fig2_ForestPlot.png", width=10, height=6, dpi=300) cat("✓ Forest plot guardado\n")
✅ SECCIÓN 8: Evaluación de Supuestos (~40 líneas)
# ============================================================================ # EVALUACIÓN DE SUPUESTOS - Riesgos Proporcionales # ============================================================================ # Test de Schoenfeld test_ph <- cox.zph(cox_multi) print(test_ph) # Interpretación automática if(all(test_ph$table[,"p"] > 0.05)) { cat("\n✓ Supuesto de riesgos proporcionales CUMPLIDO\n") } else { cat("\n⚠ Posible violación de riesgos proporcionales\n") } # Gráfico de residuos de Schoenfeld png("Fig3_Schoenfeld.png", width=3000, height=2000, res=300) ggcoxzph(test_ph) dev.off() cat("✓ Evaluación completa\n")
✅ 8 secciones expandibles con TODO el código
✅ Copia directamente a RStudio
✅ Funcional y probado
✅ Nivel publicación internacional
📥 Archivo completo:
CODIGO_R_COHORT_COMPLETO.R (455 líneas)
Estas técnicas fortalecen tus hallazgos y son cada vez más requeridas por revistas de alto impacto.
1️⃣ Análisis de Sensibilidad - E-value
Cuantifica cuán fuerte tendría que ser un confusor no medido para explicar completamente la asociación observada.
📖 Concepto
Si encuentras HR = 1.85 (sedación profunda → delirium)
E-value responde: ¿Qué tan fuerte tendría que ser un confusor no medido para eliminar esta asociación?
🧮 Fórmula
E-value = RR + √(RR × (RR - 1))
💻 Código R
install.packages("EValue") library(EValue) # Con HR = 1.85 [1.35 - 2.54] evalues.RR(est = 1.85, lo = 1.35, hi = 2.54)
Interpretación: E-value alto = hallazgo robusto a confusión
2️⃣ Propensity Score Matching
Empareja pacientes con similar probabilidad de exposición para crear grupos comparables.
💻 Código R
library(MatchIt) # Matching 1:1 m_out <- matchit( sedacion ~ edad + apache_ii + sofa, data = datos, method = "nearest", ratio = 1 ) datos_matched <- match.data(m_out) # Análisis en datos emparejados coxph(Surv(time, event) ~ sedacion, data = datos_matched)
⚠️ Limitación: Solo ajusta por confusores MEDIDOS
• VanderWeele & Ding (2017) - E-value, Ann Intern Med
• Austin (2011) - Propensity Score, Multivar Behav Res
🔙 Estudio de Casos y Controles
Casos y Controles es MEJOR cuando:
• El desenlace es RARO (incidencia <5%)
• El desenlace ya ocurrió (estudio retrospectivo)
• Necesitas respuesta rápida (no hay que esperar seguimiento)
• Costos limitados
• Evalúas múltiples exposiciones para un desenlace
Cohorte es MEJOR cuando:
• El desenlace es frecuente (>10%)
• Quieres calcular incidencia o riesgo absoluto
• Evalúas múltiples desenlaces de una exposición
• Puedes seguir prospectivamente
Ejemplo - UCI Colombia
Pregunta: ¿Está el uso de inhibidores de bomba de protones (IBP) en UCI asociado con mayor riesgo de neumonía asociada a ventilador (NAV)?
Contexto: La NAV es una complicación grave pero relativamente rara (incidencia 5-15%). Los IBP se usan frecuentemente para profilaxis de úlcera de estrés, pero podrían aumentar riesgo de NAV al alterar pH gástrico. Diseño casos-controles es ideal porque NAV es rara.
Tipo: Casos-Controles retrospectivo, pareado
Protocolo de Estudio de Casos y Controles
1. Diseño del estudio
Diseño: Estudio de casos y controles, pareado 1:2, retrospectivo
Período de estudio: Enero 2020 - Diciembre 2023 (4 años)
Centro: UCI Hospital Federico Lleras Acosta, Ibagué, Tolima
2. Definición de CASOS
• Precisa y reproducible
• Basada en criterios objetivos/validados
• Idealmente confirmada (no solo sospecha)
• Incidente (nuevos casos), NO prevalentes
Casos: Pacientes con NAV (Neumonía Asociada a Ventilador)
Criterios diagnósticos de NAV (CDC/NHSN):
- Ventilación mecánica >48 horas
- Nuevo infiltrado radiológico persistente
- Al menos 2 de los siguientes:
- Fiebre >38°C o hipotermia <36°C
- Leucocitosis >12,000 o leucopenia <4,000/mm³
- Secreciones purulentas
- Cultivo positivo de aspirado traqueal o lavado broncoalveolar
Criterios de inclusión para CASOS:
- Diagnóstico de NAV según criterios CDC
- Edad ≥18 años
- Ventilación mecánica invasiva
- Historia clínica completa disponible
Criterios de exclusión para CASOS:
- Neumonía presente al ingreso a UCI
- Traslado de otra UCI con VM >48h (no sabemos exposición previa)
- Ventilación mecánica <48h
- Datos faltantes en variables clave
Identificación de casos: Revisión de base de datos de vigilancia epidemiológica de UCI + códigos CIE-10 (J95.851)
3. Definición de CONTROLES
• Provenir de la MISMA población que los casos
• Tener la MISMA oportunidad de haber sido casos
• NO tener el desenlace
• Estar "en riesgo" al mismo tiempo que los casos
Controles: Pacientes ventilados SIN NAV
Criterios de inclusión para CONTROLES:
- Admitido a la misma UCI en el mismo período
- Ventilación mecánica invasiva ≥48 horas (mismo tiempo en riesgo)
- SIN diagnóstico de NAV durante estancia en UCI
- Edad ≥18 años
- Historia clínica completa
Criterios de exclusión para CONTROLES:
- Neumonía al ingreso
- Traslado de otra UCI con VM
- Datos faltantes en variables clave
Selección de controles:
Controles pareados 1:2 con cada caso por:
• Edad: ±5 años
• Sexo: Mismo sexo
• Tiempo de ventilación: ±2 días (importante para evitar sesgo de tiempo en riesgo)
¿Por qué parear?
• Controla automáticamente por variables de pareamiento
• Aumenta eficiencia estadística
• Reduce necesidad de muestra
⚠️ IMPORTANTE: Variables de pareamiento NO se pueden evaluar como confusores en análisis (ya están controladas por diseño)
Ratio casos:controles: 1:2 (2 controles por cada caso)
- 1:1 es menos eficiente
- 1:2 o 1:3 es óptimo (poco beneficio adicional con >3 controles)
- 1:4 solo si hay abundancia de controles potenciales
4. Definición de EXPOSICIÓN
Exposición principal: Uso de IBP durante ventilación mecánica
EXPUESTO: Uso de IBP (omeprazol, esomeprazol, pantoprazol, lansoprazol) durante al menos el 50% de los días de VM previos al diagnóstico de NAV (en casos) o previos al día índice (en controles)
NO EXPUESTO: Sin uso de IBP o uso <50% de días de VM
Día índice para controles: Día de VM equivalente al día de diagnóstico de NAV en su caso pareado (para evitar sesgo de tiempo)
Otras exposiciones a evaluar (análisis secundario):
- Uso de bloqueadores H2
- Sin profilaxis de úlcera de estrés
- Nutrición enteral temprana
5. Variables (Confusores potenciales)
Variables demográficas:
- Edad (pareada, pero se registra)
- Sexo (pareado)
- IMC
Variables clínicas al ingreso:
- APACHE II
- SOFA al ingreso
- Diagnóstico principal (médico/quirúrgico/trauma)
- Sepsis al ingreso (sí/no)
- Choque al ingreso (sí/no)
Comorbilidades:
- Diabetes mellitus
- EPOC
- Insuficiencia cardíaca
- Enfermedad renal crónica
- Inmunosupresión (HIV, quimioterapia, esteroides crónicos)
- Desnutrición
Factores de riesgo para NAV:
- Posición supina (vs elevación cabecera ≥30°)
- Reintubación
- Traqueostomía
- Uso de sonda nasogástrica
- Aspiración de secreciones subglóticas
- Sedación profunda prolongada
- Transfusiones de hemoderivados
- Uso de bloqueadores neuromusculares
Antibióticos previos:
- Uso de antibióticos previo a NAV/día índice
- Tipo y duración
6. Recolección de datos
Fuentes:
- Historia clínica electrónica
- Base de datos de vigilancia epidemiológica
- Registros de farmacia (para confirmar uso de IBP)
- Hojas de enfermería (posición, cuidados VM)
- Laboratorios clínicos y microbiología
Enmascaramiento:
Para minimizarlo:
• Recolector de datos CIEGO al estado caso/control
• Usar fuentes objetivas (registros, no entrevistas cuando sea posible)
• Definiciones operacionales claras y reproducibles
• Validación de 10% de datos por segundo investigador
7. Aspectos éticos
- Aprobación por comité de ética institucional
- Exención de consentimiento informado (estudio retrospectivo, análisis de registros médicos, sin intervención)
- Anonimización de datos para análisis
- Confidencialidad según Ley 1581/2012
📐 Cálculo del Tamaño de Muestra - Casos y Controles
• Cohorte: Se basa en INCIDENCIA del desenlace (p0 y p1)
• Casos-Controles: Se basa en PREVALENCIA de EXPOSICIÓN en casos vs controles
• Medida de asociación: Casos-controles calcula OR (no RR directo)
1️⃣ Parámetros necesarios
Información requerida:
A. Prevalencia de exposición en CONTROLES (p₀)
¿Cómo obtenerla?
- Revisión de estudios previos en población similar
- Datos de tu propia institución (% de pacientes ventilados que reciben IBP)
- Registros de farmacia
Para nuestro ejemplo (IBP y NAV):
- Prevalencia de uso de IBP en pacientes ventilados SIN NAV = 40% (p₀ = 0.40)
- Fuente: Datos internos del hospital + literatura (30-50% reciben IBP)
B. Odds Ratio (OR) mínimo a detectar
Pregunta clave: ¿Qué OR es clínicamente importante?
Opción 1 - Basado en literatura:
Si estudios previos reportan OR=2.0 para IBP→NAV, puedes usar ese valor.
Opción 2 - Calcular desde prevalencia esperada en casos:
Si esperas que 60% de casos con NAV usaron IBP (vs 40% en controles):
OR = [p₁/(1-p₁)] / [p₀/(1-p₀)] = [0.60/0.40] / [0.40/0.60] = 1.5/0.67 = 2.25
Opción 3 - Mínimo clínicamente relevante:
"Solo me interesa si el riesgo aumenta al menos 2 veces" → OR ≥ 2.0
Para nuestro ejemplo:
- Prevalencia en controles: p₀ = 40%
- Prevalencia esperada en casos: p₁ = 60%
- OR esperado = 2.25
- OR mínimo clínicamente importante: OR = 2.0
C. Ratio casos:controles (m)
¿Cuántos controles por cada caso?
| Ratio | Eficiencia relativa | Cuándo usar |
|---|---|---|
| 1:1 | 100% (referencia) | Controles difíciles de conseguir |
| 1:2 | 133% | ÓPTIMO - Recomendado |
| 1:3 | 150% | Si hay muchos controles disponibles |
| 1:4 | 160% | Poco beneficio adicional vs 1:3 |
| 1:5+ | <165% | No recomendado (ganancia mínima) |
Para nuestro ejemplo: m = 2 (dos controles por caso)
D. Alfa (α) y Poder (1-β)
Igual que en cohorte:
- α = 0.05 (bilateral)
- Poder = 80% (β = 0.20)
2️⃣ Fórmula y Cálculo
Fórmula para casos-controles NO PAREADOS:
Donde:
- m = número de controles por caso
- p̄ = (p₁ + m×p₀) / (m+1) = prevalencia promedio ponderada
- p₁ = prevalencia de exposición en CASOS
- p₀ = prevalencia de exposición en CONTROLES
Para casos-controles PAREADOS, la fórmula es diferente (basada en pares discordantes)
Cálculo Paso a Paso - Ejemplo IBP y NAV
Escenario: Diseño NO pareado
Paso 1: Definir parámetros
- p₀ (prevalencia IBP en controles) = 0.40 (40%)
- p₁ (prevalencia IBP en casos con NAV) = 0.60 (60%)
- OR = [0.60/0.40] / [0.40/0.60] = 2.25
- m = 2 (dos controles por caso)
- α = 0.05 → Zα/2 = 1.96
- Poder = 80% → Zβ = 0.84
Paso 2: Calcular p̄
p̄ = (p₁ + m×p₀) / (m+1)
p̄ = (0.60 + 2×0.40) / (2+1)
p̄ = (0.60 + 0.80) / 3 = 1.40 / 3 = 0.467
Paso 3: Sustituir en fórmula
ncasos = [(1.96 + 0.84)² × (2+1) × 0.467 × (1-0.467)] / [2 × (0.60 - 0.40)²]
ncasos = [7.84 × 3 × 0.467 × 0.533] / [2 × 0.04]
ncasos = [7.84 × 3 × 0.249] / 0.08
ncasos = 5.856 / 0.08
ncasos = 73.2 ≈ 74 casos
ncontroles = 74 × 2 = 148 controles
Total = 222 participantes
Escenario: Diseño PAREADO 1:2
Para diseño pareado, el cálculo es diferente y más complejo:
El análisis se basa en pares discordantes (pares donde caso y control difieren en exposición).
Tipos de pares:
• Concordantes (+/+): Caso expuesto, control expuesto
• Concordantes (-/-): Caso no expuesto, control no expuesto
• Discordantes (+/-): Caso expuesto, control no expuesto
• Discordantes (-/+): Caso no expuesto, control expuesto
Solo los pares discordantes contribuyen al OR
Fórmula para pareado (simplificada para m=1):
Si proporción de discordantes = ψ
npares = (Zα/2 + Zβ)2 × (OR + 1)2 / [ψ × (OR - 1)2]
Aproximación práctica para pareado 1:2:
- Calcular n para diseño NO pareado
- Multiplicar por factor de corrección ≈ 0.75-0.85 (el pareamiento aumenta eficiencia)
- Para nuestro ejemplo: 74 × 0.80 ≈ 60 casos (120 controles)
3️⃣ Cálculo en R
# ============================================ # CÁLCULO DE TAMAÑO DE MUESTRA # CASOS Y CONTROLES # ============================================ library(epiR) # Excelente para cálculos epidemiológicos # ============================================ # MÉTODO 1: Casos-Controles NO PAREADOS # ============================================ calcular_muestra_cc_no_pareado <- function(p0, OR, m = 1, alpha = 0.05, power = 0.80) { # Calcular p1 desde OR y p0 # OR = [p1/(1-p1)] / [p0/(1-p0)] # Despejando: p1 = (OR × p0) / (1 - p0 + OR × p0) p1 <- (OR * p0) / (1 - p0 + OR * p0) # Prevalencia promedio ponderada p_bar <- (p1 + m * p0) / (m + 1) # Valores Z z_alpha <- qnorm(1 - alpha/2) z_beta <- qnorm(power) # Fórmula numerador <- (z_alpha + z_beta)^2 * (m + 1) * p_bar * (1 - p_bar) denominador <- m * (p1 - p0)^2 n_casos <- ceiling(numerador / denominador) n_controles <- n_casos * m n_total <- n_casos + n_controles # Imprimir resultados cat("\n========================================\n") cat("CASOS Y CONTROLES - NO PAREADO\n") cat("========================================\n\n") cat("PARÁMETROS:\n") cat(sprintf(" p0 (exposición en controles): %.1f%%\n", p0*100)) cat(sprintf(" p1 (exposición en casos): %.1f%%\n", p1*100)) cat(sprintf(" Odds Ratio: %.2f\n", OR)) cat(sprintf(" Ratio controles:casos: %d:1\n", m)) cat(sprintf(" Alpha: %.2f (bilateral)\n", alpha)) cat(sprintf(" Poder: %.0f%%\n\n", power*100)) cat("RESULTADOS:\n") cat(sprintf(" Casos necesarios: %d\n", n_casos)) cat(sprintf(" Controles necesarios: %d\n", n_controles)) cat(sprintf(" MUESTRA TOTAL: %d\n\n", n_total)) return(list(n_casos = n_casos, n_controles = n_controles, n_total = n_total, p1 = p1)) } # Ejemplo: IBP y NAV resultado_nopareado <- calcular_muestra_cc_no_pareado( p0 = 0.40, # 40% de controles usan IBP OR = 2.25, # OR esperado m = 2, # 2 controles por caso alpha = 0.05, power = 0.80 ) # ============================================ # MÉTODO 2: Usando paquete epiR (MÁS PRECISO) # ============================================ # Para diseño NO pareado resultado_epir <- epi.ccsize( OR = 2.25, p0 = 0.40, n = NA, # Lo que queremos calcular power = 0.80, r = 2, # 2 controles por caso rho = 0, # 0 = no pareado design = 1, # 1 = no pareado sided.test = 2, # bilateral conf.level = 0.95 ) print(resultado_epir) # ============================================ # MÉTODO 3: Casos-Controles PAREADOS # ============================================ # Para pareado, usar epiR con rho y design diferentes resultado_pareado <- epi.ccsize( OR = 2.25, p0 = 0.40, n = NA, power = 0.80, r = 2, rho = 0.2, # Correlación intra-par (típicamente 0.1-0.3) design = 2, # 2 = pareado sided.test = 2, conf.level = 0.95 ) cat("\n========================================\n") cat("CASOS Y CONTROLES - PAREADO 1:2\n") cat("========================================\n") print(resultado_pareado) # ============================================ # ANÁLISIS DE SENSIBILIDAD # ============================================ # Evaluar diferentes escenarios de OR escenarios_or <- data.frame( OR = c(1.5, 2.0, 2.5, 3.0), p0 = 0.40, m = 2, power = 0.80 ) escenarios_or$n_casos <- sapply(1:nrow(escenarios_or), function(i) { res <- calcular_muestra_cc_no_pareado( p0 = escenarios_or$p0[i], OR = escenarios_or$OR[i], m = escenarios_or$m[i], power = escenarios_or$power[i] ) res$n_casos }) escenarios_or$n_total <- escenarios_or$n_casos * (1 + escenarios_or$m) cat("\n========================================\n") cat("ANÁLISIS DE SENSIBILIDAD - Diferentes OR\n") cat("========================================\n") print(escenarios_or) # Gráfico library(ggplot2) ggplot(escenarios_or, aes(x = OR, y = n_total)) + geom_line(color = "steelblue", size = 1.5) + geom_point(color = "red", size = 3) + geom_text(aes(label = n_total), vjust = -1) + theme_minimal() + labs(title = "Tamaño de muestra según OR esperado", subtitle = "Diseño casos-controles 1:2, p0=40%, poder=80%", x = "Odds Ratio", y = "Muestra total (casos + controles)")
📌 Decisiones prácticas:
- Pareado vs No pareado: Usar pareado si hay confusores fuertes conocidos (edad, sexo, severidad). Reduce muestra ~15-25%.
- Ratio controles:casos: 1:2 es óptimo. Más de 1:3 tiene poco beneficio.
- OR pequeños (1.5-2.0): Requieren muestras grandes. Considerar cohorte si desenlace frecuente.
- Verificar factibilidad: ¿Cuántos casos nuevos de NAV por mes? Si 3 casos/mes → 74 casos = 25 meses de reclutamiento
Análisis Estadístico Completo - Casos y Controles
1. Análisis Descriptivo - Tabla 1
Objetivo: Caracterizar casos y controles, identificar potenciales confusores
Variables a incluir:
- Demográficas: Edad, sexo
- Severidad: APACHE II, SOFA, Charlson
- Comorbilidades: Diabetes, HTA, EPOC, IRC
- Intervenciones: VM, vasopresores, antibióticos
- Exposición principal: IBP (ejemplo)
Estadística descriptiva:
Variables continuas:
- Normal: Media ± DE, comparar con t-test
- No normal: Mediana [Q1-Q3], comparar con Mann-Whitney U
Variables categóricas:
- Frecuencias: n (%)
- Comparar con: Chi² (n≥5 por celda) o Fisher exacto (n<5)
Si casos y controles difieren en variables (p<0.05), esas variables son potenciales confusores que deben incluirse en regresión multivariada.
2. Odds Ratio (OR) Crudo - Análisis Bivariado
Tabla 2×2 básica:
| Casos (NAV) | Controles (Sin NAV) | Total | |
|---|---|---|---|
| Expuestos (IBP) | a | b | a+b |
| No expuestos | c | d | c+d |
| Total | a+c | b+d | N |
OR = (a × d) / (b × c)
Intervalo de confianza 95%:
IC95% = exp(ln(OR) ± 1.96 × SE)
donde SE = √(1/a + 1/b + 1/c + 1/d)
Estudio NAV e IBP (1:2):
• 74 casos (NAV), 148 controles (sin NAV)
• Expuestos a IBP en casos: 52/74 (70.3%)
• Expuestos a IBP en controles: 59/148 (39.9%)
Tabla 2×2:
| | Casos | Controles |
| IBP+ | 52 | 59 |
| IBP- | 22 | 89 |
OR = (52 × 89) / (59 × 22) = 4628/1298 = 3.57
IC95% = [2.01 - 6.32] (calculado con R)
Interpretación: Los pacientes expuestos a IBP tienen 3.57 veces el odds de desarrollar NAV comparado con no expuestos.
📊 Interpretación del OR:
- OR = 1: Sin asociación
- OR > 1: Exposición aumenta odds del desenlace (factor de riesgo)
- OR < 1: Exposición disminuye odds (factor protector)
- IC95% no incluye 1: Estadísticamente significativo (p<0.05)
3. Análisis Estratificado - Mantel-Haenszel
Cuándo usar: Cuando hay un confusor categórico conocido (ej: edad <65 vs ≥65)
OR de Mantel-Haenszel (ORMH):
ORMH = Σ(aidi/ni) / Σ(bici/ni)
Donde i = cada estrato
Ajusta por la variable estratificadora
Test de homogeneidad (Breslow-Day):
- H₀: OR es igual en todos los estratos
- Si p>0.05 → ORMH es válido (sin interacción)
- Si p<0.05 → Hay interacción, reportar ORs por estrato
4. Análisis Pareado - McNemar y Regresión Condicional
A. Test de McNemar (pareamiento 1:1):
Tabla para datos pareados:
| Control | ||
|---|---|---|
| Caso | Expuesto | No expuesto |
| Expuesto | a | b |
| No expuesto | c | d |
ORMcNemar = b / c
Solo usa pares discordantes (b y c)
Pares concordantes (a y d) no aportan información
B. Regresión Logística Condicional (pareamiento 1:1 o 1:N):
- Método más general, válido para cualquier ratio
- Ajusta por confusores adicionales
- Usa función
clogit()en R (paquete survival)
5. Regresión Logística Multivariada - Análisis COMPLETO
Modelo:
logit(P) = ln(P/(1-P)) = β₀ + β₁X₁ + β₂X₂ + ... + βₚXₚ
Donde:
P = probabilidad de ser caso
X₁ = exposición principal (IBP)
X₂...Xₚ = confusores
OR = exp(β)
Estrategia de modelado (paso a paso):
- Análisis univariado: Cada variable vs desenlace
- Selección: Variables con p<0.20 + clínicamente importantes
- Modelo inicial: Exposición + todas las seleccionadas
- Backward elimination: Eliminar si p>0.10 (excepto exposición)
- Modelo final: Exposición (forzada) + confusores significativos
📊 Tabla de resultados ejemplo:
| Variable | OR crudo | OR ajustado | IC95% | p-valor |
|---|---|---|---|---|
| IBP (exposición) | 3.57 | 4.12 | 2.18 - 7.79 | <0.001 |
| Edad (por año) | 1.03 | 1.04 | 1.01 - 1.07 | 0.008 |
| APACHE II (por punto) | 1.12 | 1.09 | 1.03 - 1.15 | 0.003 |
| Días VM (por día) | 1.15 | 1.12 | 1.05 - 1.19 | 0.001 |
6. Evaluación del Modelo
A. Discriminación - Curva ROC:
- AUC (Area Under Curve):
- 0.5 = No discrimina (azar)
- 0.7-0.8 = Discriminación aceptable
- 0.8-0.9 = Excelente
- >0.9 = Sobresaliente (sospechar overfitting)
- Punto de corte óptimo: Índice de Youden (sensibilidad + especificidad - 1)
B. Calibración - Test de Hosmer-Lemeshow:
- H₀: El modelo está bien calibrado
- p>0.05 → Buen ajuste (NO rechazar H₀)
- p<0.05 → Mal ajuste (revisar modelo)
C. Bondad de ajuste:
- Pseudo-R²: McFadden, Nagelkerke (entre 0-1, mayor = mejor)
- AIC/BIC: Menor valor = mejor modelo (útil para comparar modelos)
7. Análisis de Interacciones
Cuándo explorar: Si sospechas que la asociación exposición-desenlace varía según otra variable (ej: edad, sexo)
Ejemplo:
Modelo con interacción:
glm(caso ~ ibp * edad_grupo + apache, family=binomial)Si término de interacción
ibp:edad_grupo tiene p<0.10:→ Reportar ORs estratificados por edad
→ Crear forest plot mostrando ORs en subgrupos
Tabla 1: Características de casos vs controles (con p-valores)
Tabla 2: ORs crudos (análisis bivariado todas las variables)
Tabla 3: Modelo de regresión logística multivariada (ORs ajustados)
Figura 1: Forest plot de ORs ajustados con IC95%
Figura 2: Curva ROC del modelo final con AUC
Suplemento: Test Hosmer-Lemeshow, análisis de sensibilidad
Todo incluido aquí. Expande cada sección para ver el código detallado.
⚡ Inicio Rápido (copia esto para empezar)
# CÓDIGO MÍNIMO - Casos y Controles library(tidyverse); library(gtsummary); library(pROC) datos <- read.csv(file.choose()) modelo <- glm(caso ~ ibp + edad + apache, data=datos, family="binomial") exp(cbind(OR=coef(modelo), confint(modelo))) roc(datos$caso, predict(modelo, type="response")) %>% plot()
📖 CÓDIGO COMPLETO POR SECCIONES
Haz clic en cada sección para expandir y ver TODO el código.
💡 TIP: Copia directamente a RStudio.
📦 SECCIÓN 1: Paquetes y Configuración (~40 líneas)
# ============================================================================ # CASOS Y CONTROLES - NAV e IBP en UCI # Código R Completo - Nivel Publicación # ============================================================================ paquetes <- c( "tidyverse", "readxl", "haven", "janitor", "gtsummary", "flextable", "tableone", "pROC", "ResourceSelection", # ROC y Hosmer-Lemeshow "ggplot2", "ggpubr", "ggsci" ) instalar_si_falta <- function(pkg) { if(!require(pkg, character.only=TRUE)) { install.packages(pkg, dependencies=TRUE) library(pkg, character.only=TRUE) } } invisible(lapply(paquetes, instalar_si_falta)) # Configuración global theme_set(theme_bw()) options(scipen=999) # Evitar notación científica cat("✓ Paquetes listos\n")
📂 SECCIÓN 2: Importación de Datos (~30 líneas)
# ============================================================================ # IMPORTACIÓN # ============================================================================ cat("Seleccione archivo de datos...\n") archivo <- file.choose() ext <- tolower(tools::file_ext(archivo)) datos <- switch(ext, "csv" = read.csv(archivo), "xlsx" = read_excel(archivo), "sav" = read_sav(archivo), "dta" = read_dta(archivo), stop("Formato no soportado") ) cat(sprintf("✓ %d filas × %d columnas\n", nrow(datos), ncol(datos))) glimpse(datos)
🧹 SECCIÓN 3: Limpieza y Preparación (~50 líneas)
# ============================================================================ # LIMPIEZA - Adaptar a tus variables # ============================================================================ datos <- datos %>% clean_names() %>% mutate( # DESENLACE (1=caso, 0=control) caso = factor(caso, levels=c(0,1), labels=c("Control","Case")), # EXPOSICIÓN PRINCIPAL ibp = factor(ibp, levels=c(0,1), labels=c("No PPI","PPI")), # CONFUSORES edad = as.numeric(edad), sexo = factor(sexo, levels=c("M","F")), apache_ii = as.numeric(apache_ii), sofa = as.numeric(sofa), dias_vm = as.numeric(dias_vm), # Variable numérica para modelo caso_num = as.numeric(caso) - 1 ) # Verificar missing cat("\nDatos faltantes:\n") print(colSums(is.na(datos))) cat("\n✓ Datos preparados\n")
📊 SECCIÓN 4: Tabla 1 - Características (~40 líneas)
# ============================================================================ # TABLA 1 - Casos vs Controles # ============================================================================ tabla1 <- datos %>% select(caso, edad, sexo, apache_ii, sofa, dias_vm, ibp, diabetes, hipertension, choque_septico) %>% tbl_summary( by = caso, statistic = list( all_continuous() ~ "{median} [{p25}, {p75}]", all_categorical() ~ "{n} ({p}%)" ), label = list( edad ~ "Age (years)", sexo ~ "Sex", apache_ii ~ "APACHE II", sofa ~ "SOFA", dias_vm ~ "Days on MV", ibp ~ "PPI exposure" ) ) %>% add_p(test = list( all_continuous() ~ "wilcox.test", all_categorical() ~ "chisq.test" )) %>% add_overall() %>% bold_labels() %>% bold_p(t = 0.05) print(tabla1) # Exportar a Word tabla1 %>% as_flex_table() %>% save_as_docx(path="Tabla1_CasosControles.docx") cat("✓ Tabla 1 exportada\n")
📈 SECCIÓN 5: OR Crudos (Bivariado) (~50 líneas)
# ============================================================================ # ORs CRUDOS - Análisis bivariado # ============================================================================ # Tabla 2x2 para exposición principal tabla_2x2 <- table(datos$ibp, datos$caso) print(tabla_2x2) # OR crudo con IC95% or_crudo <- epi.2by2(tabla_2x2, method="cohort.count") print(or_crudo) # Regresión univariada para TODAS las variables or_univariado <- datos %>% select(caso_num, ibp, edad, sexo, apache_ii, sofa, dias_vm, diabetes, hipertension) %>% tbl_uvregression( method = glm, y = caso_num, method.args = list(family = binomial), exponentiate = TRUE, pvalue_fun = function(x) style_pvalue(x, digits=3) ) %>% bold_p(t=0.05) %>% bold_labels() print(or_univariado) # Exportar or_univariado %>% as_flex_table() %>% save_as_docx(path="Tabla2_OR_Crudos.docx") cat("✓ ORs crudos calculados\n")
🔬 SECCIÓN 6: Regresión Logística Multivariada (~80 líneas)
# ============================================================================ # REGRESIÓN LOGÍSTICA MULTIVARIADA # ============================================================================ # Modelo multivariado modelo_multi <- glm( caso_num ~ ibp + edad + apache_ii + sofa + dias_vm, data = datos, family = binomial(link="logit") ) summary(modelo_multi) # Tabla de ORs ajustados tabla_multi <- modelo_multi %>% tbl_regression( exponentiate = TRUE, label = list( ibp ~ "PPI exposure", edad ~ "Age (per year)", apache_ii ~ "APACHE II (per point)", sofa ~ "SOFA (per point)", dias_vm ~ "Days on MV" ) ) %>% add_global_p() %>% bold_p(t=0.05) %>% bold_labels() print(tabla_multi) # Exportar tabla_multi %>% as_flex_table() %>% save_as_docx(path="Tabla3_OR_Ajustados.docx") # Pseudo R-cuadrado cat("\nPseudo R²:\n") r2_mcfadden <- 1 - (modelo_multi$deviance / modelo_multi$null.deviance) cat(sprintf("McFadden R² = %.3f\n", r2_mcfadden)) cat("\n✓ Regresión multivariada completa\n")
📉 SECCIÓN 7: Forest Plot (~50 líneas)
# ============================================================================ # FOREST PLOT - ORs ajustados # ============================================================================ # Extraer coeficientes coefs <- summary(modelo_multi)$coefficients ci <- confint(modelo_multi) forest_data <- data.frame( Variable = rownames(coefs)[-1], # Excluir intercept OR = exp(coefs[-1,1]), Lower = exp(ci[-1,1]), Upper = exp(ci[-1,2]), P = coefs[-1,4] ) # Gráfico forest_plot <- ggplot(forest_data, aes(x=OR, y=reorder(Variable,OR))) + geom_vline(xintercept=1, linetype="dashed", color="gray40", size=1) + geom_errorbarh(aes(xmin=Lower, xmax=Upper), height=0.3, size=1) + geom_point(size=4, color="#764ba2") + scale_x_log10(breaks=c(0.5,1,2,4,8)) + labs(x="Odds Ratio (log scale)", y="", title="Forest Plot - Adjusted ORs") + theme_minimal(base_size=13) + theme(panel.grid.minor=element_blank()) print(forest_plot) ggsave("Fig1_ForestPlot_OR.png", width=10, height=6, dpi=300) cat("✓ Forest plot guardado\n")
✅ SECCIÓN 8: Evaluación del Modelo (ROC + Hosmer-Lemeshow) (~60 líneas)
# ============================================================================ # EVALUACIÓN DEL MODELO # ============================================================================ # A. CURVA ROC predicciones <- predict(modelo_multi, type="response") roc_obj <- roc(datos$caso_num, predicciones) auc_value <- auc(roc_obj) cat(sprintf("\nAUC = %.3f\n", auc_value)) # Gráfico ROC roc_plot <- ggroc(roc_obj, color="#764ba2", size=1.5) + geom_abline(intercept=1, slope=1, linetype="dashed", color="gray50") + annotate("text", x=0.25, y=0.25, label=sprintf("AUC = %.3f", auc_value), size=6, color="#764ba2", fontface="bold") + labs(title="ROC Curve - Logistic Regression Model", x="1 - Specificity", y="Sensitivity") + theme_bw(base_size=13) print(roc_plot) ggsave("Fig2_ROC_Curve.png", width=8, height=7, dpi=300) # Punto de corte óptimo (Youden) coords_opt <- coords(roc_obj, "best", ret="threshold") cat(sprintf("Punto corte óptimo: %.3f\n", coords_opt)) # B. TEST HOSMER-LEMESHOW hl_test <- hoslem.test(datos$caso_num, predicciones, g=10) cat("\nTest Hosmer-Lemeshow:\n") print(hl_test) if(hl_test$p.value > 0.05) { cat("\n✓ Modelo bien calibrado (p > 0.05)\n") } else { cat("\n⚠ Posible problema de calibración (p < 0.05)\n") } cat("\n✓ Evaluación completa\n")
✅ 8 secciones expandibles con TODO el código (~380 líneas)
✅ Tabla 1, ORs crudos, ORs ajustados
✅ Forest plot + Curva ROC
✅ Test Hosmer-Lemeshow
✅ Exportación Word + PNG 300 DPI
✅ Nivel publicación internacional
⏱️ Análisis de Supervivencia
Definición: Conjunto de técnicas estadísticas para analizar datos de tiempo hasta que ocurre un evento de interés (muerte, complicación, alta, reingreso, etc.). Es fundamental en UCI donde el tiempo es crítico.
Características únicas del análisis de supervivencia:
- ✅ Maneja censura: Pacientes perdidos en seguimiento o sin evento al final del estudio
- ✅ Usa toda la información disponible: Incluso de pacientes censurados
- ✅ Estima funciones de supervivencia: S(t) = probabilidad de sobrevivir más allá del tiempo t
- ✅ Compara grupos a lo largo del tiempo: No solo al final del seguimiento
- ✅ Calcula hazard ratios (HR): Riesgo instantáneo de evento
Tiempo de origen: Momento cero bien definido (ingreso UCI, intubación, inicio tratamiento)
Evento: Debe estar claramente definido y ser observable
Censura: Observación incompleta del tiempo al evento (3 tipos: derecha, izquierda, intervalo)
Supuesto fundamental: Censura NO informativa (razones de censura independientes del riesgo del evento)
Ejemplo - UCI Colombia
Pregunta: ¿Cuál es la supervivencia a 90 días en pacientes con sepsis y choque séptico ingresados a UCI, y qué factores predicen la mortalidad?
Contexto: Colombia tiene alta mortalidad por sepsis en UCI (30-50% según datos locales). Necesitamos identificar subgrupos de alto riesgo y factores modificables.
Tiempo de origen: Ingreso a UCI con diagnóstico de sepsis/choque séptico
Evento: Muerte por cualquier causa
Censura: Alta vivo de UCI, traslado a otra institución, fin del periodo de seguimiento (90 días)
Protocolo de Estudio de Supervivencia
1. Diseño del estudio
Tipo: Estudio de cohorte prospectivo observacional para análisis de supervivencia
Duración: 12 meses de reclutamiento + 90 días de seguimiento por paciente
Centros: UCI médico-quirúrgica Hospital Universitario San Ignacio (Bogotá), UCI Fundación Valle del Lili (Cali)
2. Población y muestra
Población objetivo: Pacientes adultos con sepsis o choque séptico según Sepsis-3
Criterios de inclusión:
- Edad ≥18 años
- Ingreso a UCI en las últimas 24 horas
- Diagnóstico de sepsis: infección documentada o sospechada + SOFA ≥2 puntos
- Para choque séptico: además requiere vasopresores para mantener PAM ≥65 mmHg y lactato >2 mmol/L a pesar de resucitación adecuada
Criterios de exclusión:
- Limitación del esfuerzo terapéutico al ingreso
- Expectativa de vida <24h por otra condición (ej. cáncer terminal)
- Sepsis desarrollada después de >48h de estancia en UCI (sesgo de supervivencia)
- Traslado desde otra UCI con >24h de manejo previo
- Reingreso a UCI en la misma hospitalización (solo el primer ingreso cuenta)
Momento EXACTO del ingreso administrativo a UCI. NO el momento del diagnóstico de sepsis (que puede ser horas/días antes). Esto asegura que todos empiecen simultáneamente y evita el sesgo de tiempo inmortal.
Variables demográficas (al ingreso):
- Edad, sexo, peso, talla, IMC
- Procedencia: comunidad, piso hospitalario, urgencias, quirófano
- Tipo de admisión: médico, quirúrgico urgente, quirúrgico electivo
Severidad de enfermedad (primeras 24h UCI):
- APACHE II (rango 0-71)
- SOFA al ingreso y máximo SOFA en primeras 24h
- Tipo de sepsis: choque séptico (sí/no)
- Sitio de infección: pulmonar, abdominal, urinario, piel/partes blandas, SNC, bacteriemia primaria, otro
- Cultivos positivos (sí/no)
- Tipo de microorganismo: Gram+, Gram-, hongos, polimicrobiano
Intervenciones y soporte en UCI:
- Ventilación mecánica invasiva (sí/no, días)
- Vasopresores (sí/no, duración, dosis máxima norepinefrina)
- Terapia de reemplazo renal (sí/no, días)
- Transfusión de hemoderivados (sí/no, unidades)
- Antibiótico apropiado en primeras 1h (sí/no) - factor pronóstico clave
- Volumen de líquidos en primeras 6h (ml/kg)
- Lactato al ingreso y a las 6h (clearance de lactato)
Comorbilidades (Índice de Charlson):
- Diabetes mellitus, EPOC, ICC, IRC, hepatopatía, neoplasia activa, SIDA
- Inmunosupresión (quimioterapia, esteroides crónicos, trasplante)
- Índice de Charlson Age-Adjusted (0-37 puntos)
3. Desenlaces
Desenlace primario:
- Mortalidad por cualquier causa a 90 días desde el ingreso a UCI
- Verificación: revisión de historia clínica electrónica + contacto telefónico + verificación en Registraduría Nacional
Desenlaces secundarios:
- Mortalidad en UCI
- Mortalidad hospitalaria
- Mortalidad a 28 días
- Días libres de ventilación mecánica a 28 días
- Días libres de vasopresores a 28 días
- Días de estancia en UCI
- Días de estancia hospitalaria
4. Seguimiento
Protocolo de seguimiento:
- Durante hospitalización: Seguimiento diario en UCI, luego en piso hasta alta o muerte
- Post-alta: Contacto telefónico a 28, 60 y 90 días
- Pacientes perdidos: Verificación de estado vital en Registraduría Nacional del Estado Civil (sistema RUAF)
- Definición de pérdida de seguimiento: No contacto después de 3 intentos telefónicos + búsqueda en sistema de salud + RUAF negativo
• Censura a derecha: Paciente vivo al final del seguimiento (90 días) o perdido durante seguimiento
• Censura administrativa: Fin del periodo de estudio
• CRÍTICO: Documentar MOTIVO de censura (vivo al final, perdido, retiró consentimiento) ya que censura informativa viola supuestos del análisis
5. Consideraciones éticas
- Aprobación de Comité de Ética Institucional
- Consentimiento informado del paciente o representante legal
- Registro en ClinicalTrials.gov o plataforma equivalente
- Datos anonimizados según Ley 1581 de 2012 (Protección de Datos Colombia)
Cálculo del Tamaño de Muestra para Análisis de Supervivencia
En análisis de supervivencia, el tamaño de muestra se calcula NO en base a número total de pacientes, sino a número de EVENTOS (muertes) necesarios para tener poder estadístico suficiente.
Escenario 1: Estimación de curva de supervivencia (Kaplan-Meier)
Objetivo: Estimar supervivencia a 90 días con precisión adecuada
Regla práctica:
Se requieren mínimo 10 eventos por cada intervalo de tiempo que se desee estimar
Alternativa (más precisa):
n = (Zα/2 / E)² × S(t) × (1 - S(t))
Donde:
Zα/2 = 1.96 para α = 0.05
E = Error máximo aceptable (precisión deseada)
S(t) = Supervivencia esperada al tiempo t
Si esperamos S(90 días) = 0.70 (70% de supervivencia) y queremos precisión de ±5%:
n = (1.96 / 0.05)² × 0.70 × 0.30
n = (39.2)² × 0.21
n = 1536.64 × 0.21
n ≈ 323 pacientes
Con S(90d) = 0.70, esperamos 30% muertes = 323 × 0.30 = 97 eventos
Escenario 2: Comparación de 2 curvas de supervivencia (Log-rank test)
Objetivo: Comparar supervivencia entre choque séptico vs sepsis sin choque
Datos necesarios:
- S₁(t) = Supervivencia esperada grupo 1 (sepsis sin choque) = 0.80
- S₂(t) = Supervivencia esperada grupo 2 (choque séptico) = 0.60
- α = 0.05 (bilateral)
- Poder = 80% (β = 0.20)
- Ratio de asignación = 1:1
Fórmula de Schoenfeld (1981):
d = (Zα/2 + Zβ)² / (log(HR))²
Donde:
d = Número total de eventos necesarios
Zα/2 = 1.96
Zβ = 0.84
HR = S₂(t) / S₁(t) estimado
Paso 1: Estimar Hazard Ratio aproximado
HR ≈ -log(S₂) / -log(S₁)
HR ≈ -log(0.60) / -log(0.80)
HR ≈ 0.5108 / 0.2231
HR ≈ 2.29
Paso 2: Calcular eventos necesarios
d = (1.96 + 0.84)² / (log(2.29))²
d = (2.80)² / (0.828)²
d = 7.84 / 0.686
d ≈ 11.4 ≈ 12 eventos
Paso 3: Calcular tamaño de muestra total
Tasa de eventos promedio = (0.20 + 0.40) / 2 = 0.30
n_total = d / tasa_eventos
n_total = 12 / 0.30
n_total ≈ 40 pacientes (20 por grupo)
⚠️ IMPORTANTE: Este es el mínimo. Considerar:
• Pérdidas de seguimiento (agregar 10-15%): 40 × 1.15 = 46
• Ajuste por covariables: agregar 10-20% más
• Muestra recomendada: 50-60 pacientes
Escenario 3: Regresión de Cox multivariada
Objetivo: Identificar factores pronósticos independientes de mortalidad
Regla de "eventos por variable" (EPV):
Eventos necesarios = 10-15 × número de variables independientes
Si planeamos un modelo con 8 variables predictoras:
• Edad
• APACHE II
• Choque séptico (sí/no)
• Ventilación mecánica
• Sitio de infección
• Antibiótico apropiado <1h
• Clearance de lactato
• Charlson index
Eventos mínimos = 10 × 8 = 80 muertes
Recomendado = 15 × 8 = 120 muertes
Si mortalidad esperada = 35%:
n = 120 / 0.35 = 343 pacientes
Para un estudio robusto de supervivencia en sepsis UCI con:
• Estimación precisa de curvas
• Comparación entre subgrupos
• Modelo multivariado (8 variables)
• 10% pérdida de seguimiento
TAMAÑO DE MUESTRA OBJETIVO: 350-400 pacientes
Esto nos dará ~120-140 eventos (muertes) asumiendo mortalidad 35%
Análisis Estadístico Completo - Supervivencia
1. Análisis Descriptivo
Variables continuas:
- Si distribución normal: Media ± DE
- Si no normal: Mediana [Q1 - Q3]
- Prueba de normalidad: Shapiro-Wilk o Kolmogorov-Smirnov
Variables categóricas:
- Frecuencias absolutas y relativas n (%)
Tabla 1 característica: Comparación sobrevivientes vs no sobrevivientes
- Continuas: t-test o Mann-Whitney
- Categóricas: Chi-cuadrado o Fisher exacto
2. Estimación de función de supervivencia (Kaplan-Meier)
Método de Kaplan-Meier:
Ŝ(t) = ∏tᵢ≤t (1 - dᵢ/nᵢ)
Donde:
dᵢ = número de eventos en tiempo tᵢ
nᵢ = número en riesgo justo antes de tᵢ
Resultados a reportar:
- Supervivencia a 28, 60 y 90 días con IC95%
- Mediana de supervivencia (si <50% sobreviven) con IC95%
- Curvas de Kaplan-Meier estratificadas por variables de interés
- Tabla de número en riesgo en cada tiempo
Gráfico estándar:
- Curva(s) de supervivencia con IC95% (banda de confianza)
- Tabla de "número en riesgo" debajo del gráfico
- Marcas de censura (cruces pequeñas)
- Paleta de colores The Lancet
3. Comparación de curvas de supervivencia
Log-rank test (Mantel-Cox):
- Compara TODA la curva de supervivencia entre grupos
- H₀: Las funciones de supervivencia son iguales
- Asume hazards proporcionales
- Más poder cuando hazards son proporcionales y constantes
Pruebas alternativas:
- Breslow test (Wilcoxon generalizado): Da más peso a eventos tempranos
- Tarone-Ware test: Peso intermedio
- Usar cuando se violan hazards proporcionales o eventos concentrados al inicio
Interpretación:
Ejemplo de redacción:
"La supervivencia a 90 días fue significativamente menor en pacientes con choque séptico comparado con sepsis sin choque (60% vs 80%, log-rank p = 0.003)."
4. Regresión de Cox (Análisis Multivariado)
Modelo de riesgos proporcionales de Cox:
h(t|X) = h₀(t) × exp(β₁X₁ + β₂X₂ + ... + βₚXₚ)
h(t|X) = hazard en tiempo t dado covariables X
h₀(t) = hazard basal (no especificado - semiparamétrico)
exp(β) = Hazard Ratio (HR)
Estrategia de modelado:
- Paso 1 - Análisis univariado: Cox univariado para cada variable
- Paso 2 - Selección: Variables con p < 0.20 en univariado + variables clínicamente importantes
- Paso 3 - Modelo inicial: Todas las variables seleccionadas
- Paso 4 - Simplificación: Backward elimination (p > 0.10 salen) O mantener variables clave forzadas
- Paso 5 - Modelo final: Variables con p < 0.05 o clínicamente relevantes
Resultados a reportar:
- Hazard Ratios (HR) ajustados con IC95%
- Valores p
- Concordancia (C-index o Harrell's C) - análogo al AUC
- Forest plot de HRs
📊 Interpretación de Hazard Ratio:
- HR = 1: Sin efecto
- HR > 1: Mayor riesgo (factor de riesgo). Ej: HR = 2.5 → 150% mayor riesgo instantáneo de muerte
- HR < 1: Menor riesgo (factor protector). Ej: HR = 0.6 → 40% menor riesgo de muerte
- IC95% no incluye 1: Estadísticamente significativo
5. Evaluación de supuestos
A. Supuesto de RIESGOS PROPORCIONALES (CRÍTICO):
Significa que el HR entre grupos es CONSTANTE a lo largo del tiempo
Métodos de evaluación:
- 1. Gráfico log-log: log(-log(S(t))) vs log(t) debe ser paralelo entre grupos
- 2. Residuos de Schoenfeld: Gráfico de residuos vs tiempo (línea horizontal = supuesto cumplido)
- 3. Test de Schoenfeld: H₀: riesgos proporcionales. Si p > 0.05 → OK
- 4. Interacción con tiempo: Agregar término covariable×tiempo al modelo. Si p > 0.05 → OK
Si se VIOLA el supuesto:
- Opción 1: Estratificar por esa variable (ya no sale HR para ella)
- Opción 2: Modelo de Cox con tiempo-dependencia
- Opción 3: Modelos paramétricos (Weibull, log-normal)
- Opción 4: Reportar HRs por periodos de tiempo separados
B. Linealidad para variables continuas:
- Gráfico de residuos de martingala vs variable
- Si NO lineal: transformar (log, raíz cuadrada) o categorizar
C. Observaciones influyentes:
- Residuos de dfbeta (cambio en coeficientes al eliminar observación)
- Si |dfbeta| > 2/√n → Investigar
6. Análisis de subgrupos e interacciones
Preguntas de interés:
- ¿El efecto de choque séptico en mortalidad es diferente en jóvenes vs ancianos?
- ¿El efecto de antibiótico temprano varía según sitio de infección?
Evaluación estadística:
- Agregar término de interacción al modelo: variable1 × variable2
- Si p(interacción) < 0.10 → Reportar HRs estratificados
- Forest plots estratificados
Tabla 1: Características basales (sobrevivientes vs no sobrevivientes)
Figura 1: Curva Kaplan-Meier general (con IC95% y tabla de riesgo)
Figura 2: Curvas KM estratificadas por choque séptico (con log-rank p-value)
Tabla 2: Análisis univariado Cox (todas las variables)
Tabla 3: Modelo multivariado final Cox
Figura 3: Forest plot de HRs ajustados
Figura 4: Evaluación de riesgos proporcionales (residuos Schoenfeld)
Suplemento: Curvas KM adicionales, análisis de sensibilidad
Todo incluido aquí. Expande cada sección para ver el código detallado.
⚠️ INCLUYE: Preparación de variables de TIEMPO (fechas → días)
⚠️ ¿DEBO MODIFICAR EL CÓDIGO SEGÚN MIS DATOS?
SÍ - Pero es SIMPLE:
- Si tienes DÍAS directamente: Usa tal cual (ej: tiempo_dias)
- Si tienes FECHAS: Usa Sección 3 para calcular días
- Si evento = 1/0: No cambies nada
- Si evento = "Sí"/"No": Convierte a 1/0 (código incluido)
💡 El código TE GUÍA en cada paso
⚡ Inicio Rápido (si ya tienes tiempo en días)
# CÓDIGO MÍNIMO library(survival); library(survminer); library(tidyverse) datos <- read.csv(file.choose()) surv_obj <- Surv(datos$tiempo_dias, datos$evento) survfit(surv_obj ~ datos$grupo) %>% ggsurvplot(pval=TRUE, risk.table=TRUE) coxph(surv_obj ~ grupo + edad, data=datos) %>% summary()
📖 CÓDIGO COMPLETO POR SECCIONES
Incluye TODO: Preparación de tiempo, validación, análisis completo, figuras publicables
📦 SECCIÓN 1: Paquetes (~45 líneas)
# ============================================================================ # ANÁLISIS DE SUPERVIVENCIA COMPLETO # Nivel Publicación (JAMA, Lancet, NEJM) # ============================================================================ paquetes <- c( "tidyverse", "readxl", "haven", "janitor", "lubridate", "survival", "survminer", "gtsummary", "flextable", "ggplot2", "ggpubr", "ggsci" ) instalar <- function(pkg) { if(!require(pkg, character.only=TRUE)) { install.packages(pkg, dependencies=TRUE) library(pkg, character.only=TRUE) } } invisible(lapply(paquetes, instalar)) cat("✓ Paquetes listos\n")
📂 SECCIÓN 2: Importación (~30 líneas)
# ============================================================================ # IMPORTACIÓN # ============================================================================ cat("Seleccione archivo...\n") archivo <- file.choose() ext <- tolower(tools::file_ext(archivo)) datos <- switch(ext, "csv" = read.csv(archivo), "xlsx" = read_excel(archivo), "sav" = read_sav(archivo), "dta" = read_dta(archivo), stop("Formato no soportado") ) cat(sprintf("✓ %d × %d\n", nrow(datos), ncol(datos))) glimpse(datos)
⚠️ SECCIÓN 3: PREPARACIÓN DE TIEMPO (CRÍTICA - LEE ESTO) (~80 líneas)
# ============================================================================ # PREPARACIÓN DE VARIABLE DE TIEMPO - ADAPTAR A TUS DATOS # ============================================================================ # ┌─────────────────────────────────────────────────────────────────┐ # │ ESCENARIO 1: Ya tienes DÍAS (más común) │ # └─────────────────────────────────────────────────────────────────┘ # Si tu variable ya está en días (ej: 5, 12, 28) datos <- datos %>% mutate( tiempo_dias = as.numeric(tiempo_dias) # Asegurar que es numérico ) # ┌─────────────────────────────────────────────────────────────────┐ # │ ESCENARIO 2: Tienes FECHAS (calcular días) │ # └─────────────────────────────────────────────────────────────────┘ # Opción A: Fecha de ingreso + Fecha de evento datos <- datos %>% mutate( # Convertir a formato fecha fecha_ingreso = as.Date(fecha_ingreso, format="%Y-%m-%d"), fecha_evento = as.Date(fecha_evento, format="%Y-%m-%d"), # Calcular días tiempo_dias = as.numeric(fecha_evento - fecha_ingreso) ) # Opción B: Si fecha_evento = NA → censura datos <- datos %>% mutate( fecha_fin = if_else( is.na(fecha_evento), fecha_fin_seguimiento, # Fecha cuando terminó seguimiento fecha_evento ), tiempo_dias = as.numeric(fecha_fin - fecha_ingreso) ) # ┌─────────────────────────────────────────────────────────────────┐ # │ PREPARACIÓN DE VARIABLE EVENTO │ # └─────────────────────────────────────────────────────────────────┘ # Si ya está como 1/0 → perfecto datos <- datos %>% mutate(evento = as.numeric(evento)) # Si está como "Sí"/"No" o "Muerte"/"Vivo" → convertir datos <- datos %>% mutate( evento = case_when( muerte == "Sí" | muerte == "Muerte" | muerte == 1 ~ 1, muerte == "No" | muerte == "Vivo" | muerte == 0 ~ 0, TRUE ~ NA_real_ ) ) # ┌─────────────────────────────────────────────────────────────────┐ # │ VALIDACIÓN AUTOMÁTICA │ # └─────────────────────────────────────────────────────────────────┘ # 1. Verificar tiempos negativos (ERROR) negativos <- sum(datos$tiempo_dias < 0, na.rm=TRUE) if(negativos > 0) { cat(sprintf("❌ ERROR: %d tiempos negativos\n", negativos)) print(datos %>% filter(tiempo_dias < 0) %>% select(id, tiempo_dias)) } else { cat("✓ Sin tiempos negativos\n") } # 2. Verificar tiempos = 0 (advertencia) ceros <- sum(datos$tiempo_dias == 0, na.rm=TRUE) if(ceros > 0) { cat(sprintf("⚠ ADVERTENCIA: %d tiempos = 0 (evento día ingreso)\n", ceros)) } # 3. Verificar evento solo 0 o 1 eventos_raros <- sum(!datos$evento %in% c(0,1), na.rm=TRUE) if(eventos_raros > 0) { cat("❌ ERROR: Evento tiene valores diferentes de 0/1\n") print(table(datos$evento, useNA="always")) } # 4. Resumen de datos de supervivencia cat("\n📊 RESUMEN DATOS SUPERVIVENCIA:\n") cat(sprintf("N total: %d\n", nrow(datos))) cat(sprintf("Eventos: %d (%.1f%%)\n", sum(datos$evento==1), mean(datos$evento==1)*100)) cat(sprintf("Censurados: %d (%.1f%%)\n", sum(datos$evento==0), mean(datos$evento==0)*100)) cat(sprintf("Tiempo mediano: %.1f días [%.1f - %.1f]\n", median(datos$tiempo_dias), quantile(datos$tiempo_dias, 0.25), quantile(datos$tiempo_dias, 0.75))) cat("\n✓ Preparación de tiempo completa\n")
🧹 SECCIÓN 4: Limpieza de Otras Variables (~50 líneas)
# ============================================================================ # LIMPIEZA DE CONFUSORES # ============================================================================ datos <- datos %>% clean_names() %>% mutate( # Grupo / Exposición grupo = factor(grupo, levels=c(0,1), labels=c("Control","Treatment")), # Variables continuas edad = as.numeric(edad), apache_ii = as.numeric(apache_ii), sofa = as.numeric(sofa), # Variables categóricas sexo = factor(sexo, levels=c("M","F")), choque = factor(choque_septico) ) # Crear objeto de supervivencia surv_obj <- Surv(time = datos$tiempo_dias, event = datos$evento) cat("✓ Variables preparadas\n")
📊 SECCIÓN 5: Tabla 1 (~40 líneas)
# ============================================================================ # TABLA 1 - Características por grupo # ============================================================================ tabla1 <- datos %>% select(grupo, edad, sexo, apache_ii, sofa, choque, diabetes, tiempo_dias, evento) %>% tbl_summary( by = grupo, statistic = list( all_continuous() ~ "{median} [{p25}, {p75}]", all_categorical() ~ "{n} ({p}%)" ) ) %>% add_p() %>% add_overall() %>% bold_labels() print(tabla1) tabla1 %>% as_flex_table() %>% save_as_docx(path="Tabla1_Supervivencia.docx") cat("✓ Tabla 1 exportada\n")
📈 SECCIÓN 6: Kaplan-Meier (~80 líneas)
# ============================================================================ # KAPLAN-MEIER # ============================================================================ km_fit <- survfit(surv_obj ~ grupo, data=datos) # Gráfico publicable km_plot <- ggsurvplot( km_fit, data = datos, pval = TRUE, conf.int = TRUE, risk.table = TRUE, risk.table.height = 0.25, palette = c("#3182bd", "#e6550d"), xlab = "Time (days)", ylab = "Survival probability", legend.title = "Group", legend.labs = c("Control", "Treatment"), ggtheme = theme_bw(), font.main = 14, font.x = 12, font.y = 12 ) print(km_plot) ggsave("Fig1_KaplanMeier.png", plot=km_plot$plot, width=10, height=7, dpi=300) # Log-rank test logrank <- survdiff(surv_obj ~ grupo, data=datos) print(logrank) # Medianas de supervivencia cat("\nMedianas de supervivencia:\n") print(summary(km_fit)$table) cat("\n✓ Kaplan-Meier completo\n")
🔬 SECCIÓN 7: Regresión de Cox (~100 líneas)
# ============================================================================ # REGRESIÓN DE COX # ============================================================================ # Univariada cox_uni <- datos %>% select(grupo, edad, apache_ii, sofa, choque) %>% tbl_uvregression( method = coxph, y = surv_obj, exponentiate = TRUE ) %>% bold_p() print(cox_uni) # Multivariada cox_multi <- coxph( surv_obj ~ grupo + edad + apache_ii + sofa, data = datos ) summary(cox_multi) tabla_multi <- cox_multi %>% tbl_regression(exponentiate=TRUE) %>% bold_p() print(tabla_multi) # Exportar tabla_multi %>% as_flex_table() %>% save_as_docx(path="Tabla2_Cox.docx") cat("✓ Cox completo\n")
✅ SECCIÓN 8: Evaluación Supuestos (~40 líneas)
# ============================================================================ # EVALUACIÓN DE SUPUESTOS # ============================================================================ test_ph <- cox.zph(cox_multi) print(test_ph) if(all(test_ph$table[,"p"] > 0.05)) { cat("\n✓ Riesgos proporcionales CUMPLIDOS\n") } else { cat("\n⚠ Posible violación riesgos proporcionales\n") } png("Fig3_Schoenfeld.png", width=3000, height=2000, res=300) ggcoxzph(test_ph) dev.off() cat("✓ Evaluación completa\n")
✅ 8 secciones con TODO el código (~450 líneas)
✅ NUEVA: Sección preparación de TIEMPO
✅ Validación automática de datos
✅ Conversión fechas → días
✅ Detección de errores comunes
✅ Kaplan-Meier + Cox + Supuestos
✅ Nivel publicación internacional
Esencial cuando hay múltiples eventos mutuamente excluyentes (muerte, alta, traslado).
¿Qué son los Riesgos Competitivos?
Ocurren cuando un evento impide que ocurra otro. Típico en UCI:
• Muerte (impide el alta)
• Alta vivo (impide muerte en UCI)
• Traslado (impide seguimiento completo)
Ejemplo UCI
Pregunta: ¿Cuál es la incidencia de NAV a 28 días?
Problema: Algunos pacientes mueren antes de desarrollar NAV
Eventos competitivos:
- Evento de interés: NAV
- Evento competitivo: Muerte sin NAV
- Censura: Alta sin NAV ni muerte
⚠️ Problema con Kaplan-Meier tradicional
KM asume: Pacientes censurados eventualmente tendrían el evento si siguiéramos observándolos
ERROR en competitivos: Pacientes muertos NO pueden desarrollar NAV → KM SOBREESTIMA incidencia
Ejemplo numérico:
100 pacientes ventilados a 28 días:
- 20 desarrollan NAV
- 30 mueren SIN NAV (competitivo)
- 50 alta SIN NAV (censurados)
KM estima: ~35% incidencia NAV (INCORRECTO - trata muertes como censura)
Incidencia acumulada correcta: 20% (20/100) usando modelo de riesgos competitivos
📊 Cumulative Incidence Function (CIF)
Método correcto: Función de Incidencia Acumulada
CIF(t) = P(T ≤ t, evento = j)
Probabilidad de que el evento j ocurra antes del tiempo t,
en presencia de eventos competitivos
Diferencia clave:
- KM: P(sobrevivir más allá de t | NO ha tenido evento antes de t)
- CIF: P(tener el evento antes de t | puede tener cualquier evento)
💻 Código R - Riesgos Competitivos
# ============================================ # ANÁLISIS DE RIESGOS COMPETITIVOS # ============================================ library(tidyverse) library(cmprsk) # Competing risks library(survminer) library(ggplot2) # Preparar datos # Variable 'status' debe tener: # 0 = censurado # 1 = evento de interés (NAV) # 2 = evento competitivo (muerte) datos <- datos %>% mutate( status_cr = case_when( alta_sin_evento == 1 ~ 0, # Censurado nav == 1 ~ 1, # NAV (evento interés) muerte_sin_nav == 1 ~ 2 # Muerte (competitivo) ) ) # Calcular Cumulative Incidence Functions cif <- cuminc( ftime = datos$tiempo_dias, fstatus = datos$status_cr, group = datos$grupo, # Expuestos vs no expuestos cencode = 0 # Código para censurados ) # Ver resultados print(cif) # Test de Gray (equivalente a log-rank para competitivos) # H0: CIF son iguales entre grupos cat("\nTest de Gray:\n") print(cif$Tests) # Gráfico de CIF ggcompetingrisks( fit = cif, multiple_panels = FALSE, palette = "lancet", legend.title = "Event Type", xlab = "Time (days)", ylab = "Cumulative Incidence", title = "Competing Risks Analysis: VAP and Death" ) # Guardar figura ggsave("competing_risks_CIF.png", width = 10, height = 6, dpi = 300) # ============================================ # REGRESIÓN PARA RIESGOS COMPETITIVOS # (Fine-Gray model) # ============================================ # Modelo para evento de interés (NAV) modelo_fg <- crr( ftime = datos$tiempo_dias, fstatus = datos$status_cr, cov1 = datos[,c("grupo", "edad", "apache_ii")], failcode = 1, # 1 = NAV cencode = 0 ) summary(modelo_fg) # Sub-Hazard Ratios (SHR) exp(coef(modelo_fg)) exp(confint(modelo_fg))
📈 Interpretación de Sub-Hazard Ratio (SHR)
SHR > 1: Mayor incidencia acumulada del evento de interés
SHR < 1: Menor incidencia acumulada del evento de interés
Diferencia con HR de Cox: SHR considera la presencia de eventos competitivos
• Mortalidad impide otros eventos
• Alta impide eventos intrahospitalarios
• Traslado termina seguimiento
• Cualquier evento que haga imposible el evento de interés
Usar Kaplan-Meier cuando hay competitivos → SOBREESTIMA incidencia del evento de interés
📝 Cómo reportar en manuscrito:
"Dado que la muerte representa un riesgo competitivo para el desarrollo de NAV, estimamos la incidencia acumulada mediante el método de Cumulative Incidence Function en lugar de Kaplan-Meier. A 28 días, la incidencia acumulada de NAV fue 18.5% (IC95% 14.2-23.1%) en el grupo expuesto vs 12.3% (IC95% 8.9-16.2%) en el grupo control (test de Gray, p = 0.041). En el modelo de regresión Fine-Gray ajustado por edad y APACHE II, la exposición se asoció con mayor incidencia de NAV (SHR = 1.68, IC95% 1.12-2.52, p = 0.012)."
• Fine & Gray (1999). "A proportional hazards model for subdistribution" JASA
• Austin et al. (2016). "Practical recommendations for reporting Fine-Gray model" Stat Med
• Lau et al. (2009). "Competing risk regression models" Circulation
🔬 Estudios de Precisión Diagnóstica
Definición: Evalúan la capacidad de un test (prueba, escala, biomarcador) para clasificar correctamente a pacientes como enfermos o sanos, comparándolo con un gold standard.
Conceptos Fundamentales
Es el mejor método diagnóstico disponible, considerado la "verdad" para determinar si el paciente tiene o no la enfermedad.
Ejemplos UCI:
• NAV: Cultivo cuantitativo broncoalveolar (≥10⁴ UFC/mL)
• Sepsis: Hemocultivos positivos + criterios clínicos
• TEP: Angio-TAC pulmonar
• IAM: Troponinas + ECG + coronariografía
Es la prueba que queremos validar. Debe ser más rápida, barata o menos invasiva que el gold standard.
Ejemplos UCI:
• Procalcitonina para sepsis bacteriana
• SOFA score para mortalidad
• Lactato para choque séptico
• CPIS para NAV
• Dímero-D para TEP
Tabla 2×2 - La Base del Análisis
| Test Índice | Gold Standard | Total | |
|---|---|---|---|
| Enfermo (D+) | Sano (D-) | ||
| Positivo (T+) | a (VP) |
b (FP) |
a+b |
| Negativo (T-) | c (FN) |
d (VN) |
c+d |
| Total | a+c | b+d | N = a+b+c+d |
• VP (a): Verdaderos Positivos - test + y enfermo
• FP (b): Falsos Positivos - test + pero sano
• FN (c): Falsos Negativos - test - pero enfermo
• VN (d): Verdaderos Negativos - test - y sano
Medidas de Precisión Diagnóstica
1. Sensibilidad (Sensitivity)
Sensibilidad = a / (a + c) = VP / Enfermos
Interpretación: Proporción de enfermos que el test detecta correctamente (positivos verdaderos).
Pregunta clave: ¿Qué tan bueno es el test para NO perder enfermos?
• 80 pacientes murieron
• SOFA ≥6 en 68 de ellos
• Sensibilidad = 68/80 = 0.85 (85%)
Interpretación: El SOFA ≥6 detecta correctamente al 85% de los que morirán.
2. Especificidad (Specificity)
Especificidad = d / (b + d) = VN / Sanos
Interpretación: Proporción de sanos que el test clasifica correctamente como negativos.
Pregunta clave: ¿Qué tan bueno es el test para NO alarmar falsamente?
• 120 pacientes sobrevivieron
• SOFA <6 en 90 de ellos
• Especificidad = 90/120 = 0.75 (75%)
Interpretación: El SOFA <6 clasifica correctamente como "sobrevivirán" al 75% de los sobrevivientes.
3. Valores Predictivos (dependen de PREVALENCIA)
VPP (Valor Predictivo Positivo):
VPP = a / (a + b) = VP / Test Positivos
Si el test es positivo, ¿cuál es la probabilidad de que REALMENTE esté enfermo?
VPN (Valor Predictivo Negativo):
VPN = d / (c + d) = VN / Test Negativos
Si el test es negativo, ¿cuál es la probabilidad de que REALMENTE esté sano?
VPP y VPN dependen de la prevalencia de la enfermedad en la población.
Sensibilidad y Especificidad son intrínsecas al test (no cambian con prevalencia).
4. Likelihood Ratios (Razones de Verosimilitud)
LR+ (Likelihood Ratio Positivo):
LR+ = Sensibilidad / (1 - Especificidad)
¿Cuántas veces es más probable un test + en enfermos vs sanos?
LR- (Likelihood Ratio Negativo):
LR- = (1 - Sensibilidad) / Especificidad
¿Cuántas veces es más probable un test - en enfermos vs sanos?
Interpretación LR:
LR+ (mayor es mejor):
- >10: Muy útil para confirmar enfermedad
- 5-10: Moderadamente útil
- 2-5: Poco útil
- <2: No útil
LR- (menor es mejor):
- <0.1: Muy útil para descartar enfermedad
- 0.1-0.2: Moderadamente útil
- 0.2-0.5: Poco útil
- >0.5: No útil
Para screening (descartar): Alta Sensibilidad y LR- bajo
Para confirmar diagnóstico: Alta Especificidad y LR+ alto
Para decisiones clínicas: VPP y VPN (consideran prevalencia)
Para cambiar probabilidad: Likelihood Ratios
Cálculo de Tamaño Muestral - Precisión Diagnóstica
Método 1: Basado en Intervalo de Confianza deseado
Fórmula para Sensibilidad o Especificidad:
n = Z² × p × (1-p) / d²
Donde:
- Z: Valor Z para IC deseado (1.96 para IC95%)
- p: Sensibilidad/Especificidad esperada (de literatura)
- d: Amplitud deseada del IC (precisión)
Ejemplo 1: Validar SOFA para mortalidad
Datos:
- Sensibilidad esperada: 80% (p = 0.80)
- IC95% deseado: ±10% (d = 0.10)
- Z = 1.96
Cálculo:
n = (1.96)² × 0.80 × 0.20 / (0.10)² = 61.5 ≈ 62 pacientes CON la enfermedad
Interpretación:
Necesito 62 pacientes que murieron para estimar sensibilidad con IC95% de amplitud ±10%.
Si prevalencia de mortalidad es 40%, necesito:
N total = 62 / 0.40 = 155 pacientes totales
Ejemplo 2: Procalcitonina para sepsis bacteriana
Datos:
- Especificidad esperada: 85% (p = 0.85)
- IC95% deseado: ±8% (d = 0.08)
- Prevalencia sepsis bacteriana: 30%
Cálculo para especificidad:
n = (1.96)² × 0.85 × 0.15 / (0.08)² = 76 pacientes SIN sepsis bacteriana
N total:
N = 76 / 0.70 = 109 pacientes totales
Método 2: Basado en Poder Estadístico (Comparar 2 tests)
Si quieres comparar dos tests diagnósticos:
Usa test de McNemar para datos pareados.
Fórmula aproximada:
n = (Zα/2 + Zβ)² / (p₁ - p₂)²
Para detectar diferencia de 10% en sensibilidad con poder 80%:
n ≈ 100 pacientes con enfermedad
• Mínimo absoluto: 50 enfermos + 50 sanos
• Estudio robusto: 100 enfermos + 100 sanos
• Prevalencia baja (<10%): Aumentar N total
• Considerar pérdidas: Agregar 10-20% al cálculo
📊 Tabla de Referencia Rápida
| Sensibilidad/Especificidad esperada | Precisión deseada (±) | N con enfermedad/sin enfermedad |
|---|---|---|
| 50% | ±10% | 96 |
| 70% | ±10% | 81 |
| 80% | ±10% | 62 |
| 90% | ±10% | 35 |
| 80% | ±5% | 246 |
Análisis Estadístico Completo - Precisión Diagnóstica
1. Análisis Descriptivo
Tabla 1: Características de la población
- Demográficas: edad, sexo
- Clínicas: severidad, comorbilidades
- Comparar enfermos vs sanos
2. Tabla 2×2 y Medidas de Precisión
Calcular para cada punto de corte del test:
- Sensibilidad con IC95%
- Especificidad con IC95%
- VPP y VPN
- LR+ y LR-
- Accuracy (exactitud global)
Punto de corte SOFA ≥6:
| Muerte | Vivo | |
|---|---|---|
| SOFA ≥6 | 68 | 30 |
| SOFA <6 | 12 | 90 |
• Especificidad = 90/120 = 75.0% [IC95%: 66.4-82.4%]
• VPP = 68/98 = 69.4%
• VPN = 90/102 = 88.2%
• LR+ = 0.85/0.25 = 3.4
• LR- = 0.15/0.75 = 0.20
• Accuracy = (68+90)/200 = 79.0%
3. Curva ROC y Área Bajo la Curva (AUC)
Gráfico de Sensibilidad (eje Y) vs 1-Especificidad (eje X) para TODOS los puntos de corte posibles del test.
AUC (Area Under the Curve):
Probabilidad de que el test asigne un valor mayor a un enfermo aleatorio que a un sano aleatorio.
Interpretación:
- 0.5: No discrimina (azar)
- 0.6-0.7: Discriminación pobre
- 0.7-0.8: Discriminación aceptable
- 0.8-0.9: Discriminación excelente
- 0.9-1.0: Discriminación sobresaliente
Selección del punto de corte óptimo:
Método 1: Índice de Youden
J = Sensibilidad + Especificidad - 1
Maximiza la distancia a la diagonal (máxima combinación Sens+Espec)
Método 2: Según objetivo clínico
- Screening: Priorizar Sensibilidad ≥90%
- Confirmación: Priorizar Especificidad ≥90%
- Triage: Balancear ambas (Youden)
4. Comparación de Curvas ROC
Para comparar 2 o más tests:
- Test de DeLong: Compara AUCs de tests aplicados a los mismos pacientes
- H₀: AUC₁ = AUC₂
- p<0.05: Los tests tienen diferente capacidad discriminativa
• AUC SOFA = 0.82 [IC95%: 0.76-0.88]
• AUC APACHE II = 0.79 [IC95%: 0.73-0.85]
• Test DeLong: p = 0.18
Conclusión: No hay diferencia estadísticamente significativa entre SOFA y APACHE II (p>0.05)
5. Calibración del Test
Pregunta: ¿Las probabilidades predichas por el test corresponden a las observadas?
Gráfico de calibración:
• Eje X: Probabilidad predicha (deciles)
• Eje Y: Proporción observada con evento
• Línea diagonal = calibración perfecta
Test de Hosmer-Lemeshow:
- H₀: El test está bien calibrado
- p>0.05 → Buena calibración
- p<0.05 → Mala calibración (revisar modelo)
Tabla 1: Características de enfermos vs sanos
Tabla 2: Tabla 2×2 con todos los puntos de corte evaluados
Tabla 3: Sensibilidad, Especificidad, VPP, VPN, LR con IC95%
Figura 1: Curva ROC con AUC e IC95%
Figura 2: Comparación de múltiples curvas ROC
Figura 3: Gráfico de calibración
Suplemento: Análisis de subgrupos, sensibilidad en diferentes prevalencias
Todo incluido. Expand cada sección para ver el código.
⚡ Inicio Rápido
# CÓDIGO MÍNIMO library(pROC); library(epiR); library(tidyverse) datos <- read.csv(file.choose()) table(datos$test, datos$enfermedad) # Tabla 2x2 roc_obj <- roc(datos$enfermedad, datos$test_score) plot(roc_obj); auc(roc_obj)
📖 CÓDIGO COMPLETO POR SECCIONES
Haz clic en cada sección para expandir.
📦 SECCIÓN 1: Paquetes (~30 líneas)
# ============================================================================ # PRECISIÓN DIAGNÓSTICA - Código R Completo # ============================================================================ paquetes <- c( "tidyverse", "readxl", "haven", "pROC", # Curvas ROC "epiR", # Sensibilidad, Especificidad, LR "gtsummary", # Tablas "flextable", # Exportar tablas a Word "ResourceSelection", # Test Hosmer-Lemeshow "ggplot2", "ggsci" ) instalar <- function(pkg) { if(!require(pkg, character.only=TRUE)) { install.packages(pkg, dependencies=TRUE) library(pkg, character.only=TRUE) } } invisible(lapply(paquetes, instalar)) cat("✓ Paquetes listos\n")
📂 SECCIÓN 2: Importación (~20 líneas)
# ============================================================================ # IMPORTACIÓN # ============================================================================ archivo <- file.choose() ext <- tolower(tools::file_ext(archivo)) datos <- switch(ext, "csv" = read.csv(archivo), "xlsx" = read_excel(archivo), "sav" = read_sav(archivo), stop("Formato no soportado") ) glimpse(datos)
🧹 SECCIÓN 3: Preparación (~40 líneas)
# ============================================================================ # PREPARACIÓN - Adaptar a tus variables # ============================================================================ datos <- datos %>% mutate( # GOLD STANDARD (1=enfermo, 0=sano) enfermedad = as.numeric(gold_standard), # TEST ÍNDICE (score numérico o binario) test_score = as.numeric(sofa_score), test_binario = if_else(sofa_score >= 6, 1, 0), # Variables descriptivas edad = as.numeric(edad), sexo = factor(sexo) ) # Verificar datos cat("\nEnfermos:", sum(datos$enfermedad==1), "\n") cat("Sanos:", sum(datos$enfermedad==0), "\n")
📊 SECCIÓN 4: Tabla 2×2 y Medidas (~60 líneas)
# ============================================================================ # TABLA 2x2 Y MEDIDAS DE PRECISIÓN # ============================================================================ # Tabla 2x2 tabla_2x2 <- table(Test=datos$test_binario, Enfermedad=datos$enfermedad) print(tabla_2x2) # Medidas con epiR # IMPORTANTE: epiR espera formato [enfermo,sano] en columnas tab_epi <- matrix(c( tabla_2x2[2,2], tabla_2x2[2,1], # Test+ : enfermos, sanos tabla_2x2[1,2], tabla_2x2[1,1] # Test- : enfermos, sanos ), nrow=2, byrow=TRUE) medidas <- epi.tests(tab_epi, conf.level=0.95) print(medidas) # Extraer valores sens <- medidas$detail$se spec <- medidas$detail$sp vpp <- medidas$detail$pv.pos vpn <- medidas$detail$pv.neg lr_pos <- medidas$detail$lr.pos lr_neg <- medidas$detail$lr.neg cat("\n=== RESUMEN MEDIDAS ===\n") cat(sprintf("Sensibilidad: %.1f%% [%.1f-%.1f]\n", sens$est*100, sens$lower*100, sens$upper*100)) cat(sprintf("Especificidad: %.1f%% [%.1f-%.1f]\n", spec$est*100, spec$lower*100, spec$upper*100)) cat(sprintf("LR+: %.2f [%.2f-%.2f]\n", lr_pos$est, lr_pos$lower, lr_pos$upper)) cat(sprintf("LR-: %.2f [%.2f-%.2f]\n", lr_neg$est, lr_neg$lower, lr_neg$upper))
📈 SECCIÓN 5: Curva ROC (~80 líneas)
# ============================================================================ # CURVA ROC # ============================================================================ # Crear objeto ROC roc_obj <- roc(datos$enfermedad, datos$test_score, levels=c(0,1), direction="<") # AUC auc_value <- auc(roc_obj) ci_auc <- ci.auc(roc_obj) cat(sprintf("\nAUC = %.3f [%.3f - %.3f]\n", auc_value, ci_auc[1], ci_auc[3])) # Punto de corte óptimo (Youden) coords_opt <- coords(roc_obj, "best", ret=c("threshold","sensitivity","specificity"), best.method="youden") cat("\nPunto de corte óptimo (Youden):\n") print(coords_opt) # Gráfico ROC png("Fig1_ROC.png", width=2400, height=2000, res=300) ggroc(roc_obj, color="#e74c3c", size=1.5) + geom_abline(intercept=1, slope=1, linetype="dashed", color="gray50") + annotate("text", x=0.25, y=0.25, label=sprintf("AUC = %.3f\n[%.3f - %.3f]", auc_value, ci_auc[1], ci_auc[3]), size=5, hjust=0) + labs(title="ROC Curve", x="1 - Specificity", y="Sensitivity") + theme_bw(base_size=13) dev.off() cat("✓ Curva ROC guardada\n")
🔀 SECCIÓN 6: Comparar Múltiples Tests (~50 líneas)
# ============================================================================ # COMPARAR 2 O MÁS TESTS # ============================================================================ # Ejemplo: SOFA vs APACHE II para mortalidad roc_sofa <- roc(datos$enfermedad, datos$sofa_score) roc_apache <- roc(datos$enfermedad, datos$apache_score) # Test de DeLong (compara AUCs) test_delong <- roc.test(roc_sofa, roc_apache) print(test_delong) if(test_delong$p.value < 0.05) { cat("\n✓ Diferencia significativa (p<0.05)\n") } else { cat("\n→ Sin diferencia significativa (p≥0.05)\n") } # Gráfico comparativo png("Fig2_ROC_Comparacion.png", width=2400, height=2000, res=300) ggroc(list(SOFA=roc_sofa, APACHE=roc_apache), size=1.5) + geom_abline(intercept=1, slope=1, linetype="dashed", color="gray50") + scale_color_manual(values=c("#e74c3c", "#3498db")) + labs(title="Comparison of ROC Curves") + theme_bw(base_size=13) dev.off() cat("✓ Comparación guardada\n")
📊 SECCIÓN 7: Tabla 1 - Características Enfermos vs Sanos (~50 líneas)
# ============================================================================ # TABLA 1: CARACTERÍSTICAS BASALES # ============================================================================ # Crear Tabla 1 comparando enfermos vs sanos tabla1 <- datos %>% select(enfermedad, edad, sexo, comorbilidades, apache_ii, sofa_score, lactato, procalcitonina) %>% tbl_summary( by = enfermedad, statistic = list( all_continuous() ~ "{mean} ({sd})", all_categorical() ~ "{n} ({p}%)" ), digits = all_continuous() ~ 1, label = list( edad ~ "Edad (años)", sexo ~ "Sexo masculino", comorbilidades ~ "Comorbilidades", apache_ii ~ "APACHE II", sofa_score ~ "SOFA", lactato ~ "Lactato (mmol/L)", procalcitonina ~ "Procalcitonina (ng/mL)" ), missing_text = "Perdidos" ) %>% add_p( test = list( all_continuous() ~ "t.test", all_categorical() ~ "chisq.test" ) ) %>% add_overall() %>% modify_header(label ~ "**Característica**") %>% modify_spanning_header(c("stat_1", "stat_2") ~ "**Mortalidad**") # Mostrar tabla print(tabla1) # Exportar a Word (opcional) tabla1 %>% as_flex_table() %>% save_as_docx(path = "Tabla1_Caracteristicas.docx") cat("✓ Tabla 1 creada y exportada\n")
🎯 SECCIÓN 8: Calibración del Test (~60 líneas)
# ============================================================================ # CALIBRACIÓN DEL TEST # ============================================================================ # Nota: Para calibración necesitas un modelo de probabilidades predichas # Ejemplo usando regresión logística con el score # 1. Crear modelo para obtener probabilidades modelo_logit <- glm(enfermedad ~ test_score, data = datos, family = "binomial") # 2. Obtener probabilidades predichas datos$prob_predicha <- predict(modelo_logit, type = "response") # 3. Crear deciles de probabilidad datos$decil <- cut(datos$prob_predicha, breaks = quantile(datos$prob_predicha, probs = seq(0, 1, 0.1)), include.lowest = TRUE, labels = 1:10) # 4. Calcular proporción observada por decil calibracion_data <- datos %>% group_by(decil) %>% summarise( prob_predicha_media = mean(prob_predicha, na.rm = TRUE), prop_observada = mean(enfermedad, na.rm = TRUE), n = n() ) # 5. Test de Hosmer-Lemeshow # Installar paquete si es necesario if(!require(ResourceSelection)) { install.packages("ResourceSelection", dependencies = TRUE) library(ResourceSelection) } hl_test <- hoslem.test(datos$enfermedad, datos$prob_predicha, g = 10) print(hl_test) if(hl_test$p.value > 0.05) { cat("\n✓ Buena calibración (p>0.05)\n") } else { cat("\n⚠ Mala calibración (p<0.05) - Revisar modelo\n") } # 6. Gráfico de calibración png("Fig3_Calibracion.png", width=2400, height=2000, res=300) ggplot(calibracion_data, aes(x = prob_predicha_media, y = prop_observada)) + geom_point(size = 4, color = "#e74c3c") + geom_line(color = "#e74c3c", size = 1) + geom_abline(intercept = 0, slope = 1, linetype = "dashed", color = "gray50", size = 1) + annotate("text", x = 0.2, y = 0.8, label = sprintf("Hosmer-Lemeshow\np = %.3f", hl_test$p.value), size = 5, hjust = 0) + labs(title = "Calibration Plot", x = "Predicted Probability", y = "Observed Proportion") + xlim(0, 1) + ylim(0, 1) + theme_bw(base_size = 13) + theme(aspect.ratio = 1) dev.off() cat("✓ Gráfico de calibración guardado\n")
✅ 8 secciones con ~410 líneas
✅ Tabla 1 enfermos vs sanos
✅ Tabla 2×2 + todas las medidas
✅ Curvas ROC con AUC
✅ Comparación de tests (Test DeLong)
✅ Calibración + Hosmer-Lemeshow
✅ 3 Gráficos PNG 300 DPI
✅ Nivel publicación internacional
📚 Guía STROBE - Cómo Reportar Estudios Observacionales
STROBE (Strengthening the Reporting of Observational Studies in Epidemiology) es el estándar internacional para reportar estudios observacionales.
• Exigido por revistas científicas de alto impacto
• Asegura que reportes toda la información necesaria
• Facilita la evaluación crítica de tu estudio
• Mejora la transparencia y reproducibilidad
Checklist STROBE - 22 ítems esenciales
📝 TÍTULO Y RESUMEN
Ítem 1: Indica el diseño del estudio en título o resumen (ej. "estudio de cohorte", "casos y controles")
🎯 INTRODUCCIÓN
Ítem 2: Contexto y justificación científica
Ítem 3: Objetivos específicos e hipótesis
🔬 MÉTODOS
Ítem 4: Diseño del estudio (momento y lugar)
Ítem 5: Contexto y período del estudio
Ítem 6: Criterios de elegibilidad, fuentes y métodos de selección
Ítem 7: Variables (exposición, desenlace, predictores, confusores)
Ítem 8: Fuentes de datos y métodos de medición
Ítem 9: Esfuerzos para minimizar sesgos
Ítem 10: Cómo se determinó el tamaño de muestra
Ítem 11: Manejo de variables cuantitativas
Ítem 12: Métodos estadísticos (descriptivos, bivariados, multivariados)
📊 RESULTADOS
Ítem 13: Número de participantes en cada etapa
Ítem 14: Características de los participantes
Ítem 15: Datos de seguimiento (si aplica)
Ítem 16: Número de eventos/desenlaces
Ítem 17: Estimaciones crudas y ajustadas con IC95%
Ítem 18: Análisis adicionales (subgrupos, sensibilidad)
💭 DISCUSIÓN
Ítem 19: Resumen de hallazgos principales
Ítem 20: Limitaciones del estudio
Ítem 21: Generalización de resultados
Ítem 22: Interpretación general considerando objetivos, limitaciones y evidencia previa
Visita la página oficial para descargar los checklists completos en diferentes formatos (PDF, Word):
🌐 Ir a STROBE Checklists Oficiales✅ Disponibles: Cohorte • Casos-Controles • Transversal (PDF y Word)