Introducción

En este taller aprenderemos a comparar la mortalidad entre poblaciones con estructuras de edad diferentes (Blancos, Negros e Hispanos). Para evitar sesgos, utilizaremos el Método Directo de Estandarización usando la población estándar de EE.UU. año 2000.

1. Preparación del Entorno

Primero, cargamos las librerías y preparamos los datos desde el Excel.

# --- LIBRERÍAS ---
library(readxl)
library(dplyr)
library(tidyr)
library(ggplot2)
library(writexl)

archivo <- "direct_indirect_student_v2.xlsx"

Explicación

  • library(): Es como abrir una caja de herramientas. R base tiene funciones básicas, pero para análisis complejos necesitamos “paquetes” especializados.

  • readxl y writexl: Son el puente entre R y Excel. Nos permiten leer (read) y guardar (write) archivos .xlsx.

  • dplyr y tidyr: Son el estándar de oro en ciencia de datos (parte del “Tidyverse”). dplyr funciona con verbos (seleccionar, filtrar) y tidyr ayuda a cambiar la forma de los datos.


2. Importación de Datos

2.1 Datos de Muertes (Numerador)

Leemos la hoja de muertes seleccionando exactamente el área donde están los datos numéricos.

# --- PASO 1: LEER MUERTES ---
# Leemos un rango específico (A4:J14) para evitar títulos innecesarios
df_muertes <- read_excel(archivo, sheet = "Maryland Deaths", range = "A4:J14", col_names = FALSE)

# Asignamos nombres manuales para evitar espacios y caracteres extraños
colnames(df_muertes) <- c("Edad", 
                          "Blanco_Total", "Blanco_Hombres", "Blanco_Mujeres",
                          "Negro_Total", "Negro_Hombres", "Negro_Mujeres",
                          "Hispano_Total", "Hispano_Hombres", "Hispano_Mujeres")

# Vemos las primeras filas
head(df_muertes, 3)
## # A tibble: 3 × 10
##   Edad  Blanco_Total Blanco_Hombres Blanco_Mujeres Negro_Total Negro_Hombres
##   <chr>        <dbl>          <dbl>          <dbl>       <dbl>         <dbl>
## 1 <1             150             78             72         250           141
## 2 1-4             26             17              9          34            18
## 3 5-14            29             14             15          36            22
## # ℹ 4 more variables: Negro_Mujeres <dbl>, Hispano_Total <dbl>,
## #   Hispano_Hombres <dbl>, Hispano_Mujeres <dbl>

Explicación

  • range = "A4:J14": Esta función actúa como un “cortador de galletas”. Le decimos a R que ignore todo el archivo y solo recorte el rectángulo exacto donde están nuestros datos.

  • col_names = FALSE: Le decimos a R que la primera fila que lee (la 4) es un dato, no el título de la columna.

  • colnames(...) <- c(...): Renombramos las columnas manualmente. En programación, es vital evitar espacios en los nombres (ej: usar Blanco_Total en vez de Blanco Total) para no tener errores después.


2.2 Datos de Población (Denominador)

Leemos la población y seleccionamos solo las columnas que coinciden con las muertes.

# --- PASO 2: LEER POBLACIÓN ---
df_pob_raw <- read_excel(archivo, sheet = "Maryland Population", range = "A5:R23", col_names = FALSE)

# Seleccionamos columnas útiles usando su posición numérica
df_pob_raw <- df_pob_raw %>% select(1, 2, 4, 6, 8, 10, 12, 14, 16, 18)

# Usamos los mismos nombres que en la tabla de muertes
colnames(df_pob_raw) <- colnames(df_muertes)

Explicación

  • %>% (Pipe): Este símbolo significa “y luego…”. Toma el resultado de la izquierda y lo pasa a la función de la derecha.

  • select(1, 2, 4...): El Excel original tenía columnas vacías intercaladas. Aquí le decimos a R: “Quédate con la columna 1, la 2, salta la 3, quédate con la 4…”. Así limpiamos la “basura” del Excel.


3. Homologación de Grupos de Edad

El archivo de Población tiene 19 grupos de edad (ej: 5-9 y 10-14), pero el de Muertes tiene 11 (ej: 5-14). Debemos sumar la población para que coincidan.

# --- PASO 3: HOMOLOGAR POBLACIÓN (19 filas -> 11 filas) ---

# Creamos un "mapa" o índice de agrupación
# 1 y 2 se quedan solos. Del 3 al 10 se repiten dos veces (para sumar pares).
grupos_id <- c(1, 2, rep(3:10, each = 2), 11)

# Aplicamos la agrupación ("Split-Apply-Combine")
df_pob_agrupada <- df_pob_raw %>%
  mutate(Grupo_ID = grupos_id) %>%          # 1. Agregamos el ID
  group_by(Grupo_ID) %>%                    # 2. Agrupamos por ese ID
  summarise(across(where(is.numeric), sum)) %>% # 3. Sumamos los números
  select(-Grupo_ID)                         # 4. Quitamos el ID

# Verificamos dimensiones (debe tener 11 filas)
nrow(df_pob_agrupada)
## [1] 11

Explicación

Esta es la parte lógica más compleja:

  • rep(3:10, each = 2): Genera una secuencia 3, 3, 4, 4, 5, 5…. Esto le dice a R que las filas 3 y 4 del Excel original deben sumarse juntas en un solo grupo nuevo.

  • group_by + summarise: Es la combinación más poderosa de dplyr.

    • group_by: “Junta las filas que tengan el mismo ID”.

    • summarise(sum): “Una vez juntas, suma sus valores”.

  • El resultado es una tabla comprimida de 11 filas, lista para dividirse con la tabla de muertes.


4. Cálculo de Tasas Ajustadas

Aplicamos los ponderadores de la población estándar (US 2000) a cada grupo.

# --- PASO 4: DEFINIR PONDERADORES ---
pesos_us_2000 <- c(0.013818, 0.055317, 0.145565, 0.138646, 0.135573, 
                   0.162613, 0.134834, 0.087247, 0.066037, 0.044842, 0.015508)

# --- PASO 5: CÁLCULO ITERATIVO ---
# Creamos una tabla vacía para guardar resultados
resumen_ajuste <- data.frame()

# Obtenemos los nombres de las categorías (ej: Blanco_Hombres, etc.)
categorias <- colnames(df_muertes)[2:10] 

for (cat in categorias) {
  # Extraemos las columnas actuales
  muertes <- df_muertes[[cat]]
  poblacion <- df_pob_agrupada[[cat]]
  
  # 1. Tasa Cruda (Real)
  tasa_cruda <- (sum(muertes) / sum(poblacion)) * 1000
  
  # 2. Tasa Ajustada (Hipotética)
  # Fórmula: Sumatoria de (Tasa Específica x Peso Estándar)
  tasas_especificas <- (muertes / poblacion) * 1000
  muertes_esperadas <- tasas_especificas * pesos_us_2000
  tasa_ajustada <- sum(muertes_esperadas)
  
  # Guardamos en la tabla resumen
  resumen_ajuste <- rbind(resumen_ajuste, data.frame(
    Categoria = cat,
    Tasa_Cruda = tasa_cruda,
    Tasa_Ajustada = tasa_ajustada
  ))
}

# Mostramos la tabla final
knitr::kable(resumen_ajuste, digits = 2, caption = "Tasas Crudas vs Ajustadas")
Tasas Crudas vs Ajustadas
Categoria Tasa_Cruda Tasa_Ajustada
Blanco_Total 8.46 6.71
Blanco_Hombres 8.51 7.96
Blanco_Mujeres 8.42 5.74
Negro_Total 6.70 8.00
Negro_Hombres 7.21 9.88
Negro_Mujeres 6.25 6.71
Hispano_Total 1.39 3.09
Hispano_Hombres 1.44 3.45
Hispano_Mujeres 1.34 2.75

Explicación para el Estudiante

  • pesos_us_2000: Es la estructura de edad “ideal” que usamos para comparar.

  • for (cat in categorias): Es un Bucle. Le dice a R: “Repite las instrucciones que están entre corchetes { } para cada columna de datos”.

  • La Matemática:

    • Tasa Cruda: Es el promedio simple. (Total Muertes / Total Pob).

    • Tasa Ajustada: Es un promedio ponderado. Multiplicamos la tasa de cada edad por el “peso” de esa edad en la población estándar.


5. Visualización

Comparamos visualmente las tasas para entender el impacto del ajuste en la población masculina y femenina.

5.1 Mortalidad en Hombres (Males)

Observa la diferencia entre las barras blancas (Cruda) y negras (Ajustada).

# --- 1. PREPARAR DATOS (TIDY) ---
datos_males <- resumen_ajuste %>%
  filter(grepl("_Hombres", Categoria)) %>%  # Filtramos solo hombres
  mutate(Raza = case_when(                  # Creamos columna Raza limpia
    grepl("Blanco", Categoria) ~ "White",
    grepl("Negro", Categoria) ~ "Black",
    grepl("Hispano", Categoria) ~ "Hispanic"
  )) %>%
  pivot_longer(cols = c("Tasa_Cruda", "Tasa_Ajustada"), # De Ancho a Largo
               names_to = "Tipo", values_to = "Valor")

# --- 2. GRAFICAR Y GUARDAR EN OBJETO ---
g_males <- ggplot(datos_males, aes(x = Tipo, y = Valor, fill = Raza)) +
  geom_bar(stat = "identity", position = "dodge", color = "black") +
  geom_text(aes(label = round(Valor, 1)), position = position_dodge(0.9), vjust = -0.5) +
  scale_fill_manual(values = c("White"="white", "Black"="black", "Hispanic"="grey70")) +
  labs(title = "Mortalidad en Hombres (Males)", y = "Tasa por 1,000 hab.", x = NULL) +
  theme_classic()

# Ahora sí lo imprimimos para que se vea en el tutorial
print(g_males)

Explicación

  • filter(grepl(...)): Busca un patrón de texto. Aquí decimos “Quédate solo con las filas que digan ’_Hombres’“.

  • pivot_longer: Convierte la tabla de formato “Ancho” (columnas separadas para Cruda y Ajustada) a formato “Largo”. ggplot2 prefiere el formato largo para poder usar una sola columna de valores para la altura de las barras.

  • geom_bar(position = "dodge"): Por defecto, las barras se apilan. dodge le dice a R que las ponga una al lado de la otra.


5.2 Mortalidad en Mujeres (Females)

# --- 1. PREPARAR DATOS ---
datos_fem <- resumen_ajuste %>%
  filter(grepl("_Mujeres", Categoria)) %>%
  mutate(Raza = case_when(
    grepl("Blanco", Categoria) ~ "White",
    grepl("Negro", Categoria) ~ "Black",
    grepl("Hispano", Categoria) ~ "Hispanic"
  )) %>%
  pivot_longer(cols = c("Tasa_Cruda", "Tasa_Ajustada"), 
               names_to = "Tipo", values_to = "Valor")

# --- 2. GRAFICAR ---
g_fem <- ggplot(datos_fem, aes(x = Tipo, y = Valor, fill = Raza)) +
  geom_bar(stat = "identity", position = "dodge", color = "black") +
  geom_text(aes(label = round(Valor, 1)), position = position_dodge(0.9), vjust = -0.5) +
  scale_fill_manual(values = c("White"="white", "Black"="black", "Hispanic"="grey70")) +
  labs(title = "Mortalidad en Mujeres (Females)", y = "Tasa por 1,000 hab.", x = NULL) +
  theme_classic()

# Imprimimos
print(g_fem)

Explicación

El código es casi idéntico al de hombres, solo cambiamos el filtro inicial a _Mujeres. Esto demuestra la ventaja de programar: el código es reutilizable. Una vez resuelves el problema para un grupo, es muy fácil aplicarlo a otros.


6. Exportación de Resultados

Por último, guardamos las tablas en Excel y las imágenes en alta calidad en la carpeta del proyecto.

# Guardar Excel
write_xlsx(list("Resumen" = resumen_ajuste), "Reporte_Maryland.xlsx")

# Guardar Imágenes PNG
ggsave("Grafico_Hombres.png", plot = g_males, width = 7, height = 5, dpi = 300)
ggsave("Grafico_Mujeres.png", plot = g_fem, width = 7, height = 5, dpi = 300)

Explicación

  • list(...): Al exportar a Excel, podemos crear un archivo con varias hojas (pestañas). Cada elemento dentro de la lista (list("Nombre Pestaña" = datos)) se convierte en una hoja del archivo Excel. Explicación para el Estudiante (Sobre este error) Este es un excelente momento de enseñanza:

  • “En R, si escribes 2 + 2, la consola te muestra 4 pero lo olvida inmediatamente. Si quieres usar ese 4 después, tienes que decirle: resultado <- 2 + 2. Con los gráficos pasa lo mismo: ggplot(...) solo muestra el dibujo. g_males <- ggplot(...) guarda el dibujo en una variable para poder exportarlo a un archivo después.”


7. Estandarización Indirecta: Comparativa Multi-Grupo

En esta sección final, calcularemos 6 Razones de Mortalidad Estandarizada (SMR). Compararemos a la población Hispana y Negra (General, Hombres y Mujeres) contra el estándar de la población Blanca.

El objetivo es responder: “¿Tienen estos grupos mayor o menor riesgo de morir que los blancos de su mismo sexo y edad?”

7.1 Configuración y Función Automatizada

Este bloque define la hoja de Excel y crea la “máquina” (función) que hará los cálculos repetitivos.

# --- 1. CONFIGURACIÓN INICIAL ---
# Nombre de la hoja en el Excel donde están los datos indirectos
hoja_ind <- "Indirect MRs"

# --- 2. DEFINICIÓN DE LA FUNCIÓN ---
# Creamos una función para no repetir el mismo código 6 veces.
# Inputs: Rangos de celdas (Texto) y el número total de muertes observadas (Número).

calcular_un_smr <- function(rango_tasa_blancos, rango_pob_estudio, obs_reales, nombre_grupo) {
  
  # A. Leer Tasa de Referencia (Blancos)
  # Usamos col_names = FALSE porque tu diagnóstico demostró que así SÍ lee los datos
  df_tasa <- read_excel(archivo, sheet = hoja_ind, range = rango_tasa_blancos, col_names = FALSE)
  colnames(df_tasa) <- "Tasa_Ref" # Le ponemos nombre manualmente
  
  # B. Leer Población de Estudio
  df_pob <- read_excel(archivo, sheet = hoja_ind, range = rango_pob_estudio, col_names = FALSE)
  colnames(df_pob) <- "Pob_Estudio" # Le ponemos nombre manualmente
  
  # C. Calcular Muertes Esperadas
  # Convertimos a numérico explícitamente para evitar cualquier error de formato
  tasa_num <- as.numeric(df_tasa$Tasa_Ref)
  pob_num  <- as.numeric(df_pob$Pob_Estudio)
  
  # Cálculo matemático
  esperadas <- sum((tasa_num / 1000) * pob_num, na.rm = TRUE)
  
  # D. Calcular SMR e Intervalos
  if(esperadas == 0) {
     # Si da 0, devolvemos NA para no romper el código
     return(data.frame(Grupo = nombre_grupo, Observadas = obs_reales, Esperadas = 0, SMR = NA, Lower_CI = NA, Upper_CI = NA))
  }
  
  smr <- obs_reales / esperadas
  se <- smr / sqrt(obs_reales)
  
  return(data.frame(
    Grupo = nombre_grupo,
    Observadas = obs_reales,
    Esperadas = round(esperadas, 1),
    SMR = round(smr, 3),
    Lower_CI = round(smr - (1.96 * se), 3),
    Upper_CI = round(smr + (1.96 * se), 3)
  ))
}

Explicación

¿Qué es una function()? En programación, cuando tienes que repetir la misma tarea muchas veces (en este caso, 6 veces), es mala práctica copiar y pegar el código. En su lugar, creamos una “máquina personalizada”.

  • Inputs (Entradas): Le decimos a la máquina: “Toma las tasas de la celda X a la Y, toma la población de Z a W, y usa este número de muertes reales”.

  • Output (Salida): La máquina nos devuelve una fila limpia con el SMR calculado y sus intervalos de confianza listos.

  • Automatización: Esto reduce errores humanos. Si te equivocaste en la fórmula del SMR, solo la corriges en un lugar (dentro de la función) y se arregla automáticamente para todos los grupos.

7.2 Gráfico Final de SMR

Este gráfico resume todo el análisis de estandarización indirecta.

# --- 3. EJECUCIÓN DE CÁLCULOS ---
# Inicializamos una tabla vacía
tabla_final_smr <- data.frame()

# --- A) POBLACIÓN HISPANA (Columna C vs Tasa Blancos Col B) ---

# 1. Total (Filas 8-18)
tabla_final_smr <- rbind(tabla_final_smr, calcular_un_smr(
  "B8:B18", "C8:C18", sum(df_muertes$Hispano_Total), "Hispano - Total"))

# 2. Hombres (Filas 24-34 -> Tomamos 11 filas exactas)
tabla_final_smr <- rbind(tabla_final_smr, calcular_un_smr(
  "B24:B34", "C24:C34", sum(df_muertes$Hispano_Hombres), "Hispano - Hombres"))

# 3. Mujeres (Filas 40-50)
tabla_final_smr <- rbind(tabla_final_smr, calcular_un_smr(
  "B40:B50", "C40:C50", sum(df_muertes$Hispano_Mujeres), "Hispano - Mujeres"))


# --- B) POBLACIÓN NEGRA (Columna N vs Tasa Blancos Col B) ---
# Nota: La tasa de referencia (Blancos) sigue siendo la Columna B.

# 4. Total (Filas 8-18)
tabla_final_smr <- rbind(tabla_final_smr, calcular_un_smr(
  "B8:B18", "N8:N18", sum(df_muertes$Negro_Total), "Negro - Total"))

# 5. Hombres (Filas 24-34)
tabla_final_smr <- rbind(tabla_final_smr, calcular_un_smr(
  "B24:B34", "N24:N34", sum(df_muertes$Negro_Hombres), "Negro - Hombres"))

# 6. Mujeres (Filas 40-50)
tabla_final_smr <- rbind(tabla_final_smr, calcular_un_smr(
  "B40:B50", "N40:N50", sum(df_muertes$Negro_Mujeres), "Negro - Mujeres"))

# Mostrar tabla final
knitr::kable(tabla_final_smr, caption = "Resumen SMR: Comparación vs. Población Blanca")
Resumen SMR: Comparación vs. Población Blanca
Grupo Observadas Esperadas SMR Lower_CI Upper_CI
Hispano - Total 741 1700.2 0.436 0.404 0.467
Hispano - Hombres 402 763.9 0.526 0.475 0.578
Hispano - Mujeres 339 936.3 0.362 0.324 0.401
Negro - Total 12355 10754.6 1.149 1.129 1.169
Negro - Hombres 6219 4202.0 1.480 1.443 1.517
Negro - Mujeres 6136 6552.6 0.936 0.913 0.960

Explicación

La Lógica de Comparación:

  • Para los Hispanos, usamos las Tasas de Blancos (Columna B) multiplicadas por la Población Hispana (Columna C).

  • Para los Negros, usamos las mismas Tasas de Blancos (Columna B) multiplicadas por la Población Negra (Columna N).

  • ¿Por qué siempre la Columna B? Porque la Población Blanca es nuestro Estándar de Referencia (“The Gold Standard”) contra el cual medimos a los demás.

rbind (Row Bind): Significa “Unir Filas”. Imaginen que estamos apilando ladrillos. Calculamos una fila, la ponemos en la tabla tabla_final_smr, calculamos la siguiente y la ponemos encima, y así sucesivamente.


7.3 Visualización Gráfica (SMR)

Este bloque genera el gráfico de barras con intervalos de confianza y colorea según el riesgo.

# --- 4. VISUALIZACIÓN FINAL ---

# Añadimos una columna para colorear automáticamente (Rojo = Malo, Verde = Bueno)
tabla_final_smr <- tabla_final_smr %>%
  mutate(Condicion = ifelse(SMR > 1, "Mayor Riesgo", "Menor Riesgo"))

# Crear Gráfico
g_final <- ggplot(tabla_final_smr, aes(x = Grupo, y = SMR, fill = Condicion)) +
  geom_bar(stat = "identity", color = "black", width = 0.7) +
  
  # Barras de error (Intervalo de Confianza)
  geom_errorbar(aes(ymin = Lower_CI, ymax = Upper_CI), width = 0.2) +
  
  # Línea de Referencia (SMR = 1)
  geom_hline(yintercept = 1, color = "red", linetype = "dashed", size = 1.2) +
  
  # Etiquetas de texto sobre las barras
  geom_text(aes(label = SMR), vjust = -2.5, fontface = "bold") +
  
  # Colores personalizados
  scale_fill_manual(values = c("Mayor Riesgo" = "#ff6666", "Menor Riesgo" = "#66cc66")) +
  
  # Títulos y Ejes
  labs(title = "SMR: Comparación con Población Blanca",
       subtitle = "SMR = 1 indica riesgo idéntico a los Blancos (Línea Roja)",
       y = "Razón de Mortalidad Estandarizada",
       x = NULL) +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) # Rotar etiquetas del eje X

# Imprimir y Guardar
print(g_final)

ggsave("SMR_Completo.png", plot = g_final, width = 10, height = 6)

Explicación

Interpretación Visual:

  • Línea Roja (y = 1): Es la frontera del riesgo normal (el riesgo de los Blancos).

  • Barras Verdes (SMR < 1): Significan “Protección” o menor mortalidad. Por ejemplo, si la barra de Hispanos está en 0.5, significa que mueren la mitad de lo esperado comparado con los blancos. Esto se conoce en epidemiología como la “Paradoja Hispana”.

  • Barras Rojas (SMR > 1): Significan “Exceso de Riesgo”. Si la barra de Negros está en 1.2, tienen un 20% más riesgo de muerte que los blancos de la misma edad.

  • Barras de Error (las “antenas”): Representan el intervalo de confianza. Si la línea negra cruza la línea roja punteada, la diferencia no es estadísticamente significativa (podría ser casualidad).

# --- BLOQUE DE DIAGNÓSTICO DE EXCEL ---
library(readxl)

# 1. Verificar si el archivo existe
print(paste("¿Archivo encontrado?:", file.exists(archivo)))
## [1] "¿Archivo encontrado?: TRUE"
# 2. Listar las pestañas que R ve en el archivo
# (Fíjate si "Indirect MRs" aparece EXACTAMENTE así en la lista)
print("Pestañas disponibles en el Excel:")
## [1] "Pestañas disponibles en el Excel:"
print(excel_sheets(archivo))
## [1] "Maryland Deaths"     "Maryland Population" "Direct MRs"         
## [4] "Indirect MRs"
# 3. VER LA REALIDAD: ¿Qué hay exactamente en B8:B18?
# Leemos sin encabezados para ver el dato crudo
print("--- CONTENIDO DE TASAS (B8:B18) ---")
## [1] "--- CONTENIDO DE TASAS (B8:B18) ---"
test_rango <- read_excel(archivo, sheet = "Indirect MRs", range = "B8:B18", col_names = FALSE)
print(test_rango)
## # A tibble: 11 × 1
##        ...1
##       <dbl>
##  1   3.58  
##  2   0.156 
##  3   0.0678
##  4   0.574 
##  5   0.947 
##  6   1.77  
##  7   4.43  
##  8   9.15  
##  9  16.0   
## 10  46.1   
## 11 134.
# 4. VER LA REALIDAD: ¿Qué hay exactamente en C8:C18?
print("--- CONTENIDO DE POBLACIÓN (C8:C18) ---")
## [1] "--- CONTENIDO DE POBLACIÓN (C8:C18) ---"
test_pob <- read_excel(archivo, sheet = "Indirect MRs", range = "C8:C18", col_names = FALSE)
print(test_pob)
## # A tibble: 11 × 1
##      ...1
##     <dbl>
##  1  11887
##  2  45679
##  3  90447
##  4  81986
##  5 103347
##  6  87939
##  7  58276
##  8  30953
##  9  13504
## 10   6073
## 11   2283