1 ¿Qué es R?

R es un lenguaje de programación y un entorno de trabajo orientado a:

1.1 Ventajas de R

  • Gratuito y de código abierto: disponible sin costo y con desarrollo comunitario.
  • Altamente extensible: dispone de miles de paquetes especializados (epidemiología, bioestadística, visualización, aprendizaje automático, etc.).
  • Comunidad activa: existe abundante documentación, cursos en línea, foros y ejemplos reproducibles.

1.2 Usos principales en ciencias de la salud

  1. Análisis de datos clínicos y epidemiológicos.
  2. Estadística descriptiva e inferencial.
  3. Visualización de datos (gráficos de barras, líneas, boxplots, mapas, etc.).
  4. Modelado estadístico (regresión, modelos de supervivencia, series de tiempo).
  5. Ciencia de datos y aprendizaje automático aplicado a salud pública y biomedicina.

2 Primeros pasos prácticos en R

2.1 Ejemplo básico en la consola de R

# Operaciones aritméticas simples
2 + 2
## [1] 4
5 * 3
## [1] 15
10 / 4
## [1] 2.5
# Asignación de objetos
x <- 10
y <- 3
x + y
## [1] 13
x^2
## [1] 100

2.2 Vectores y operaciones elementales

# Crear un vector numérico
edades <- c(23, 35, 41, 29, 50, 60)

# Resumen rápido
summary(edades)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   23.00   30.50   38.00   39.67   47.75   60.00
# Operaciones sobre vectores
edades + 1            # agrega 1 a cada edad
## [1] 24 36 42 30 51 61
edades[edades >= 40]  # filtra edades ≥ 40
## [1] 41 50 60

3 Data frames, tablas y resúmenes

3.1 Creación de un pequeño conjunto de datos

# Crear un data frame sencillo
pacientes <- data.frame(
  id         = 1:6,
  sexo       = c("Mujer", "Hombre", "Mujer", "Hombre", "Mujer", "Hombre"),
  edad       = c(23, 35, 41, 29, 50, 60),
  hipertenso = c(0, 1, 1, 0, 1, 1)  # 0 = No, 1 = Sí
)

pacientes
##   id   sexo edad hipertenso
## 1  1  Mujer   23          0
## 2  2 Hombre   35          1
## 3  3  Mujer   41          1
## 4  4 Hombre   29          0
## 5  5  Mujer   50          1
## 6  6 Hombre   60          1

3.2 Mostrar tabla en formato presentable

knitr::kable(
  pacientes,
  caption = "Ejemplo de base de datos clínica mínima (pacientes simulados)."
)
Ejemplo de base de datos clínica mínima (pacientes simulados).
id sexo edad hipertenso
1 Mujer 23 0
2 Hombre 35 1
3 Mujer 41 1
4 Hombre 29 0
5 Mujer 50 1
6 Hombre 60 1

3.3 Resumen rápido por sexo

library(dplyr)

pacientes_resumen <- pacientes |>
  group_by(sexo) |>
  summarise(
    n         = n(),
    edad_prom = mean(edad),
    prop_hta  = mean(hipertenso) # proporción con hipertensión
  )

pacientes_resumen
## # A tibble: 2 × 4
##   sexo       n edad_prom prop_hta
##   <chr>  <int>     <dbl>    <dbl>
## 1 Hombre     3      41.3    0.667
## 2 Mujer      3      38      0.667
knitr::kable(
  pacientes_resumen,
  digits  = 2,
  caption = "Resumen de pacientes por sexo: tamaño de muestra, edad promedio y proporción de hipertensos."
)
Resumen de pacientes por sexo: tamaño de muestra, edad promedio y proporción de hipertensos.
sexo n edad_prom prop_hta
Hombre 3 41.33 0.67
Mujer 3 38.00 0.67

4 Visualización básica con ggplot2

4.1 Histograma de edades

library(ggplot2)

ggplot(pacientes, aes(x = edad)) +
  geom_histogram(binwidth = 10, boundary = 0) +
  labs(
    title = "Distribución de edades en la muestra simulada",
    x     = "Edad (años)",
    y     = "Frecuencia"
  )

4.2 Proporción de hipertensión por sexo (gráfico de barras)

pacientes |>
  mutate(hta_factor = ifelse(hipertenso == 1, "Hipertenso", "No hipertenso")) |>
  ggplot(aes(x = sexo, fill = hta_factor)) +
  geom_bar(position = "fill") +
  scale_y_continuous(labels = scales::percent) +
  labs(
    title = "Proporción de hipertensión por sexo (datos simulados)",
    x     = "Sexo",
    y     = "Proporción",
    fill  = "Condición"
  )


5 Configuración inicial del entorno

5.1 Instalar R y RStudio

  1. Descargar R desde CRAN: https://cran.r-project.org/
  2. Descargar RStudio (entorno de desarrollo): https://posit.co/download/rstudio-desktop/

5.2 Configuración de paquetes

Los paquetes son bibliotecas que extienden las capacidades de R.

install.packages(c("tidyverse", "ggplot2", "readxl"))
library(tidyverse)
library(ggplot2)
library(readxl)

6 Consola, Script y R Markdown


7 Introducción a la Epidemiología

7.1 Definición

La epidemiología es la disciplina que estudia la distribución y los determinantes de los eventos relacionados con la salud en poblaciones humanas, y la aplicación de este conocimiento al control de problemas de salud.

7.2 Conceptos básicos

  • Incidencia: mide cuántos casos nuevos de una enfermedad ocurren en un período específico en una población en riesgo.

  • Prevalencia: mide cuántas personas presentan la enfermedad en un momento determinado (o período), independientemente de cuándo inició.

7.2.1 Ejemplo conceptual

  • Si en un hospital se registran 10 casos nuevos de dengue en una semana entre 100 personas en riesgo, la incidencia acumulada en ese periodo es 10/100 = 10%.
  • Si en ese mismo momento 20 personas tienen dengue (casos nuevos + existentes), la prevalencia puntual es 20/100 = 20%.

7.3 Importancia en biomedicina y salud pública

  1. Identificación de factores de riesgo.
  2. Evaluación de intervenciones preventivas y terapéuticas.
  3. Orientación de políticas de salud basadas en evidencia.
  4. Planificación y evaluación de servicios de salud.

8 Metodología de la clase

8.1 Objetivos

  1. Cargar datos en R a partir de archivos externos.
  2. Calcular incidencia y prevalencia en bases de datos simples.
  3. Crear visualizaciones básicas para interpretar datos epidemiológicos.

9 Preparar el entorno para epidemiología

9.1 Carga (opcional) del paquete epirhandbook

Este paquete proporciona datos y ejemplos del Epidemiologist R Handbook. El documento está diseñado para no fallar aunque no esté instalado; en ese caso, solo se mostrará un mensaje.

# Intentar cargar epirhandbook si está disponible
if (requireNamespace("epirhandbook", quietly = TRUE)) {
  library(epirhandbook)
} else {
  message(
    "El paquete 'epirhandbook' no está instalado.\n",
    "Si desea usar los datos del Epidemiologist R Handbook, ejecute en la consola:\n",
    "  install.packages('remotes')\n",
    "  remotes::install_github('appliedepi/epirhandbook')\n"
  )
}

# Paquetes de trabajo general
paquetes_basicos <- c("tidyverse", "ggplot2", "readxl", "dplyr")

instalar_si_falta <- function(pk) {
  if (!requireNamespace(pk, quietly = TRUE)) {
    install.packages(pk)
  }
  library(pk, character.only = TRUE)
}

invisible(lapply(paquetes_basicos, instalar_si_falta))

9.2 Descarga de datos de ejemplo (para ejecutar solo una vez, opcional)

Este bloque se deja como ejemplo y está desactivado (eval=FALSE) para que el documento HTML se pueda compilar sin depender de interacción con RStudio ni de archivos locales. Úselo manualmente en su sesión si desea descargar los datos.

# Ejecutar manualmente en una sesión interactiva de RStudio para descargar los datos:
# (creará una carpeta y descargará los archivos de ejemplo)

library(epirhandbook)
epirhandbook::get_data("all")
# Ejemplo: lectura desde Excel (ajustar ruta según la carpeta donde descargó los datos)
#en una carpeta "EPIDATA/linelist_cleaned.xlsx"

10 Ejemplo de linelist (estructura de datos de vigilancia)

Si previamente descargó los datos del Handbook y los almacenó en una carpeta, por ejemplo EPIDATA/, puede usar el siguiente código para leer la base linelist_cleaned.xlsx. Este bloque asume que el archivo existe en esa ruta; si no, ajusta la ruta o desactiva el chunk.

11 Análisis descriptivo de la base linelist_cleaned

En esta sección se utilizará una base de datos tipo linelist, donde cada fila representa un caso y cada columna corresponde a una variable epidemiológica o clínica (edad, sexo, fecha de inicio de síntomas, desenlace, etc.).

Nota: este bloque asume que ya descargó los datos del Epidemiologist R Handbook y que el archivo linelist_cleaned.xlsx se encuentra en una carpeta accesible (por ejemplo, EPIDATA/). Ajuste la ruta según su organización local.

11.1 Carga de la base linelist

# Ajustar la ruta si es necesario
ruta_linelist <- "C:/Users/fidel/OneDrive/Documentos/EPIDATA/linelist_cleaned.xlsx" #esto lo tienes que personalizar a tu path

if (!file.exists(ruta_linelist)) {
  stop(
    paste0(
      "No se encontró el archivo ", ruta_linelist, ".\n",
      "Verifique la ruta o descargue la base con epirhandbook::get_data('all')."
    )
  )
}

linelist <- readxl::read_excel(ruta_linelist)

# Vista general de las primeras filas
head(linelist)
## # A tibble: 6 × 30
##   case_id generation date_infection      date_onset         
##   <chr>        <dbl> <dttm>              <dttm>             
## 1 5fe599           4 2014-05-08 00:00:00 2014-05-13 00:00:00
## 2 8689b7           4 NA                  2014-05-13 00:00:00
## 3 11f8ea           2 NA                  2014-05-16 00:00:00
## 4 b8812a           3 2014-05-04 00:00:00 2014-05-18 00:00:00
## 5 893f25           3 2014-05-18 00:00:00 2014-05-21 00:00:00
## 6 be99c8           3 2014-05-03 00:00:00 2014-05-22 00:00:00
## # ℹ 26 more variables: date_hospitalisation <dttm>, date_outcome <dttm>,
## #   outcome <chr>, gender <chr>, age <dbl>, age_unit <chr>, age_years <dbl>,
## #   age_cat <chr>, age_cat5 <chr>, hospital <chr>, lon <dbl>, lat <dbl>,
## #   infector <chr>, source <chr>, wt_kg <dbl>, ht_cm <dbl>, ct_blood <dbl>,
## #   fever <chr>, chills <chr>, cough <chr>, aches <chr>, vomit <chr>,
## #   temp <dbl>, time_admission <chr>, bmi <dbl>, days_onset_hosp <dbl>

11.2 Exploración de la estructura de la base

# Dimensiones de la base (número de filas y columnas)
dim(linelist)
## [1] 5888   30
# Nombres de las variables
names(linelist)
##  [1] "case_id"              "generation"           "date_infection"      
##  [4] "date_onset"           "date_hospitalisation" "date_outcome"        
##  [7] "outcome"              "gender"               "age"                 
## [10] "age_unit"             "age_years"            "age_cat"             
## [13] "age_cat5"             "hospital"             "lon"                 
## [16] "lat"                  "infector"             "source"              
## [19] "wt_kg"                "ht_cm"                "ct_blood"            
## [22] "fever"                "chills"               "cough"               
## [25] "aches"                "vomit"                "temp"                
## [28] "time_admission"       "bmi"                  "days_onset_hosp"
# Resumen general de las variables
summary(linelist)
##    case_id            generation    date_infection                 
##  Length:5888        Min.   : 0.00   Min.   :2014-03-19 00:00:00.0  
##  Class :character   1st Qu.:13.00   1st Qu.:2014-09-06 00:00:00.0  
##  Mode  :character   Median :16.00   Median :2014-10-11 00:00:00.0  
##                     Mean   :16.56   Mean   :2014-10-22 21:47:24.2  
##                     3rd Qu.:20.00   3rd Qu.:2014-12-05 00:00:00.0  
##                     Max.   :37.00   Max.   :2015-04-27 00:00:00.0  
##                                     NA's   :2087                   
##    date_onset                     date_hospitalisation            
##  Min.   :2014-04-07 00:00:00.00   Min.   :2014-04-17 00:00:00.00  
##  1st Qu.:2014-09-16 00:00:00.00   1st Qu.:2014-09-19 00:00:00.00  
##  Median :2014-10-23 00:00:00.00   Median :2014-10-23 00:00:00.00  
##  Mean   :2014-11-03 01:16:26.93   Mean   :2014-11-03 14:33:49.89  
##  3rd Qu.:2014-12-19 00:00:00.00   3rd Qu.:2014-12-17 00:00:00.00  
##  Max.   :2015-04-30 00:00:00.00   Max.   :2015-04-30 00:00:00.00  
##  NA's   :256                                                      
##   date_outcome                      outcome             gender         
##  Min.   :2014-04-19 00:00:00.00   Length:5888        Length:5888       
##  1st Qu.:2014-09-26 00:00:00.00   Class :character   Class :character  
##  Median :2014-11-01 00:00:00.00   Mode  :character   Mode  :character  
##  Mean   :2014-11-12 21:21:48.55                                        
##  3rd Qu.:2014-12-28 00:00:00.00                                        
##  Max.   :2015-06-04 00:00:00.00                                        
##  NA's   :936                                                           
##       age          age_unit           age_years       age_cat         
##  Min.   : 0.00   Length:5888        Min.   : 0.00   Length:5888       
##  1st Qu.: 6.00   Class :character   1st Qu.: 6.00   Class :character  
##  Median :13.00   Mode  :character   Median :13.00   Mode  :character  
##  Mean   :16.07                      Mean   :16.02                     
##  3rd Qu.:23.00                      3rd Qu.:23.00                     
##  Max.   :84.00                      Max.   :84.00                     
##  NA's   :86                         NA's   :86                        
##    age_cat5           hospital              lon              lat       
##  Length:5888        Length:5888        Min.   :-13.27   Min.   :8.446  
##  Class :character   Class :character   1st Qu.:-13.25   1st Qu.:8.461  
##  Mode  :character   Mode  :character   Median :-13.23   Median :8.469  
##                                        Mean   :-13.23   Mean   :8.470  
##                                        3rd Qu.:-13.22   3rd Qu.:8.480  
##                                        Max.   :-13.21   Max.   :8.492  
##                                                                        
##    infector            source              wt_kg            ht_cm    
##  Length:5888        Length:5888        Min.   :-11.00   Min.   :  4  
##  Class :character   Class :character   1st Qu.: 41.00   1st Qu.: 91  
##  Mode  :character   Mode  :character   Median : 54.00   Median :129  
##                                        Mean   : 52.64   Mean   :125  
##                                        3rd Qu.: 66.00   3rd Qu.:159  
##                                        Max.   :111.00   Max.   :295  
##                                                                      
##     ct_blood        fever              chills             cough          
##  Min.   :16.00   Length:5888        Length:5888        Length:5888       
##  1st Qu.:20.00   Class :character   Class :character   Class :character  
##  Median :22.00   Mode  :character   Mode  :character   Mode  :character  
##  Mean   :21.21                                                           
##  3rd Qu.:22.00                                                           
##  Max.   :26.00                                                           
##                                                                          
##     aches              vomit                temp       time_admission    
##  Length:5888        Length:5888        Min.   :35.20   Length:5888       
##  Class :character   Class :character   1st Qu.:38.20   Class :character  
##  Mode  :character   Mode  :character   Median :38.80   Mode  :character  
##                                        Mean   :38.56                     
##                                        3rd Qu.:39.20                     
##                                        Max.   :40.80                     
##                                        NA's   :149                       
##       bmi           days_onset_hosp 
##  Min.   :-1200.00   Min.   : 0.000  
##  1st Qu.:   24.56   1st Qu.: 1.000  
##  Median :   32.12   Median : 1.000  
##  Mean   :   46.89   Mean   : 2.059  
##  3rd Qu.:   50.01   3rd Qu.: 3.000  
##  Max.   : 1250.00   Max.   :22.000  
##                     NA's   :256
variables_resumen <- tibble(
  variable = names(linelist),
  tipo     = sapply(linelist, class)
)

knitr::kable(
  variables_resumen,
  caption = "Resumen de variables en la base linelist_cleaned: nombre y tipo de dato.",
  longtable = TRUE
)
Resumen de variables en la base linelist_cleaned: nombre y tipo de dato.
variable tipo
case_id character
generation numeric
date_infection POSIXct, POSIXt
date_onset POSIXct, POSIXt
date_hospitalisation POSIXct, POSIXt
date_outcome POSIXct, POSIXt
outcome character
gender character
age numeric
age_unit character
age_years numeric
age_cat character
age_cat5 character
hospital character
lon numeric
lat numeric
infector character
source character
wt_kg numeric
ht_cm numeric
ct_blood numeric
fever character
chills character
cough character
aches character
vomit character
temp numeric
time_admission character
bmi numeric
days_onset_hosp numeric

11.3 Selección de variables clave para la descripción inicial

Para la descripción epidemiológica básica, es útil identificar variables clave como:

  • Edad del caso.
  • Sexo.
  • Fecha de inicio de síntomas.
  • Fecha de consulta u hospitalización.
  • Estado al final del seguimiento (por ejemplo, vivo/fallecido).

Estas variables pueden tener nombres distintos según el diseño de la base. A modo de ejemplo, asumiremos que existen los siguientes campos (ajustar según el dataset real):

  • age (edad en años).
  • sex (“male”/“female” u otras codificaciones).
  • date_onset (fecha de inicio de síntomas).
  • date_report o date_hospitalisation.
  • outcome (estado final del paciente).
# Ejemplo de selección de variables (ajustar nombres a la base real)
vars_clave <- c("id", "age", "gender", "date_onset", "date_report", "outcome")

vars_clave[!vars_clave %in% names(linelist)]
## [1] "id"          "date_report"
# Crear una versión reducida con las variables que sí existen
linelist_reducida <- linelist |> 
  dplyr::select(any_of(vars_clave))

head(linelist_reducida)
## # A tibble: 6 × 4
##     age gender date_onset          outcome
##   <dbl> <chr>  <dttm>              <chr>  
## 1     2 m      2014-05-13 00:00:00 <NA>   
## 2     3 f      2014-05-13 00:00:00 Recover
## 3    56 m      2014-05-16 00:00:00 Recover
## 4    18 f      2014-05-18 00:00:00 <NA>   
## 5     3 m      2014-05-21 00:00:00 Recover
## 6    16 f      2014-05-22 00:00:00 Recover
names(linelist_reducida)
## [1] "age"        "gender"     "date_onset" "outcome"

12 Descripción de variables demográficas

12.1 Distribución de edad

linelist_reducida |> 
  summarise(
    n_casos      = sum(!is.na(age)),
    edad_min     = min(age, na.rm = TRUE),
    edad_max     = max(age, na.rm = TRUE),
    edad_media   = mean(age, na.rm = TRUE),
    edad_mediana = median(age, na.rm = TRUE)
  ) |> 
  knitr::kable(
    digits  = 1,
    caption = "Resumen descriptivo de la edad en la base linelist."
  )
Resumen descriptivo de la edad en la base linelist.
n_casos edad_min edad_max edad_media edad_mediana
5802 0 84 16.1 13
linelist_reducida |> 
  ggplot(aes(x = age)) +
  geom_histogram(binwidth = 5, boundary = 0, closed = "left") +
  labs(
    title = "Distribución de la edad en casos registrados (linelist)",
    x     = "Edad (años)",
    y     = "Frecuencia"
  )

12.2 Distribución por sexo

linelist_reducida |> 
  count(gender) |> 
  mutate(prop = n / sum(n)) |> 
  knitr::kable(
    digits  = 3,
    caption = "Distribución de casos por sexo (frecuencia y proporción)."
  )
Distribución de casos por sexo (frecuencia y proporción).
gender n prop
f 2807 0.477
m 2803 0.476
NA 278 0.047
linelist_reducida |> 
  count(gender) |> 
  mutate(prop = n / sum(n)) |> 
  ggplot(aes(x = gender, y = prop)) +
  geom_col() +
  scale_y_continuous(labels = scales::percent) +
  labs(
    title = "Proporción de casos por sexo (linelist)",
    x     = "Sexo",
    y     = "Proporción de casos"
  )

12.3 Edad por sexo (distribución conjunta)

linelist_reducida |> 
  ggplot(aes(x = gender, y = age)) +
  geom_boxplot() +
  labs(
    title = "Distribución de la edad por sexo",
    x     = "Sexo",
    y     = "Edad (años)"
  )


13 Curva epidémica (epicurva) a partir de la fecha de inicio de síntomas

Para la caracterización temporal del brote o evento, se construye una curva epidémica, agrupando los casos por fecha (o semana) de inicio de síntomas.

13.1 Preparación de fechas

library(lubridate)

linelist_fechas <- linelist_reducida |> 
  mutate(
    date_onset = as.Date(date_onset),
    semana_onset = floor_date(date_onset, unit = "week", week_start = 1)
  )

# Vista rápida para comprobar la transformación
linelist_fechas |> 
  dplyr::select(date_onset, semana_onset) |> 
  head()
## # A tibble: 6 × 2
##   date_onset semana_onset
##   <date>     <date>      
## 1 2014-05-13 2014-05-12  
## 2 2014-05-13 2014-05-12  
## 3 2014-05-16 2014-05-12  
## 4 2014-05-18 2014-05-12  
## 5 2014-05-21 2014-05-19  
## 6 2014-05-22 2014-05-19

13.2 Curva epidémica semanal

linelist_fechas |> 
  count(semana_onset) |> 
  ggplot(aes(x = semana_onset, y = n)) +
  geom_col() +
  labs(
    title = "Curva epidémica semanal según fecha de inicio de síntomas",
    x     = "Semana de inicio de síntomas",
    y     = "Número de casos"
  )

13.3 Curva epidémica estratificada por sexo

linelist_fechas |> 
  filter(!is.na(gender)) |> 
  count(semana_onset, gender) |> 
  ggplot(aes(x = semana_onset, y = n, fill = gender)) +
  geom_col(position = "stack") +
  labs(
    title = "Curva epidémica semanal estratificada por sexo",
    x     = "Semana de inicio de síntomas",
    y     = "Número de casos",
    fill  = "Sexo"
  )


14 Desenlace clínico y letalidad (si la variable está disponible)

Si la base incluye una variable de desenlace (por ejemplo, outcome con categorías como “Died” / “Recovered”), es posible estimar proporciones de letalidad y explorar diferencias por edad o sexo.

if ("outcome" %in% names(linelist_reducida)) {
  linelist_reducida |> 
    count(outcome) |> 
    mutate(prop = n / sum(n)) |> 
    knitr::kable(
      digits  = 3,
      caption = "Distribución de desenlaces clínicos (outcome) en la base linelist."
    )
}
Distribución de desenlaces clínicos (outcome) en la base linelist.
outcome n prop
Death 2582 0.439
Recover 1983 0.337
NA 1323 0.225
if (all(c("outcome", "sex") %in% names(linelist_reducida))) {
  linelist_reducida |> 
    mutate(muerto = ifelse(outcome %in% c("Died", "Dead", "Fallecido"), 1, 0)) |> 
    group_by(sex) |> 
    summarise(
      n        = n(),
      muertes  = sum(muerto, na.rm = TRUE),
      letalidad = muertes / n
    ) |> 
    knitr::kable(
      digits  = 3,
      caption = "Letalidad (proporción de fallecidos) por sexo, según variable outcome."
    )
}
14 Desenlace clínico y letalidad (si la variable está disponible) ===============================================================
Si la base incluye una variable de desenlace (por ejemplo, outcome con categorías como "Death" / "Recover"), es posible estimar proporciones de letalidad y explorar diferencias por edad o sexo/género.
14.1 Tabla de desenlaces (outcome)
if ("outcome" %in% names(linelist_reducida)) {
  linelist_reducida |>
    count(outcome) |>
    mutate(prop = n / sum(n)) |>
    knitr::kable(
      digits  = 3,
      caption = "Distribución de desenlaces clínicos (outcome) en la base linelist."
    )
}
Distribución de desenlaces clínicos (outcome) en la base linelist.
outcome n prop
Death 2582 0.439
Recover 1983 0.337
NA 1323 0.225

14.1 14.2 Letalidad por género (versión básica)

En la base linelist_cleaned, la variable disponible es gender (y no sex), por lo que conviene usarla como estrato.

if (all(c("outcome", "gender") %in% names(linelist_reducida))) {
  linelist_reducida |>
    mutate(muerto = ifelse(outcome %in% c("Death", "Died", "Dead", "Fallecido"), 1, 0)) |>
    group_by(gender) |>
    summarise(
      n         = n(),
      muertes   = sum(muerto, na.rm = TRUE),
      letalidad = muertes / n
    ) |>
    knitr::kable(
      digits  = 3,
      caption = "Letalidad (proporción de fallecidos) por género, según la variable outcome."
    )
}
Letalidad (proporción de fallecidos) por género, según la variable outcome.
gender n muertes letalidad
f 2807 1227 0.437
m 2803 1228 0.438
NA 278 127 0.457

14.2 14.3 Incorporando gtsummary para tablas clínicas

El paquete gtsummary facilita la construcción de tablas descriptivas y de resultados de modelos, con formato adecuado para informes y manuscritos.

if (!requireNamespace("gtsummary", quietly = TRUE)) {
  install.packages("gtsummary")
}
library(gtsummary)

14.2.1 14.3.1 Tabla de resumen con gtsummary (edad y género)

if (all(c("age", "gender") %in% names(linelist_reducida))) {
  linelist_reducida |>
    select(age, gender) |>
    tbl_summary(
      by = gender,
      statistic = list(
        all_continuous()  ~ "{mean} ({sd})",
        all_categorical() ~ "{n} ({p}%)"
      ),
      missing = "no"
    ) |>
    add_n() |>
    modify_header(label ~ "Variable") |>
    modify_caption("**Resumen descriptivo de edad por género (gtsummary)**")
}
Resumen descriptivo de edad por género (gtsummary)
Variable N f
N = 2,807
1
m
N = 2,803
1
age 5,610 13 (10) 20 (14)
1 Mean (SD)

14.2.2 14.3.2 Tabla de desenlace (muerto / no muerto) por género con gtsummary

Primero se crea una variable binaria de fallecimiento (muerto), luego se resume su distribución por género:

if (all(c("outcome", "gender") %in% names(linelist_reducida))) {
  linelist_tab_out <- linelist_reducida |>
    mutate(
      genero = gender,
      muerto = dplyr::if_else(
        outcome %in% c("Death", "Died", "Dead", "Fallecido"),
        "Sí",
        "No",
        missing = "No"
      )
    )
  
  linelist_tab_out |>
    select(genero, muerto) |>
    tbl_summary(
      by        = genero,
      statistic = all_categorical() ~ "{n} ({p}%)",
      missing   = "no"
    ) |>
    modify_header(label ~ "Variable") |>
    modify_caption("**Distribución de fallecimiento (muerto = Sí) por género (gtsummary)**")
}
Distribución de fallecimiento (muerto = Sí) por género (gtsummary)
Variable f
N = 2,807
1
m
N = 2,803
1
muerto

    No 1,580 (56%) 1,575 (56%)
    Sí 1,227 (44%) 1,228 (44%)
1 n (%)

15 15 Letalidad por grupos de edad

En muchos análisis epidemiológicos resulta útil estimar la letalidad por grupos de edad, para identificar un gradiente de riesgo.

15.1 15.1 Cálculo de letalidad por grupos de edad

if (all(c("outcome", "age") %in% names(linelist_reducida))) {
  
  linelist_grupos <- linelist_reducida |>
    mutate(
      muerto = ifelse(outcome %in% c("Death", "Died", "Dead", "Fallecido"), 1, 0),
      grupo_edad = cut(
        age,
        breaks = c(-Inf, 4, 14, 24, 44, 64, Inf),
        labels = c("0-4", "5-14", "15-24", "25-44", "45-64", "65+")
      )
    )
  
  tabla_letalidad_edad <- linelist_grupos |>
    group_by(grupo_edad) |>
    summarise(
      n        = n(),
      muertes  = sum(muerto, na.rm = TRUE),
      letalidad = muertes / n
    )
  
  knitr::kable(
    tabla_letalidad_edad,
    digits  = 3,
    caption = "Letalidad por grupos de edad en la base linelist."
  )
}
Letalidad por grupos de edad en la base linelist.
grupo_edad n muertes letalidad
0-4 1072 464 0.433
5-14 2051 920 0.449
15-24 1387 611 0.441
25-44 1098 477 0.434
45-64 175 70 0.400
65+ 19 8 0.421
NA 86 32 0.372

15.2 15.2 Gráfico de letalidad por grupos de edad

if (exists("tabla_letalidad_edad")) {
  tabla_letalidad_edad |>
    ggplot(aes(x = grupo_edad, y = letalidad)) +
    geom_col() +
    scale_y_continuous(labels = scales::percent) +
    labs(
      title = "Letalidad por grupo de edad",
      x     = "Grupo de edad (años)",
      y     = "Letalidad (%)"
    )
}

15.3 15.3 Letalidad por grupos de edad y género (gtsummary estratificado)

if (all(c("outcome", "age", "gender") %in% names(linelist_reducida))) {
  
  linelist_grupos_gt <- linelist_reducida |>
    mutate(
      grupo_edad = cut(
        age,
        breaks = c(-Inf, 4, 14, 24, 44, 64, Inf),
        labels = c("0-4", "5-14", "15-24", "25-44", "45-64", "65+")
      ),
      muerto = dplyr::if_else(
        outcome %in% c("Death", "Died", "Dead", "Fallecido"),
        "Sí",
        "No",
        missing = "No"
      )
    )
  
  linelist_grupos_gt |>
    select(grupo_edad, gender, muerto) |>
    tbl_summary(
      by        = grupo_edad,
      statistic = all_categorical() ~ "{n} ({p}%)",
      missing   = "no"
    ) |>
    modify_caption("**Distribución de fallecidos por grupo de edad y género (gtsummary)**")
}
Distribución de fallecidos por grupo de edad y género (gtsummary)
Characteristic 0-4
N = 1,072
1
5-14
N = 2,051
1
15-24
N = 1,387
1
25-44
N = 1,098
1
45-64
N = 175
1
65+
N = 19
1
gender





    f 626 (60%) 1,169 (59%) 667 (49%) 335 (31%) 10 (5.9%) 0 (0%)
    m 409 (40%) 800 (41%) 682 (51%) 736 (69%) 159 (94%) 17 (100%)
muerto





    No 608 (57%) 1,131 (55%) 776 (56%) 621 (57%) 105 (60%) 11 (58%)
    Sí 464 (43%) 920 (45%) 611 (44%) 477 (43%) 70 (40%) 8 (42%)
1 n (%)

16 16 Curva epidémica estratificada por desenlace

16.1 16.1 Curva epidémica semanal por desenlace

if (all(c("date_onset", "outcome") %in% names(linelist_reducida))) {
  
  linelist_fechas_out <- linelist_reducida |>
    mutate(
      date_onset   = as.Date(date_onset),
      semana_onset = lubridate::floor_date(date_onset, unit = "week", week_start = 1)
    )
  
  linelist_fechas_out |>
    filter(!is.na(outcome)) |>
    count(semana_onset, outcome) |>
    ggplot(aes(x = semana_onset, y = n, fill = outcome)) +
    geom_col(position = "stack") +
    labs(
      title = "Curva epidémica semanal estratificada por desenlace (outcome)",
      x     = "Semana de inicio de síntomas",
      y     = "Número de casos",
      fill  = "Desenlace"
    )
}

16.2 16.2 Curva epidémica por género y desenlace

if (all(c("date_onset", "outcome", "gender") %in% names(linelist_reducida))) {
  
  linelist_fechas_go <- linelist_reducida |>
    mutate(
      date_onset   = as.Date(date_onset),
      semana_onset = lubridate::floor_date(date_onset, unit = "week", week_start = 1)
    )
  
  linelist_fechas_go |>
    filter(!is.na(outcome), !is.na(gender)) |>
    count(semana_onset, gender, outcome) |>
    ggplot(aes(x = semana_onset, y = n, fill = outcome)) +
    geom_col(position = "stack") +
    facet_wrap(~ gender) +
    labs(
      title = "Curva epidémica semanal por género y desenlace",
      x     = "Semana de inicio de síntomas",
      y     = "Número de casos",
      fill  = "Desenlace"
    )
}

17 17 Modelo exploratorio: asociación entre edad, género y muerte

Como ejemplo introductorio de análisis multivariable, se puede ajustar un modelo de regresión logística para explorar la asociación entre edad, género y probabilidad de fallecer. Aquí se muestra tanto el resumen clásico como su presentación con gtsummary.

17.1 17.1 Ajuste del modelo logístico

if (all(c("outcome", "age", "gender") %in% names(linelist_reducida))) {
  
  linelist_modelo <- linelist_reducida |>
    mutate(
      muerto = ifelse(outcome %in% c("Death", "Died", "Dead", "Fallecido"), 1, 0)
    ) |>
    filter(!is.na(muerto), !is.na(age), !is.na(gender))
  
  if (!requireNamespace("broom", quietly = TRUE)) {
    install.packages("broom")
  }
  library(broom)
  
  modelo_logit <- glm(
    muerto ~ age + gender,
    data   = linelist_modelo,
    family = binomial(link = "logit")
  )
  
  summary(modelo_logit)
}
## 
## Call:
## glm(formula = muerto ~ age + gender, family = binomial(link = "logit"), 
##     data = linelist_modelo)
## 
## Coefficients:
##              Estimate Std. Error z value Pr(>|z|)    
## (Intercept) -0.228825   0.047277  -4.840  1.3e-06 ***
## age         -0.001899   0.002220  -0.856    0.392    
## genderm      0.017081   0.055954   0.305    0.760    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 7689.5  on 5609  degrees of freedom
## Residual deviance: 7688.8  on 5607  degrees of freedom
## AIC: 7694.8
## 
## Number of Fisher Scoring iterations: 3

17.2 17.2 Presentación del modelo con gtsummary

if (exists("modelo_logit")) {
  
  tbl_regression(
    modelo_logit,
    exponentiate = TRUE
  ) |>
    modify_header(label ~ "Variable") |>
    modify_caption("**Modelo de regresión logística para mortalidad, ajustado por edad y género (OR e IC 95%)**")
}
Modelo de regresión logística para mortalidad, ajustado por edad y género (OR e IC 95%)
Variable OR1 95% CI1 p-value
age 1.00 0.99, 1.00 0.4
gender


    f
    m 1.02 0.91, 1.14 0.8
1 OR = Odds Ratio, CI = Confidence Interval

18 18 Ejercicios sugeridos para la práctica

  1. Repetir el cálculo de letalidad utilizando otros puntos de corte de grupos de edad (por ejemplo, decenios).
  2. Incluir otras covariables disponibles en la base (por ejemplo, presencia de fiebre, fever, o estado nutricional aproximado con bmi) dentro del modelo logístico.
  3. Construir tablas 2×2 específicas (por ejemplo, exposición = fiebre sí/no, desenlace = muerte sí/no) y comparar:
    • Riesgo de muerte.
    • Razón de riesgos.
    • Diferencia de riesgos.
  4. Replicar las tablas descriptivas usando gtsummary, pero ahora estratificando por hospital (hospital) o por categoría de edad (age_cat, age_cat5).

Estos ejercicios permiten consolidar el vínculo entre exploración descriptiva, visualización epidemiológica y modelos estadísticos dentro de un flujo reproducible en R Markdown.