if (!require(archive)) install.packages("archive", repos = "http://cran.r-project.org")
if (!require(arrow)) install.packages("arrow", repos = "http://cran.r-project.org")
if (!require(readxl)) install.packages("readxl", repos = "http://cran.r-project.org")
if (!require(dplyr)) install.packages("dplyr", repos = "http://cran.r-project.org")
if (!require(janitor)) install.packages("janitor", repos = "http://cran.r-project.org")
if (!require(stringr)) install.packages("stringr", repos = "http://cran.r-project.org")
library(archive)
library(arrow)
library(readxl)
library(dplyr)
library(janitor)
library(stringr)
url <- "https://storage.googleapis.com/bktdescargascenso2024/viv_hog_per_censo2024.zip"
zip <- paste0("2025/12/04/", basename(url))
if (!file.exists(zip)) {
download.file(url, zip, mode = "wb")
}
archive_extract(zip, dir = dirname(zip))If this post is useful to you I kindly ask a minimal donation on Buy Me a Coffee. It shall be used to continue my Open Source efforts.
You can send me questions for the blog using this form and subscribe to receive an email when there is a new post.
Un querido amigo me envío el siguiente mensaje: “https://censo2024.ine.gob.cl/resultados/ Salieron por fin para descargar”
Asumiré que quería que actualizara mi post del Censo 2017 en R utilizando estos nuevos datos.
Parto por los puntos que rescato del trabajo de los equipos del INE en la divulgación de los datos censales:
- Cuentan con documentación detallada
- Utilizan formatos abiertos (CSV y parquet)
- No usan el formato REDATAM
Dudo haber influido de manera estadísticamente significativa al cambio en la presentación de los datos. Espero creer que tras años de correspondencia con el INE y mi artículo de crítica constructiva al formato REDATAM, hayan decidido cambiar a formatos más amigables.
Para quienes deseen leer mi crítica, esta corresponde al siguiente artículo:
Vargas Sepúlveda, Mauricio and Barkai, Lital. 2025. “The REDATAM format and its challenges for data access and information creation in public policy.” Data & Policy 7 (January): e18. https://dx.doi.org/10.1017/dap.2025.4.
Los puntos anteriores se ven parcialmente eclipsados por algunos errores en la documentación:
- Menciona archivos tar.gz a la vez que los datos se han liberado comprimidos en zip.
- En distintas partes confunde KB con MB, lo que puede llevar a confusión sobre el tamaño real de los archivos.
- La documentación podría haber sido explicita al momento de proporcionar ejemplos de lectura de los datos y cruce de tablas.
Se podría haber realizado un mejor trabajo proporcionando los datos en una base de datos relacional, como lo es el caso de DuckDB, que proporciona una base de datos SQL embebida, de código abierto, rápida y ligera. Esto habría facilitado el trabajo de análisis de los datos y las asociaciones entre las variables en distintas tablas.
A modo de intentar realizar un aporte, a continuación muestro cómo obtener el porcentaje de población por nivel educacional y región. Para realizar esto debo unir tablas a modo de situar a cada persona en la región donde vive.
Parto por extraer los datos en R usando el paquete “archive” que ahorra bastantes dolores de cabeza con archivos zip codificados para un sistema operativo en específico:
Procedo a leer el diccionario de variables:
dic_personas <- read_excel("2025/12/04/diccionario_variables_censo2024.xlsx", sheet = "tabla_personas") |>
clean_names()
dic_regiones <- read_excel("2025/12/04/diccionario_variables_censo2024.xlsx", sheet = "codigos_territoriales") |>
clean_names()La variable de educación que mide “Logro educativo de acuerdo con la Clasificación Internacional Normalizada de la Educación” es “cine11” en la tabla de personas.
dic_personas |>
filter(nombre_variable == "cine11") |>
> dic_personas |>
+ filter(nombre_variable == "cine11")
# A tibble: 13 × 8
entidad nombre_variable descripcion_de_la_varia…¹ valor etiqueta_de_categoria
<chr> <chr> <chr> <chr> <chr>
1 Persona cine11 Logro educativo de acuer… 1 01: Nunca cursó un p…
2 Persona cine11 Logro educativo de acuer… 2 02: Educación de la …
3 Persona cine11 Logro educativo de acuer… 3 03: Primaria en form…
4 Persona cine11 Logro educativo de acuer… 4 10: Educación primar…
5 Persona cine11 Logro educativo de acuer… 5 14: Educación primar…
6 Persona cine11 Logro educativo de acuer… 6 24: Educación secund…
7 Persona cine11 Logro educativo de acuer… 7 25: Educación secund…
8 Persona cine11 Logro educativo de acuer… 8 35: Educación tercia…
9 Persona cine11 Logro educativo de acuer… 9 46: Grado de educaci…
10 Persona cine11 Logro educativo de acuer… 10 56: Nivel de maestrí…
11 Persona cine11 Logro educativo de acuer… 11 64: Nivel de doctora…
12 Persona cine11 Logro educativo de acuer… 12 98: Educación especi…
13 Persona cine11 Logro educativo de acuer… -99 No respuesta
# ℹ abbreviated name: ¹descripcion_de_la_variable
# ℹ 3 more variables: rango <chr>, universo <chr>, conteo <dbl>La siguiente tabla muestra que, a diferencia del Censo 2017, no necesito asociar cada persona a un hogar y luego el hogar a una región geográfica, ya que la tabla de personas cuenta con la variable “region” que indica la región donde vive cada persona.
personas <- read_parquet("2025/12/04/personas_censo2024.parquet")
glimpse(personas)
> glimpse(personas)
Rows: 18,480,432
Columns: 63
$ id_vivienda <int> 1, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 6, …
$ id_hogar <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, …
$ id_persona <int> 1, 2, 3, 4, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 1, …
$ region <int> 5, 5, 5, 5, 4, 4, 4, 11, 11, 11, 1, 1, 1, 8, …
$ provincia <int> 58, 58, 58, 58, 43, 43, 43, 112, 112, 112, 11…
$ comuna <int> 5802, 5802, 5802, 5802, 4303, 4303, 4303, 112…
$ comuna_bajo_umbral <int> 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 2, 2, 2, 2, …
$ area <int> 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, …
$ tipo_operativo <int> 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, …
$ sexo <int> 2, 1, 2, 2, 1, 2, 1, 2, 1, 1, 1, 2, 1, 2, 1, …
$ edad <int> 80, 52, 45, 8, 69, 65, 58, -66, -66, -66, 73,…
$ edad_quinquenal <int> 80, 50, 45, 5, 65, 65, 55, 30, 55, 5, 70, 70,…
$ parentesco <int> 1, 11, 5, 12, 9, 7, 1, 1, 4, 5, 1, 2, 5, 1, 5…
$ p23_est_civil <int> 6, 8, 8, NA, 1, 1, 8, 2, 2, NA, 1, 1, 8, 8, 8…
$ p24_lug_resid5 <int> 3, 2, 2, 2, 3, 3, 2, 3, 2, 3, 2, 2, 2, 2, 2, …
$ p24_lug_resid5_esp <int> 13117, 5802, 5802, 5802, 4301, 4301, 4303, 10…
$ p25_lug_nacimiento <int> 2, 2, 2, 1, 2, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, …
$ p25_lug_nacimiento_rec <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, …
$ p25_lug_nacimiento_esp <int> 12101, 5101, 13120, 5802, 5109, 4303, 4303, -…
$ p26_llegada_periodo <int> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N…
$ p27_nacionalidad <int> 1, 1, 1, 1, 1, 1, 1, -66, -66, -66, 1, 1, 1, …
$ p27_nacionalidad_esp <int> 152, 152, 152, 152, 152, 152, 152, -66, -66, …
$ p27_nacionalidad_rec <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, …
$ p28_autoid_pueblo <int> 2, 2, 2, 2, 2, 2, 2, 1, 2, 1, 2, 2, 2, 2, 2, …
$ p28_pueblo_pert <int> NA, NA, NA, NA, NA, NA, NA, 1, NA, 1, NA, NA,…
$ p29_afrodescendencia_rec <int> 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, …
$ p29_afrodescendencia <int> 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, …
$ p30_lengua_indigena <int> 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, …
$ p30_lengua_indigena_rec <int> 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, …
$ p31_religion <int> 12, 12, 12, NA, 1, 1, 12, 2, 1, NA, 1, 1, 1, …
$ p31_religion_rec <int> 2, 2, 2, NA, 1, 1, 2, 1, 1, NA, 1, 1, 1, 1, 1…
$ p32a_dificultad_ver <int> 2, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, …
$ p32b_dificultad_oir <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, …
$ p32c_dificultad_mover <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, …
$ p32d_dificultad_cogni <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, …
$ p32e_dificultad_cuidado <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, …
$ p32f_dificultad_comunic <int> 1, 1, 1, 1, 1, 1, 1, 3, 1, 2, 1, 1, 1, 1, 1, …
$ discapacidad <int> 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, …
$ p33_edu_asiste <int> 2, 2, 2, 1, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 1, …
$ asistencia_parv <int> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N…
$ asistencia_basica <int> NA, NA, NA, 1, NA, NA, NA, NA, NA, -66, NA, N…
$ asistencia_media <int> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N…
$ asistencia_superior <int> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N…
$ p37_alfabet <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, …
$ escolaridad <int> 17, 14, 12, 2, 12, 12, 15, 8, 5, 3, 8, 8, 16,…
$ cine11 <int> 9, 6, 6, 3, 6, 6, 6, 5, 3, 3, 5, 5, 6, 5, 5, …
$ sit_fuerza_trabajo <int> 3, 1, 1, NA, 3, 3, 1, 1, 1, NA, 1, 3, 1, 3, 3…
$ p40_cise_rec <int> NA, 1, 2, NA, NA, NA, 1, 2, 1, NA, 1, NA, 2, …
$ depend_econ_deficit_hab <int> 1, 1, 1, 2, 1, 2, 1, 1, 1, 2, 1, 1, 1, 1, 2, …
$ cod_ciuo <int> NA, 7, 2, NA, NA, NA, 7, 5, 7, NA, 1, NA, 3, …
$ cod_caenes <chr> NA, "F", "P", NA, NA, NA, "F", "I", "F", NA, …
$ p44_lug_trab <int> NA, 5, 2, NA, NA, NA, 2, 1, 1, NA, 2, NA, 2, …
$ p44_lug_trab_esp <int> NA, 998, 5802, NA, NA, NA, 4303, 11202, 11202…
$ p45_medio_transporte <int> NA, 2, 3, NA, NA, NA, 2, NA, NA, NA, 1, NA, 2…
$ p46a_tot_hijs_nac <int> 3, NA, 1, NA, NA, 3, NA, 2, NA, NA, NA, 3, NA…
$ p46b_hijas_nac <int> 2, NA, 1, NA, NA, 0, NA, 1, NA, NA, NA, 0, NA…
$ p46c_hijos_nac <int> 1, NA, 0, NA, NA, 3, NA, 1, NA, NA, NA, 3, NA…
$ p47a_tot_hijs_sobrev <int> 3, NA, 1, NA, NA, 2, NA, 2, NA, NA, NA, 3, NA…
$ p47b_hijas_sobrev <int> 2, NA, 1, NA, NA, -99, NA, 1, NA, NA, NA, 0, …
$ p47c_hijos_sobrev <int> 1, NA, 0, NA, NA, -99, NA, 1, NA, NA, NA, 3, …
$ p48_anio_nac_uh <int> 1978, NA, 2015, NA, NA, 1984, NA, 2014, NA, N…
$ p48_mes_nac_uh <int> 7, NA, 9, NA, NA, 6, NA, 12, NA, NA, NA, 10, …
$ div_genero <int> 2, 2, 2, NA, -66, -66, -66, -66, -66, NA, 2, …A partir de estos debo asociar códigos de región a nombres de región. Esto es un punto favorable, podría haber sido mucho peor. Para esto uso el diccionario de variables pero el la unión con la tabla región arroja una advertencia de algunas regiones no tienen una correspondencia uno a uno:
personas_educacion <- personas |>
group_by(region, cine11) |>
summarise(total = n(), .groups = "drop") |>
collect() |>
inner_join(
dic_personas |>
filter(nombre_variable == "cine11") |>
select(cine11 = valor, nivel_educacional = etiqueta_de_categoria) |>
mutate(cine11 = as.integer(cine11))
) |>
inner_join(
dic_regiones |>
select(region = codigo_territorial, nombre_region = territorio) |>
mutate(region = as.integer(region))
)Explorando el diccionario, el problema es el siguiente:
> dic_regiones |>
+ filter(codigo_territorial %in% 1:16) |>
+ group_by(codigo_territorial) |>
+ filter(n() > 1)
# A tibble: 4 × 2
# Groups: codigo_territorial [2]
codigo_territorial territorio
<dbl> <chr>
1 11 Aysén del General Carlos Ibáñez del Campo
2 11 Iquique
3 14 Los Ríos
4 14 Del Tamarugal Dado que Iquique y Aisén se encuentran a 3.600 kilómetros de distancia, asumo que el error está en el diccionario e intento con el código de comuna:
personas_educacion <- personas |>
group_by(comuna, cine11) |>
summarise(total = n(), .groups = "drop") |>
collect() |>
inner_join(
dic_personas |>
filter(nombre_variable == "cine11") |>
select(cine11 = valor, nivel_educacional = etiqueta_de_categoria) |>
mutate(cine11 = as.integer(cine11))
) |>
inner_join(
dic_regiones |>
select(comuna = codigo_territorial, nombre_comuna = territorio) |>
mutate(comuna = as.integer(comuna))
)A partir del código de comuna, asumiendo que este respeta el estándar oficial, puedo asociar cada comuna a una región utilizando mi conocimiento previo del país:
personas_educacion <- personas_educacion |>
mutate(
comuna = str_pad(comuna, width = 5, side = "left", pad = "0"),
region = str_sub(comuna, 1, 2),
nombre_region = case_when(
region == "01" ~ "Región de Arica y Parinacota",
region == "02" ~ "Región de Tarapacá",
region == "03" ~ "Región de Antofagasta",
region == "04" ~ "Región de Atacama",
region == "05" ~ "Región de Coquimbo",
region == "06" ~ "Región de Valparaíso",
region == "07" ~ "Región Metropolitana de Santiago",
region == "08" ~ "Región del Libertador General Bernardo O'Higgins",
region == "09" ~ "Región del Maule",
region == "10" ~ "Región de Ñuble",
region == "11" ~ "Región del Biobío",
region == "12" ~ "Región de La Araucanía",
region == "13" ~ "Región de Los Ríos",
region == "14" ~ "Región de Los Lagos",
region == "15" ~ "Región de Aysén del General Carlos Ibáñez del Campo",
region == "16" ~ "Región de Magallanes y de la Antártica Chilena",
TRUE ~ "Desconocida"
)
)A modo de verificación, tomemos una comuna de altos ingresos y una de bajos ingresos, como lo es el caso de Vitacura y La Pintana:
personas_educacion |>
filter(nombre_comuna == "Vitacura") |>
group_by(nivel_educacional) |>
summarise(n_vitacura = sum(total)) |>
inner_join(
personas_educacion |>
filter(nombre_comuna == "La Pintana") |>
group_by(nivel_educacional) |>
summarise(n_la_pintana = sum(total))
)
> personas_educacion |>
+ filter(nombre_comuna == "Vitacura") |>
+ group_by(nivel_educacional) |>
+ summarise(n_vitacura = sum(total)) |>
+ inner_join(
+ personas_educacion |>
+ filter(nombre_comuna == "La Pintana") |>
+ group_by(nivel_educacional) |>
+ summarise(n_la_pintana = sum(total))
+ )
Joining with `by = join_by(nivel_educacional)`
# A tibble: 13 × 3
nivel_educacional n_vitacura n_la_pintana
<chr> <int> <int>
1 01: Nunca cursó un programa educativo 1916 6463
2 02: Educación de la primera infancia (incluye la for… 4955 8938
3 03: Primaria en forma parcial (sin conclusión del ni… 5738 25842
4 10: Educación primaria (nivel 1) 2443 14269
5 14: Educación primaria (nivel 2), con orientación ge… 6202 43583
6 24: Educación secundaria, con orientación general 14926 33280
7 25: Educación secundaria, con orientación vocacional 842 26427
8 35: Educación terciaria de ciclo corto, con orientac… 4020 7375
9 46: Grado de educación terciaria o nivel equivalente… 32349 6242
10 56: Nivel de maestría, especialización o equivalente… 10567 123
11 64: Nivel de doctorado o equivalente, con orientació… 1077 11
12 98: Educación especial o diferencial 181 1541
13 No respuesta 1204 1327Comparando las proporciones y la cantidad de personas en cada nivel educacional entre ambas comunas, se observa que los códigos de comuna y región han sido correctamente asociados. Vitacura presenta una mayor proporción de personas con niveles educacionales más altos en comparación con La Pintana, lo cual es consistente con las diferencias socioeconómicas entre ambas comunas.
En lo personal me gustaría observar que el próximo presidente de Chile implemente reformas educacionales que lleven a que las personas de La Pintana y otras comunas de bajos puedan acceder a grados como maestría y doctorado si es su interés y hay una necesidad social (o nacional) de becar a personas para cubrir vacíos en áreas donde el país requiere de conocimiento especializado y no en lo que sea que las personas quieran estudiar sin considerar las necesidades del país y la empleabilidad futura de tales becados. De manera similar tengo el deseo de que quienes no acceden a la educación terciaria puedan igualmente acceder a educación que les permita descubrir sus intereses y oficios que les permitan llevar al máximo sus capacidades y talentos.
Espero que este post tan modesto llegue a José Antonio Kast o alguien de su equipo. No busco consultorías pagadas, para eso ya tengo mi nicho en el sector privado. Busco poder apotar a mi país y que este me ayude de vuelta de ser menos gruñón respecto de los errores evidentes que encontré en el censo.