1 Objetivo, parámetros y configuración

Este documento reproduce, de principio a fin, el cálculo de las tasas específicas de fecundidad (TEF) y de la tasa global de fecundidad (TGF) de México para 1985-2025:

  1. Descomprime los 41 archivos ZIP y extrae las bases NACIM85 a NACIM25 (formato DBF).
  2. Construye tabla_nacimientos: grupos de edad de la madre (filas) por año de ocurrencia (columnas), sumando los registros de todos los años de registro.
  3. Construye tabla_mujeres: población femenina a mitad de año de CONAPO por grupo de edad y año.
  4. Calcula las TEF = nacimientos / mujeres y la TGF = (suma de TEF) × 5.
  5. Grafica las TEF cada cinco años.

Los chunks se ejecutan en el orden en que aparecen y cada uno depende de los anteriores. A lo largo del documento, cada chunk va precedido de una explicación de lo que hace.

1.1 Parámetros del encabezado (YAML)

El encabezado define el título, el formato HTML (índice flotante, secciones numeradas, código plegable) y los parámetros que controlan el comportamiento del documento. Los chunks los leen con params$nombre.

Parámetro Valor por defecto Para qué sirve
directorio ruta de Downloads Carpeta con los ZIP, los DBF y el Excel de CONAPO. Es el único lugar donde se define el directorio de trabajo.
archivo_poblacion Pob_Mitad_1950_2070.xlsx Nombre del Excel de población a mitad de año de CONAPO.
extraer_zip true Si es false, se omite la descompresión (útil cuando los DBF ya están extraídos).
recalcular_nacimientos true Si es false, se reutiliza resumen_nacimientos.rds en lugar de volver a leer los 41 DBF.
exportar_excel false Si es true, genera un Excel con las tres tablas y la gráfica.

1.2 Chunk setup (oculto)

No aparece en el HTML (include=FALSE), pero se ejecuta primero. Hace dos cosas:

  • opts_knit$set(root.dir = params$directorio) fija el directorio de trabajo de todos los chunks. Debe usarse opts_knit y no opts_chunk. Reemplaza al setwd(), que dentro de un Rmd solo tiene efecto dentro de su propio chunk.
  • opts_chunk$set(...) define los valores por defecto de todos los chunks: mostrar el código, ocultar los mensajes, mostrar las advertencias (útiles para detectar años faltantes) y centrar las figuras.

1.3 Chunk css (oculto)

No es código R sino CSS. Hace que las tablas se desplacen horizontalmente en lugar de desbordar la página (las tablas de 41 años son muy anchas), sin saltos de línea y con letra un poco más pequeña. También amplía el ancho máximo del contenido.

1.4 Chunk paquetes

Este chunk hace cuatro cosas:

  • Carga las librerías: foreign (read.dbf), readxl (lee el Excel de CONAPO), dplyr, tidyr y purrr (manipulación de datos y map), stringr (str_extract), ggplot2 (gráfica) y knitr (kable).
  • Define los parámetros del análisis: anio_ini, anio_fin, anios (vector de texto “1985”, …, “2025”, que coincide con los nombres de columna de las tablas), variables_requeridas y orden_edades.
  • Crea la función tabla_html(), que formatea una tabla con kable, con decimales y separador de miles.
  • Imprime el directorio de trabajo para confirmar que apunta a la carpeta correcta.
library(foreign)
library(readxl)
library(dplyr)
library(purrr)
library(tidyr)
library(stringr)
library(ggplot2)
library(knitr)

anio_ini <- 1985
anio_fin <- 2025
anios    <- as.character(anio_ini:anio_fin)

variables_requeridas <- c("ANO_NAC", "ANO_REG", "EDAD_MADN")

orden_edades <- c("12-14", "15-19", "20-24", "25-29",
                  "30-34", "35-39", "40-44", "45-49")

# Formato de tablas para HTML
tabla_html <- function(x, digits = 0) {
  kable(x, digits = digits, format.args = list(big.mark = ","), align = "r")
}

cat("Directorio de trabajo:", getwd(), "\n")
## Directorio de trabajo: C:/Users/fcrigel/Downloads/natalidad_base_datos_1985_dbf

2 Descompresión de las bases de natalidad

2.1 Chunk descomprimir (solo si extraer_zip: true)

  1. Lista todos los .zip de la carpeta.
  2. Para cada uno, unzip(list = TRUE) consulta qué contiene sin extraerlo, y grepl selecciona los nombres que terminan en .dbf.
  3. Extrae solo esos archivos a la raíz con exdir = "." y junkpaths = TRUE, que descarta las subcarpetas internas del ZIP.
  4. Si un ZIP no trae ningún DBF, lanza una advertencia con su nombre.
  5. Devuelve una tabla (extraccion) con cuántos DBF salieron de cada ZIP, y se imprime el total.
# Lista todos los .zip de la carpeta y extrae solo los .dbf a la raíz
archivos_zip <- list.files(pattern = "\\.zip$", full.names = TRUE,
                           ignore.case = TRUE)
cat("Archivos ZIP encontrados:", length(archivos_zip), "\n")
## Archivos ZIP encontrados: 41
extraccion <- map_dfr(archivos_zip, function(zip_path) {
  contenido <- unzip(zip_path, list = TRUE)
  dbf <- contenido$Name[grepl("\\.dbf$", contenido$Name, ignore.case = TRUE)]

  if (length(dbf) > 0) {
    unzip(zip_path, files = dbf, exdir = ".", junkpaths = TRUE)
  } else {
    warning("No se encontró ningún archivo .dbf en: ", basename(zip_path))
  }

  tibble(zip = basename(zip_path), dbf_extraidos = length(dbf))
})

cat("Total de archivos DBF extraídos:", sum(extraccion$dbf_extraidos), "\n")
## Total de archivos DBF extraídos: 82

2.2 Chunk verificar-bases

  • Busca los archivos que cumplen el patrón NACIMxx.dbf (sin distinguir mayúsculas) y se detiene con un error claro si no hay ninguno.
  • Extrae los dos dígitos del nombre y los convierte a año: 85 a 99 pasan a 1985-1999 y 00 a 25 pasan a 2000-2025. Esto es necesario porque el orden alfabético pondría NACIM00 antes que NACIM85.
  • Ordena los archivos cronológicamente e imprime cuántos encontró (esperado: 41) y sus nombres.
  • Compara contra 1985-2025 y avisa con una advertencia si falta algún año.
archivos <- list.files(
  path = ".",
  pattern = "^NACIM[0-9]{2}\\.dbf$",
  full.names = TRUE,
  ignore.case = TRUE
)

if (length(archivos) == 0) {
  stop("No se encontraron archivos NACIM85.dbf a NACIM25.dbf en: ", getwd())
}

# Año de cada base según su nombre (85-99 -> 1985-1999; 00-25 -> 2000-2025)
aa <- as.numeric(str_extract(basename(archivos), "[0-9]{2}"))
anio_archivo <- ifelse(aa >= 85, 1900 + aa, 2000 + aa)

# Orden cronológico (no alfabético)
archivos <- archivos[order(anio_archivo)]
anio_archivo <- sort(anio_archivo)

cat("Bases NACIM encontradas:", length(archivos), "(esperadas: 41)\n")
## Bases NACIM encontradas: 41 (esperadas: 41)
cat(basename(archivos), fill = 100)
## NACIM85.dbf NACIM86.dbf NACIM87.dbf NACIM88.dbf NACIM89.dbf NACIM90.dbf NACIM91.dbf NACIM92.dbf 
## NACIM93.dbf NACIM94.dbf NACIM95.dbf NACIM96.dbf NACIM97.dbf NACIM98.dbf NACIM99.dbf NACIM00.dbf 
## NACIM01.dbf NACIM02.dbf NACIM03.dbf NACIM04.dbf NACIM05.dbf NACIM06.dbf NACIM07.dbf NACIM08.dbf 
## NACIM09.dbf NACIM10.dbf NACIM11.dbf NACIM12.dbf NACIM13.dbf NACIM14.dbf NACIM15.dbf NACIM16.DBF 
## NACIM17.DBF NACIM18.DBF NACIM19.DBF NACIM20.DBF nacim21.dbf nacim22.dbf nacim23.dbf nacim24.dbf 
## nacim25.dbf
anios_faltantes <- setdiff(anio_ini:anio_fin, anio_archivo)
if (length(anios_faltantes) > 0) {
  warning("No se encontró base para los años: ",
          paste(anios_faltantes, collapse = ", "))
}

3 Tabla de nacimientos

3.1 Chunk nacimientos-proceso

Es el chunk más pesado y tiene dos partes. Cada base se resume dentro de la función (conteo por año de ocurrencia y grupo de edad) para no mantener decenas de millones de registros en memoria.

La función procesar_base(), que se aplica a cada DBF:

  • Lee el DBF con as.is = TRUE para evitar que convierta textos a factores, y pone los nombres de columna en mayúsculas.
  • Verifica que existan ANO_NAC, ANO_REG y EDAD_MADN; si falta alguna, se detiene indicando el archivo y la variable.
  • Convierte las tres variables a número pasando por texto. Los valores no numéricos se vuelven NA sin generar error.
  • Libera el DBF original (rm(df)) y asigna el grupo de edad con case_when. Las edades fuera de 12-49 o no especificadas quedan como NA.
  • Resume la base con count(Ano_nac, grupo_edad), conservando los NA para poder controlarlos después. Así no se guardan los microdatos, solo unas pocas filas por archivo.
  • Arma un diagnóstico por archivo: número de registros, registros con año de ocurrencia fuera de 1985-2025, años mínimo y máximo de ocurrencia y de registro, y registros sin grupo de edad. Luego limpia memoria con gc().

La lógica de caché y consolidación:

  • Si recalcular_nacimientos es true, o aún no existe resumen_nacimientos.rds, procesa los 41 archivos y los combina. Luego suma por año de ocurrencia y grupo de edad: un mismo año de ocurrencia aparece en varios archivos (los nacimientos de 1985 registrados en 1985, 1986, etc.), y esa suma implementa el objetivo de acumular los registros de todos los años de registro. Después guarda el resultado en el .rds.
  • Si no, simplemente carga el .rds guardado, lo que evita releer los DBF.
  • Imprime el total de registros leídos.
procesar_base <- function(ruta_archivo) {

  df <- read.dbf(ruta_archivo, as.is = TRUE)
  names(df) <- toupper(trimws(names(df)))

  faltantes <- setdiff(variables_requeridas, names(df))
  if (length(faltantes) > 0) {
    stop("Faltan variables en: ", basename(ruta_archivo),
         " -> ", paste(faltantes, collapse = ", "))
  }

  a_num <- function(x) suppressWarnings(as.numeric(trimws(as.character(x))))

  datos <- tibble(
    Ano_nac  = a_num(df$ANO_NAC),
    Ano_reg  = a_num(df$ANO_REG),
    edad_num = a_num(df$EDAD_MADN)
  )
  rm(df)

  datos <- datos %>%
    mutate(
      grupo_edad = case_when(
        edad_num >= 12 & edad_num <= 14 ~ "12-14",
        edad_num >= 15 & edad_num <= 19 ~ "15-19",
        edad_num >= 20 & edad_num <= 24 ~ "20-24",
        edad_num >= 25 & edad_num <= 29 ~ "25-29",
        edad_num >= 30 & edad_num <= 34 ~ "30-34",
        edad_num >= 35 & edad_num <= 39 ~ "35-39",
        edad_num >= 40 & edad_num <= 44 ~ "40-44",
        edad_num >= 45 & edad_num <= 49 ~ "45-49",
        TRUE ~ NA_character_
      )
    )

  resumen <- datos %>%
    count(Ano_nac, grupo_edad, name = "nacimientos") %>%
    mutate(archivo = basename(ruta_archivo))

  diagnostico <- tibble(
    archivo             = basename(ruta_archivo),
    registros           = nrow(datos),
    ano_nac_fuera_rango = sum(is.na(datos$Ano_nac) |
                                datos$Ano_nac < anio_ini |
                                datos$Ano_nac > anio_fin),
    ano_nac_min         = suppressWarnings(min(datos$Ano_nac, na.rm = TRUE)),
    ano_nac_max         = suppressWarnings(max(datos$Ano_nac, na.rm = TRUE)),
    ano_reg_min         = suppressWarnings(min(datos$Ano_reg, na.rm = TRUE)),
    ano_reg_max         = suppressWarnings(max(datos$Ano_reg, na.rm = TRUE)),
    edad_sin_grupo      = sum(is.na(datos$grupo_edad))
  )

  rm(datos)
  gc(verbose = FALSE)

  list(resumen = resumen, diagnostico = diagnostico)
}

rds_nac <- "resumen_nacimientos.rds"

if (params$recalcular_nacimientos || !file.exists(rds_nac)) {
  resultados <- map(archivos, procesar_base)

  resumen_total <- map_dfr(resultados, "resumen") %>%
    group_by(Ano_nac, grupo_edad) %>%
    summarise(nacimientos = sum(nacimientos), .groups = "drop")

  diagnostico_archivos <- map_dfr(resultados, "diagnostico")

  saveRDS(list(resumen_total = resumen_total,
               diagnostico_archivos = diagnostico_archivos), rds_nac)
} else {
  guardado <- readRDS(rds_nac)
  resumen_total        <- guardado$resumen_total
  diagnostico_archivos <- guardado$diagnostico_archivos
}

cat("Total de registros leídos:",
    format(sum(diagnostico_archivos$registros), big.mark = ","), "\n")
## Total de registros leídos: 101,799,025

3.2 Chunk tabla-nacimientos

Construye la tabla de nacimientos (grupos de edad en filas, años de ocurrencia en columnas):

  • Filtra los registros con grupo de edad válido y año entre 1985 y 2025.
  • complete() garantiza los 8 grupos por 41 años, con 0 donde no hubo nacimientos. Se hace antes del pivote para que no queden NA ni columnas faltantes.
  • Ordena los grupos con un factor y pivot_wider() convierte los años en columnas.
  • Agrega la columna Total (suma por grupo de edad) y la fila Total (suma por año), y muestra la tabla.
tabla_nacimientos <- resumen_total %>%
  filter(
    !is.na(grupo_edad),
    Ano_nac >= anio_ini, Ano_nac <= anio_fin
  ) %>%
  complete(
    grupo_edad = orden_edades,
    Ano_nac    = as.numeric(anio_ini:anio_fin),
    fill       = list(nacimientos = 0)
  ) %>%
  mutate(grupo_edad = factor(grupo_edad, levels = orden_edades)) %>%
  arrange(grupo_edad, Ano_nac) %>%
  pivot_wider(names_from = Ano_nac, values_from = nacimientos) %>%
  mutate(grupo_edad = as.character(grupo_edad))

tabla_nacimientos <- tabla_nacimientos %>%
  mutate(Total = rowSums(across(-grupo_edad)))

fila_total <- tabla_nacimientos %>%
  summarise(across(-grupo_edad, sum)) %>%
  mutate(grupo_edad = "Total", .before = 1)

tabla_nacimientos <- bind_rows(tabla_nacimientos, fila_total)

tabla_html(tabla_nacimientos)
grupo_edad 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 Total
12-14 467 461 563 721 701 1,050 1,030 1,468 1,767 2,031 2,431 2,850 4,098 7,848 8,195 9,511 8,817 8,359 8,001 8,328 8,529 8,667 8,962 9,417 9,680 9,323 8,984 9,384 9,072 8,937 8,604 7,800 7,854 7,274 6,502 4,948 5,209 5,227 4,543 4,035 2,832 234,480
15-19 3,561 3,994 4,704 5,480 6,569 9,207 11,783 19,981 29,357 40,042 52,907 71,155 165,796 407,097 413,426 429,535 415,314 410,061 397,614 395,003 394,775 392,619 407,843 427,267 438,763 435,950 443,352 445,942 435,657 417,405 395,293 374,935 374,563 348,780 312,488 268,268 239,528 219,105 192,751 168,339 118,852 10,545,061
20-24 5,755 6,400 7,369 8,362 10,729 14,315 19,152 32,969 46,789 63,952 84,458 112,645 287,929 773,308 769,720 783,541 751,279 743,684 728,444 718,424 703,358 702,587 704,617 707,579 700,441 699,324 702,510 714,907 705,672 693,747 681,535 649,482 626,694 588,317 539,489 497,159 450,008 431,674 383,493 336,574 247,682 18,436,073
25-29 4,995 5,730 6,504 7,325 9,333 12,421 16,222 26,522 37,209 49,733 64,784 85,386 222,586 648,337 653,788 674,282 652,033 644,339 626,406 615,835 603,954 598,641 597,309 596,581 586,049 582,020 579,804 573,421 561,439 552,348 553,575 543,105 542,444 528,987 506,478 474,549 430,101 417,416 368,957 324,202 253,216 15,838,366
30-34 3,408 3,802 4,283 4,970 6,257 8,516 11,381 18,750 26,271 35,184 45,130 57,473 140,595 390,932 390,955 406,164 399,206 404,863 402,895 408,868 408,517 408,868 407,507 406,327 396,299 391,635 387,975 384,331 376,735 374,861 373,498 367,190 364,578 356,123 344,007 330,069 307,214 311,873 285,411 256,081 202,450 10,611,452
35-39 2,035 2,238 2,660 2,977 3,798 4,973 6,747 10,696 14,868 19,721 25,866 33,311 75,425 182,326 181,314 188,373 182,890 181,464 179,417 179,610 180,072 182,414 186,175 190,502 191,217 192,476 194,860 187,525 183,646 182,319 181,141 178,464 178,373 176,959 172,659 166,251 155,009 157,293 144,841 129,907 103,723 5,096,535
40-44 910 951 1,132 1,087 1,459 2,042 2,692 4,457 6,314 8,224 10,588 13,569 26,527 51,837 51,104 52,726 49,884 48,626 48,120 46,994 46,024 46,154 45,099 44,623 44,453 43,745 43,925 45,362 44,908 45,463 45,594 44,572 44,530 44,258 43,522 42,287 39,341 39,805 36,345 32,263 24,224 1,315,740
45-49 288 301 358 404 442 574 704 1,084 1,457 1,833 2,498 2,975 4,863 7,739 7,331 7,126 6,497 6,022 5,564 5,493 5,340 4,994 4,641 4,315 4,308 3,936 3,668 3,500 3,396 3,344 3,410 3,500 3,510 3,516 3,501 3,532 3,124 3,053 2,687 2,364 1,820 139,012
Total 21,419 23,877 27,573 31,326 39,288 53,098 69,711 115,927 164,032 220,720 288,662 379,364 927,819 2,469,424 2,475,833 2,551,258 2,465,920 2,447,418 2,396,461 2,378,555 2,350,569 2,344,944 2,362,153 2,386,611 2,371,210 2,358,409 2,365,078 2,364,372 2,320,525 2,278,424 2,242,650 2,169,048 2,142,546 2,054,214 1,928,646 1,787,063 1,629,534 1,585,446 1,419,028 1,253,765 954,799 62,216,719

3.3 Control de calidad de los nacimientos

3.3.1 Chunk control-nacimientos

Para cada año de ocurrencia calcula el total de nacimientos, cuántos tienen grupo de edad, cuántos no y el porcentaje sin grupo. Sirve para ver cuánto se pierde por edad no especificada o fuera de 12-49 años.

control_anual <- resumen_total %>%
  filter(Ano_nac >= anio_ini, Ano_nac <= anio_fin) %>%
  group_by(Ano_nac) %>%
  summarise(
    total_nacimientos = sum(nacimientos),
    con_grupo_edad    = sum(nacimientos[!is.na(grupo_edad)]),
    sin_grupo_edad    = sum(nacimientos[is.na(grupo_edad)]),
    .groups = "drop"
  ) %>%
  mutate(pct_sin_grupo = round(100 * sin_grupo_edad / total_nacimientos, 2))

tabla_html(control_anual)
Ano_nac total_nacimientos con_grupo_edad sin_grupo_edad pct_sin_grupo
1,985 31,372 21,419 9,953 32
1,986 33,368 23,877 9,491 28
1,987 36,761 27,573 9,188 25
1,988 41,269 31,326 9,943 24
1,989 48,940 39,288 9,652 20
1,990 64,857 53,098 11,759 18
1,991 81,346 69,711 11,635 14
1,992 129,180 115,927 13,253 10
1,993 177,714 164,032 13,682 8
1,994 235,202 220,720 14,482 6
1,995 303,984 288,662 15,322 5
1,996 395,784 379,364 16,420 4
1,997 946,268 927,819 18,449 2
1,998 2,497,680 2,469,424 28,256 1
1,999 2,504,433 2,475,833 28,600 1
2,000 2,580,918 2,551,258 29,660 1
2,001 2,495,027 2,465,920 29,107 1
2,002 2,477,304 2,447,418 29,886 1
2,003 2,425,408 2,396,461 28,947 1
2,004 2,407,144 2,378,555 28,589 1
2,005 2,379,913 2,350,569 29,344 1
2,006 2,372,982 2,344,944 28,038 1
2,007 2,388,445 2,362,153 26,292 1
2,008 2,410,505 2,386,611 23,894 1
2,009 2,396,969 2,371,210 25,759 1
2,010 2,377,010 2,358,409 18,601 1
2,011 2,383,883 2,365,078 18,805 1
2,012 2,378,246 2,364,372 13,874 1
2,013 2,334,129 2,320,525 13,604 1
2,014 2,291,428 2,278,424 13,004 1
2,015 2,256,528 2,242,650 13,878 1
2,016 2,182,066 2,169,048 13,018 1
2,017 2,157,276 2,142,546 14,730 1
2,018 2,070,173 2,054,214 15,959 1
2,019 1,954,398 1,928,646 25,752 1
2,020 1,850,916 1,787,063 63,853 3
2,021 1,700,606 1,629,534 71,072 4
2,022 1,670,634 1,585,446 85,188 5
2,023 1,530,190 1,419,028 111,162 7
2,024 1,381,457 1,253,765 127,692 9
2,025 1,058,332 954,799 103,533 10

3.3.2 Chunk diagnostico-bases

Muestra la tabla de diagnóstico por archivo generada en nacimientos-proceso. Los valores infinitos (que aparecen cuando una base no tiene datos para el mínimo o el máximo) se reemplazan por NA. Es la tabla para detectar problemas como años de dos dígitos o registros fuera de rango.

diagnostico_archivos %>%
  mutate(across(where(is.numeric), ~ ifelse(is.finite(.x), .x, NA))) %>%
  tabla_html()
archivo registros ano_nac_fuera_rango ano_nac_min ano_nac_max ano_reg_min ano_reg_max edad_sin_grupo
NACIM85.dbf 2,655,571 2,655,571 1 99 85 85 87,156
NACIM86.dbf 2,579,301 2,579,301 1 99 86 86 79,890
NACIM87.dbf 2,794,390 2,794,390 1 99 87 87 87,238
NACIM88.dbf 2,622,031 2,622,031 1 99 88 88 95,622
NACIM89.dbf 2,620,262 2,620,262 1 99 89 89 94,837
NACIM90.dbf 2,735,312 2,735,312 1 99 90 90 140,803
NACIM91.dbf 2,756,447 2,756,447 1 99 91 91 133,533
NACIM92.dbf 2,797,397 2,797,397 1 99 92 92 119,841
NACIM93.dbf 2,839,686 2,839,686 1 99 93 93 144,719
NACIM94.dbf 2,904,389 2,904,389 1 99 94 94 178,750
NACIM95.dbf 2,750,444 2,750,444 1 99 95 95 138,157
NACIM96.dbf 2,707,718 2,707,718 1 99 96 96 120,429
NACIM97.dbf 2,698,425 2,698,425 1 99 97 97 128,376
NACIM98.dbf 2,668,428 139,273 1,901 9,999 1,998 1,998 118,031
NACIM99.dbf 2,769,089 187,733 1,901 9,999 1,999 1,999 160,071
NACIM00.dbf 2,798,339 233,176 1,901 9,999 2,000 2,000 189,482
NACIM01.dbf 2,767,610 264,386 1,901 9,999 2,001 2,001 222,278
NACIM02.dbf 2,699,084 210,390 1,901 9,999 2,002 2,002 189,844
NACIM03.dbf 2,655,894 226,941 1,901 9,999 2,003 2,003 203,277
NACIM04.dbf 2,625,056 195,549 1,901 9,999 2,004 2,004 181,392
NACIM05.dbf 2,567,906 142,876 1,901 9,999 2,005 2,005 138,658
NACIM06.dbf 2,505,939 127,524 1,901 9,999 2,006 2,006 126,651
NACIM07.dbf 2,655,083 144,641 1,901 9,999 2,007 2,007 144,273
NACIM08.dbf 2,636,110 130,854 1,901 9,999 2,008 2,008 134,372
NACIM09.dbf 2,577,214 101,591 1,901 9,999 2,009 2,009 116,169
NACIM10.dbf 2,643,908 109,405 1,901 9,999 2,010 2,010 117,013
NACIM11.dbf 2,586,287 106,591 1,901 9,999 2,011 2,011 121,744
NACIM12.dbf 2,498,880 70,567 1,901 9,999 2,012 2,012 82,622
NACIM13.dbf 2,478,889 59,578 1,901 9,999 2,013 2,013 71,424
NACIM14.dbf 2,463,420 65,271 1,901 9,999 2,014 2,014 75,402
NACIM15.dbf 2,353,596 52,166 1,901 9,999 2,015 2,015 66,459
NACIM16.DBF 2,293,708 46,465 1,902 9,999 2,016 2,016 58,224
NACIM17.DBF 2,234,039 36,856 1,902 9,999 2,017 2,017 54,271
NACIM18.DBF 2,162,535 31,926 1,901 9,999 2,018 2,018 46,738
NACIM19.DBF 2,092,214 31,265 1,901 9,999 2,019 2,019 47,302
NACIM20.DBF 1,629,211 13,875 1,923 9,999 2,020 2,020 62,688
nacim21.dbf 1,912,178 24,038 1,924 9,999 2,021 2,021 95,749
nacim22.dbf 1,891,388 29,391 1,925 9,999 2,022 2,022 120,715
nacim23.dbf 1,820,888 33,928 1,926 9,999 2,023 2,023 168,386
nacim24.dbf 1,672,227 34,678 1,927 9,999 2,024 2,024 196,047
nacim25.dbf 1,678,532 46,673 1,928 9,999 2,025 2,025 259,268

4 Tabla de mujeres (población a mitad de año, CONAPO)

4.1 Chunk tabla-mujeres

  • Lee el Excel de CONAPO con el nombre del parámetro archivo_poblacion.
  • Pone los nombres en mayúsculas y localiza la columna del año con un patrón (^A.{1,2}O$), para no depender de cómo se codifique la Ñ. La renombra a ANIO y verifica las demás columnas requeridas.
  • Convierte a número y filtra mujeres, año entre 1985 y 2025 y CVE_GEO == 0. Ese último filtro usa solo el renglón “República Mexicana”, que ya es la suma de las 32 entidades, para no duplicar la población.
  • Asigna los mismos ocho grupos de edad (con etiqueta “12-14”, igual que en nacimientos) y descarta las demás edades.
  • Suma la población por grupo y año y repite el patrón de complete, pivot_wider y totales del chunk tabla-nacimientos.
  • Revisa si alguna columna de año quedó entera en cero y, si es así, avisa que el archivo no trae datos para esos años.
conapo <- read_excel(params$archivo_poblacion)

# Estandarizar nombres. La columna del año ("AÑO") se detecta por patrón
# para evitar problemas de codificación con la Ñ.
names(conapo) <- toupper(trimws(names(conapo)))
col_anio <- grep("^A.{1,2}O$", names(conapo), value = TRUE)
if (length(col_anio) != 1) stop("No se pudo identificar la columna del año.")
names(conapo)[names(conapo) == col_anio] <- "ANIO"

variables_pob <- c("ANIO", "CVE_GEO", "EDAD", "SEXO", "POBLACION")
faltantes <- setdiff(variables_pob, names(conapo))
if (length(faltantes) > 0) {
  stop("Faltan variables: ", paste(faltantes, collapse = ", "))
}

# El archivo incluye "República Mexicana" (CVE_GEO = 0), que ya es la suma
# de las 32 entidades: se usa solo ese renglón para no duplicar población.
mujeres <- conapo %>%
  transmute(
    anio      = as.numeric(ANIO),
    cve_geo   = as.numeric(CVE_GEO),
    edad      = as.numeric(EDAD),
    sexo      = trimws(as.character(SEXO)),
    poblacion = as.numeric(POBLACION)
  ) %>%
  filter(
    tolower(sexo) == "mujeres",
    cve_geo == 0,
    anio >= anio_ini, anio <= anio_fin
  ) %>%
  mutate(
    grupo_edad = case_when(
      edad >= 12 & edad <= 14 ~ "12-14",
      edad >= 15 & edad <= 19 ~ "15-19",
      edad >= 20 & edad <= 24 ~ "20-24",
      edad >= 25 & edad <= 29 ~ "25-29",
      edad >= 30 & edad <= 34 ~ "30-34",
      edad >= 35 & edad <= 39 ~ "35-39",
      edad >= 40 & edad <= 44 ~ "40-44",
      edad >= 45 & edad <= 49 ~ "45-49",
      TRUE ~ NA_character_
    )
  ) %>%
  filter(!is.na(grupo_edad))

tabla_mujeres <- mujeres %>%
  group_by(grupo_edad, anio) %>%
  summarise(mujeres = sum(poblacion, na.rm = TRUE), .groups = "drop") %>%
  complete(
    grupo_edad = orden_edades,
    anio       = as.numeric(anio_ini:anio_fin),
    fill       = list(mujeres = 0)
  ) %>%
  mutate(grupo_edad = factor(grupo_edad, levels = orden_edades)) %>%
  arrange(grupo_edad, anio) %>%
  pivot_wider(names_from = anio, values_from = mujeres) %>%
  mutate(grupo_edad = as.character(grupo_edad))

tabla_mujeres <- tabla_mujeres %>%
  mutate(Total = rowSums(across(-grupo_edad)))

fila_total <- tabla_mujeres %>%
  summarise(across(-grupo_edad, sum)) %>%
  mutate(grupo_edad = "Total", .before = 1)

tabla_mujeres <- bind_rows(tabla_mujeres, fila_total)

# Años sin información en el archivo (columnas en cero)
anios_vacios <- names(tabla_mujeres)[
  sapply(tabla_mujeres, function(x) is.numeric(x) && all(x == 0))
]
if (length(anios_vacios) > 0) {
  warning("Años sin datos en el archivo: ", paste(anios_vacios, collapse = ", "))
}

tabla_html(tabla_mujeres)
grupo_edad 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 Total
12-14 2,965,709 3,026,474 3,072,211 3,098,444 3,105,623 3,101,053 3,095,339 3,096,325 3,106,144 3,124,439 3,149,378 3,178,147 3,206,819 3,231,817 3,252,316 3,271,142 3,293,979 3,324,378 3,361,471 3,398,207 3,426,105 3,440,664 3,442,781 3,434,080 3,419,778 3,411,371 3,417,233 3,433,721 3,447,246 3,444,108 3,422,467 3,394,152 3,371,302 3,359,236 3,356,516 3,359,673 3,363,710 3,361,458 3,346,574 3,317,416 3,280,231 134,709,237
15-19 4,340,793 4,460,789 4,579,243 4,691,893 4,794,115 4,879,625 4,941,934 4,979,055 4,995,447 4,999,419 4,999,936 5,005,318 5,020,931 5,047,211 5,081,462 5,123,040 5,173,306 5,228,556 5,283,480 5,335,204 5,391,588 5,460,816 5,538,324 5,609,600 5,662,657 5,693,010 5,698,945 5,686,745 5,670,895 5,662,384 5,662,037 5,666,681 5,670,370 5,665,627 5,647,119 5,618,427 5,589,928 5,571,193 5,564,568 5,564,987 5,563,771 216,820,429
20-24 3,591,645 3,704,955 3,813,261 3,917,894 4,019,889 4,121,716 4,225,149 4,328,479 4,428,691 4,521,998 4,602,322 4,662,358 4,698,233 4,711,792 4,709,007 4,702,153 4,707,898 4,734,160 4,778,766 4,834,588 4,900,753 4,977,631 5,061,681 5,144,267 5,220,877 5,297,863 5,378,733 5,457,736 5,526,888 5,577,279 5,599,663 5,595,305 5,577,115 5,561,170 5,556,576 5,563,996 5,576,853 5,586,722 5,585,055 5,567,936 5,540,050 201,669,103
25-29 2,883,443 2,997,711 3,113,326 3,228,571 3,341,521 3,449,799 3,552,393 3,651,208 3,747,978 3,843,814 3,941,089 4,040,999 4,140,639 4,234,982 4,318,150 4,386,113 4,437,969 4,473,200 4,494,617 4,507,680 4,524,140 4,556,880 4,611,735 4,681,088 4,757,034 4,838,502 4,920,910 4,999,836 5,074,361 5,143,314 5,207,133 5,270,719 5,334,712 5,394,519 5,442,117 5,469,917 5,475,466 5,467,669 5,459,302 5,459,620 5,470,611 184,344,787
30-34 2,273,097 2,376,872 2,483,854 2,591,902 2,699,260 2,806,098 2,914,751 3,025,341 3,136,392 3,245,983 3,351,765 3,452,469 3,549,037 3,641,887 3,730,704 3,818,475 3,909,773 4,003,903 4,097,853 4,187,403 4,269,769 4,342,228 4,402,270 4,445,875 4,475,571 4,502,522 4,535,847 4,581,196 4,639,278 4,705,567 4,773,373 4,839,056 4,902,679 4,965,833 5,028,763 5,093,463 5,162,202 5,233,563 5,299,979 5,352,822 5,385,792 166,234,467
35-39 1,894,238 1,940,947 1,994,042 2,057,119 2,133,923 2,225,299 2,326,059 2,430,336 2,536,096 2,641,535 2,746,777 2,853,895 2,962,418 3,070,271 3,174,792 3,274,049 3,368,711 3,461,001 3,552,629 3,644,463 3,740,524 3,844,629 3,955,206 4,064,445 4,165,518 4,254,770 4,326,617 4,378,164 4,412,314 4,434,339 4,450,911 4,471,878 4,505,826 4,555,120 4,616,369 4,683,563 4,750,981 4,819,107 4,887,520 4,954,478 5,023,257 147,584,136
40-44 1,619,498 1,668,344 1,717,286 1,765,714 1,812,010 1,856,178 1,901,832 1,954,014 2,016,170 2,091,961 2,182,182 2,281,492 2,383,847 2,486,944 2,588,631 2,689,275 2,792,011 2,897,178 3,003,515 3,109,137 3,212,893 3,315,138 3,417,535 3,518,642 3,617,565 3,717,699 3,821,049 3,925,478 4,027,716 4,122,653 4,203,558 4,265,596 4,308,448 4,336,383 4,355,421 4,371,237 4,391,928 4,428,573 4,481,896 4,546,015 4,616,724 129,819,366
45-49 1,355,513 1,397,976 1,442,049 1,487,435 1,534,725 1,582,835 1,630,791 1,679,044 1,726,895 1,772,729 1,816,537 1,861,561 1,912,624 1,973,056 2,046,210 2,133,028 2,229,037 2,328,740 2,430,350 2,532,220 2,635,114 2,741,988 2,852,885 2,964,417 3,073,624 3,179,078 3,280,118 3,377,909 3,473,944 3,568,248 3,661,986 3,757,511 3,854,661 3,951,283 4,042,905 4,120,099 4,176,773 4,219,707 4,252,152 4,275,120 4,296,374 112,629,251
Total 20,923,936 21,574,068 22,215,272 22,838,972 23,441,066 24,022,603 24,588,248 25,143,802 25,693,813 26,241,878 26,789,986 27,336,239 27,874,548 28,397,960 28,901,272 29,397,275 29,912,684 30,451,116 31,002,681 31,548,902 32,100,886 32,679,974 33,282,417 33,862,414 34,392,624 34,894,815 35,379,452 35,840,785 36,272,642 36,657,892 36,981,128 37,260,898 37,525,113 37,789,171 38,045,786 38,280,375 38,487,841 38,687,992 38,877,046 39,038,394 39,176,810 1,293,810,776

5 Tasas específicas de fecundidad y TGF

TEF = nacimientos / mujeres, por grupo de edad y año. El renglón Total (TGF) es la suma de las tasas específicas multiplicada por 5.

5.1 Chunk tasas

  • La función preparar() quita la fila y la columna Total de ambas tablas y ordena los grupos de edad igual en las dos.
  • stopifnot() comprueba que los grupos coincidan entre tablas y que estén todos los años; si no, el documento se detiene con un mensaje específico.
  • Divide las dos matrices elemento a elemento (nacimientos entre mujeres). Si hay división por cero, los resultados Inf o NaN se convierten en NA.
  • Calcula la TGF como colSums(tasas) × 5, es decir, la suma de las tasas por año multiplicada por la amplitud del grupo.
  • Arma tabla_tasas con los ocho grupos más el renglón Total (TGF) y la muestra con 4 decimales.
preparar <- function(tabla) {
  tabla %>%
    filter(grupo_edad != "Total") %>%
    select(-any_of("Total")) %>%
    arrange(match(grupo_edad, orden_edades))
}

nac <- preparar(tabla_nacimientos)
muj <- preparar(tabla_mujeres)

stopifnot(
  "Los grupos de edad no coinciden entre tablas" =
    identical(nac$grupo_edad, muj$grupo_edad),
  "Faltan años en tabla_nacimientos" = all(anios %in% names(nac)),
  "Faltan años en tabla_mujeres"     = all(anios %in% names(muj))
)

tasas <- as.matrix(nac[, anios]) / as.matrix(muj[, anios])
tasas[!is.finite(tasas)] <- NA          # evita Inf/NaN si mujeres = 0

# Total = TGF = (suma de las tasas específicas) * 5
tgf <- colSums(tasas, na.rm = TRUE) * 5

tabla_tasas <- bind_rows(
  data.frame(grupo_edad = nac$grupo_edad, tasas, check.names = FALSE),
  data.frame(grupo_edad = "Total (TGF)", t(tgf), check.names = FALSE)
) %>% as_tibble()

tabla_html(tabla_tasas, digits = 4)
grupo_edad 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025
12-14 0.0002 0.0002 0.0002 0.0002 0.0002 0.0003 0.0003 0.0005 0.0006 0.0007 0.0008 0.0009 0.0013 0.0024 0.0025 0.0029 0.0027 0.0025 0.0024 0.0025 0.0025 0.0025 0.0026 0.0027 0.0028 0.0027 0.0026 0.0027 0.0026 0.0026 0.0025 0.0023 0.0023 0.0022 0.0019 0.0015 0.0015 0.0016 0.0014 0.0012 0.0009
15-19 0.0008 0.0009 0.0010 0.0012 0.0014 0.0019 0.0024 0.0040 0.0059 0.0080 0.0106 0.0142 0.0330 0.0807 0.0814 0.0838 0.0803 0.0784 0.0753 0.0740 0.0732 0.0719 0.0736 0.0762 0.0775 0.0766 0.0778 0.0784 0.0768 0.0737 0.0698 0.0662 0.0661 0.0616 0.0553 0.0477 0.0428 0.0393 0.0346 0.0302 0.0214
20-24 0.0016 0.0017 0.0019 0.0021 0.0027 0.0035 0.0045 0.0076 0.0106 0.0141 0.0184 0.0242 0.0613 0.1641 0.1635 0.1666 0.1596 0.1571 0.1524 0.1486 0.1435 0.1411 0.1392 0.1375 0.1342 0.1320 0.1306 0.1310 0.1277 0.1244 0.1217 0.1161 0.1124 0.1058 0.0971 0.0894 0.0807 0.0773 0.0687 0.0604 0.0447
25-29 0.0017 0.0019 0.0021 0.0023 0.0028 0.0036 0.0046 0.0073 0.0099 0.0129 0.0164 0.0211 0.0538 0.1531 0.1514 0.1537 0.1469 0.1440 0.1394 0.1366 0.1335 0.1314 0.1295 0.1274 0.1232 0.1203 0.1178 0.1147 0.1106 0.1074 0.1063 0.1030 0.1017 0.0981 0.0931 0.0868 0.0786 0.0763 0.0676 0.0594 0.0463
30-34 0.0015 0.0016 0.0017 0.0019 0.0023 0.0030 0.0039 0.0062 0.0084 0.0108 0.0135 0.0166 0.0396 0.1073 0.1048 0.1064 0.1021 0.1011 0.0983 0.0976 0.0957 0.0942 0.0926 0.0914 0.0885 0.0870 0.0855 0.0839 0.0812 0.0797 0.0782 0.0759 0.0744 0.0717 0.0684 0.0648 0.0595 0.0596 0.0539 0.0478 0.0376
35-39 0.0011 0.0012 0.0013 0.0014 0.0018 0.0022 0.0029 0.0044 0.0059 0.0075 0.0094 0.0117 0.0255 0.0594 0.0571 0.0575 0.0543 0.0524 0.0505 0.0493 0.0481 0.0474 0.0471 0.0469 0.0459 0.0452 0.0450 0.0428 0.0416 0.0411 0.0407 0.0399 0.0396 0.0388 0.0374 0.0355 0.0326 0.0326 0.0296 0.0262 0.0206
40-44 0.0006 0.0006 0.0007 0.0006 0.0008 0.0011 0.0014 0.0023 0.0031 0.0039 0.0049 0.0059 0.0111 0.0208 0.0197 0.0196 0.0179 0.0168 0.0160 0.0151 0.0143 0.0139 0.0132 0.0127 0.0123 0.0118 0.0115 0.0116 0.0111 0.0110 0.0108 0.0104 0.0103 0.0102 0.0100 0.0097 0.0090 0.0090 0.0081 0.0071 0.0052
45-49 0.0002 0.0002 0.0002 0.0003 0.0003 0.0004 0.0004 0.0006 0.0008 0.0010 0.0014 0.0016 0.0025 0.0039 0.0036 0.0033 0.0029 0.0026 0.0023 0.0022 0.0020 0.0018 0.0016 0.0015 0.0014 0.0012 0.0011 0.0010 0.0010 0.0009 0.0009 0.0009 0.0009 0.0009 0.0009 0.0009 0.0007 0.0007 0.0006 0.0006 0.0004
Total (TGF) 0.0383 0.0411 0.0460 0.0503 0.0612 0.0802 0.1023 0.1645 0.2258 0.2951 0.3763 0.4813 1.1404 2.9590 2.9198 2.9698 2.8332 2.7750 2.6828 2.6296 2.5645 2.5214 2.4972 2.4815 2.4291 2.3841 2.3602 2.3307 2.2637 2.2042 2.1554 2.0738 2.0382 1.9462 1.8205 1.6808 1.5274 1.4822 1.3224 1.1650 0.8856

6 Gráfica de las tasas específicas, cada cinco años

6.1 Chunk grafica (fig.width=10, fig.height=6)

  • Selecciona los años quinquenales (seq(1985, 2025, by = 5), nueve años) y excluye el renglón de TGF.
  • pivot_longer() pasa los datos a formato largo (grupo de edad, año, tasa) y convierte ambas variables en factores para controlar el orden.
  • ggplot dibuja una línea con puntos por año. group = anio hace que cada año sea una línea continua a través de los grupos de edad, y scale_color_viridis_d asigna una paleta secuencial de colores.
  • La última línea (grafica) es la que hace que la figura aparezca en el HTML.
anios_graf <- seq(anio_ini, anio_fin, by = 5)   # 1985, 1990, ..., 2025

datos_graf <- tabla_tasas %>%
  filter(grupo_edad != "Total (TGF)") %>%
  select(grupo_edad, all_of(as.character(anios_graf))) %>%
  pivot_longer(-grupo_edad, names_to = "anio", values_to = "tasa") %>%
  mutate(
    grupo_edad = factor(grupo_edad, levels = orden_edades),
    anio       = factor(anio, levels = as.character(anios_graf))
  )

grafica <- ggplot(datos_graf,
                  aes(x = grupo_edad, y = tasa, color = anio, group = anio)) +
  geom_line(linewidth = 1) +
  geom_point(size = 2) +
  scale_color_viridis_d(name = "Año", option = "D") +
  labs(
    title    = "Tasas específicas de fecundidad por grupo de edad",
    subtitle = "México, años quinquenales 1985-2025",
    x        = "Grupo de edad de la madre",
    y        = "Nacimientos por mujer"
  ) +
  theme_minimal(base_size = 12) +
  theme(legend.position = "right")

grafica

7 Exportación opcional a Excel

7.1 Chunk exportar (solo si exportar_excel: true)

Con el valor por defecto (false) no se ejecuta. Si se activa, carga openxlsx (solo aquí, así no es necesario tenerlo instalado en otro caso), guarda la gráfica como PNG y crea Fecundidad_1985_2025.xlsx con tres hojas (nacimientos, mujeres y tasas), con la imagen insertada debajo de la tabla de tasas.

# Solo se ejecuta con el parámetro exportar_excel: true
library(openxlsx)

ggsave("Tasas_especificas_fecundidad.png", grafica,
       width = 10, height = 6, dpi = 300, bg = "white")

wb <- createWorkbook()
addWorksheet(wb, "Nacimientos por edad"); writeData(wb, 1, tabla_nacimientos)
addWorksheet(wb, "Mujeres por edad");     writeData(wb, 2, tabla_mujeres)
addWorksheet(wb, "Tasas especificas");    writeData(wb, 3, tabla_tasas)
insertImage(wb, 3, "Tasas_especificas_fecundidad.png",
            width = 10, height = 6, startRow = 13, startCol = 1)
saveWorkbook(wb, "Fecundidad_1985_2025.xlsx", overwrite = TRUE)

8 Notas metodológicas

  • Años recientes subestimados. Las bases NACIM solo incluyen los registros hasta 2025, por lo que los nacimientos de los últimos años (2024-2025) están incompletos por el registro tardío. Sus tasas y su TGF resultan más bajos de lo real.
  • Edad no especificada. Los nacimientos sin edad de la madre, o con edad fuera de 12-49 años, no entran en las tasas (véase el control de calidad). Esto también reduce la TGF.
  • Grupo 12-14. Abarca 3 años de edad, no 5; multiplicarlo por 5 sobrestima ligeramente su aporte a la TGF, aunque la diferencia es pequeña por sus tasas muy bajas.
  • Comparación con cifras oficiales. Las cifras de CONAPO se basan en una conciliación demográfica que corrige el subregistro; el cálculo de este documento usa nacimientos registrados sin corrección, por lo que puede diferir de la serie oficial.
  • Denominador. Población femenina a mitad de año del archivo de CONAPO, renglón nacional (CVE_GEO = 0).