Semana 8: laboratorio de remuneraciones municipales

Construcción de base, tablas y exportación

Dr.(c) César Marcelo Ávila Fuentes

Universidad Católica de Temuco

2026-06-01

Objetivo

Construir una base de remuneraciones municipales a partir de archivos descargados desde Transparencia Activa.

En este laboratorio vamos a:

  1. cargar archivos CSV
  2. agregar variables identificadoras
  3. estandarizar columnas
  4. unir archivos usando bind_rows()
  5. revisar brevemente un ejemplo de left_join()
  6. construir tablas descriptivas
  7. exportar resultados a Word, Excel e imagen.

1. Preparar el proyecto

La carpeta del proyecto debería tener una estructura parecida a esta:

semana8_remuneraciones/
├── datos_raw/
│   ├── planta_marzo_2026.csv
│   ├── contrata_marzo_26.csv
│   ├── cdt_marzo_26.csv
│   └── honorarios_26.csv
├── datos_clean/
├── outputs/
├── scripts/
└── semana8_laboratorio_remuneraciones.qmd

2. Cargar paquetes

library(dplyr)
library(readr)
library(janitor)
library(stringr)
library(flextable)
library(officer)
library(writexl)
library(gt)

Recordar primero instalar en caso de no contar en la biblioteca con los paquetes antes mencionados.

3. Cargar archivos

Los archivos vienen separados por punto y coma, por eso usaremos read_csv2().

planta <- read_csv2("datos_raw/planta_marzo_2026.csv", locale = locale(encoding = "Latin1")) |>
  clean_names()

contrata <- read_csv2("datos_raw/contrata_marzo_26.csv", locale = locale(encoding = "Latin1")) |>
  clean_names()

cdt <- read_csv2("datos_raw/cdt_marzo_26.csv", locale = locale(encoding = "Latin1")) |>
  clean_names()

honorarios <- read_csv2("datos_raw/honorarios_26.csv", locale = locale(encoding = "Latin1")) |>
  clean_names()

4. Revisar nombres de variables

Antes de limpiar, debemos mirar qué columnas trae cada archivo.

names(planta)
names(contrata)
names(cdt)
names(honorarios)

4. Revisar nombres de variables Planta

Antes de limpiar, debemos mirar qué columnas trae cada archivo.

names(planta)
 [1] "ano"                                                                               
 [2] "mes"                                                                               
 [3] "estamento"                                                                         
 [4] "nombre_completo"                                                                   
 [5] "cargo_o_funcion"                                                                   
 [6] "grado_eus_o_jornada"                                                               
 [7] "calificacion_profesional_o_formacion"                                              
 [8] "region"                                                                            
 [9] "asignaciones_especiales_del_mes_inc_en_rem_bruta"                                  
[10] "remuneracion_bruta_del_mes_incluye_bonos_e_incentivos_asig_especiales_horas_extras"
[11] "remuneracion_liquida_del_mes"                                                      
[12] "rem_adicionales_del_mes_no_inc_en_rem_bruta"                                       
[13] "remuneracion_bonos_incentivos_del_mes_inc_en_rem_bruta"                            
[14] "derecho_a_horas_extraordinarias"                                                   
[15] "montos_y_horas_extraordinarias_diurnas_del_mes_inc_en_rem_bruta"                   
[16] "montos_y_horas_extraordinarias_nocturnas_del_mes_inc_en_rem_bruta"                 
[17] "montos_y_horas_extraordinarias_festivas_del_mes_inc_en_rem_bruta"                  
[18] "fecha_de_inicio_dd_mm_aa"                                                          
[19] "fecha_de_termino_dd_mm_aa"                                                         
[20] "observaciones"                                                                     
[21] "viaticos_del_mes_no_inc_en_rem_bruta"                                              
[22] "x22"                                                                               

4. Revisar nombres de variables Contrata

Antes de limpiar, debemos mirar qué columnas trae cada archivo.

names(contrata)
 [1] "ano"                                                                               
 [2] "mes"                                                                               
 [3] "estamento"                                                                         
 [4] "nombre_completo"                                                                   
 [5] "cargo_o_funcion"                                                                   
 [6] "grado_eus_o_jornada"                                                               
 [7] "calificacion_profesional_o_formacion"                                              
 [8] "region"                                                                            
 [9] "asignaciones_especiales_del_mes_inc_en_rem_bruta"                                  
[10] "remuneracion_bruta_del_mes_incluye_bonos_e_incentivos_asig_especiales_horas_extras"
[11] "remuneracion_liquida_del_mes"                                                      
[12] "rem_adicionales_del_mes_no_inc_en_rem_bruta"                                       
[13] "remuneracion_bonos_incentivos_del_mes_inc_en_rem_bruta"                            
[14] "derecho_a_horas_extraordinarias"                                                   
[15] "montos_y_horas_extraordinarias_diurnas_del_mes_inc_en_rem_bruta"                   
[16] "montos_y_horas_extraordinarias_nocturnas_del_mes_inc_en_rem_bruta"                 
[17] "montos_y_horas_extraordinarias_festivas_del_mes_inc_en_rem_bruta"                  
[18] "fecha_de_inicio_dd_mm_aa"                                                          
[19] "fecha_de_termino_dd_mm_aa"                                                         
[20] "viaticos_del_mes_no_inc_en_rem_bruta"                                              
[21] "observaciones"                                                                     
[22] "x22"                                                                               

4. Revisar nombres de variables Código del Trabajo

Antes de limpiar, debemos mirar qué columnas trae cada archivo.

names(cdt)
 [1] "ano"                                                                               
 [2] "mes"                                                                               
 [3] "nombre_completo"                                                                   
 [4] "cargo_o_funcion"                                                                   
 [5] "grado_eus_o_jornada"                                                               
 [6] "calificacion_profesional_o_formacion"                                              
 [7] "region"                                                                            
 [8] "asignaciones_especiales_del_mes_inc_en_rem_bruta"                                  
 [9] "remuneracion_bruta_del_mes_incluye_bonos_e_incentivos_asig_especiales_horas_extras"
[10] "remuneracion_liquida_del_mes"                                                      
[11] "rem_adicionales_del_mes_no_inc_en_rem_bruta"                                       
[12] "remuneracion_bonos_incentivos_del_mes_inc_en_rem_bruta"                            
[13] "monto_de_desvinculacion_no_inc_en_rem_bruta"                                       
[14] "derecho_a_horas_extraordinarias"                                                   
[15] "montos_y_horas_extraordinarias_del_mes_inc_en_rem_bruta"                           
[16] "viaticos_del_mes_no_inc_en_rem_bruta"                                              
[17] "fecha_de_inicio_dd_mm_aaaa"                                                        
[18] "fecha_de_termino_dd_mm_aaaa"                                                       
[19] "observaciones"                                                                     
[20] "x20"                                                                               

4. Revisar nombres de variables Honorarios

Antes de limpiar, debemos mirar qué columnas trae cada archivo.

names(honorarios)
 [1] "ano"                                 
 [2] "mes"                                 
 [3] "nombre_completo"                     
 [4] "grado_eus_si_corresponde"            
 [5] "descripcion_de_la_funcion"           
 [6] "calificacion_profesional_o_formacion"
 [7] "region"                              
 [8] "honorario_total_bruto_del_mes"       
 [9] "honorario_total_liquido_del_mes"     
[10] "tipo_de_pago"                        
[11] "descripcion_pago"                    
[12] "numero_de_cuotas"                    
[13] "fecha_de_inicio"                     
[14] "fecha_de_termino"                    
[15] "observaciones"                       
[16] "enlace_funciones_desarrolladas"      
[17] "viaticos"                            
[18] "x18"                                 

5. Revisar estructura general

glimpse(planta)

5. Revisar estructura general

glimpse(planta)
Rows: 72
Columns: 22
$ ano                                                                                <dbl> …
$ mes                                                                                <chr> …
$ estamento                                                                          <chr> …
$ nombre_completo                                                                    <chr> …
$ cargo_o_funcion                                                                    <chr> …
$ grado_eus_o_jornada                                                                <dbl> …
$ calificacion_profesional_o_formacion                                               <chr> …
$ region                                                                             <chr> …
$ asignaciones_especiales_del_mes_inc_en_rem_bruta                                   <chr> …
$ remuneracion_bruta_del_mes_incluye_bonos_e_incentivos_asig_especiales_horas_extras <chr> …
$ remuneracion_liquida_del_mes                                                       <chr> …
$ rem_adicionales_del_mes_no_inc_en_rem_bruta                                        <chr> …
$ remuneracion_bonos_incentivos_del_mes_inc_en_rem_bruta                             <chr> …
$ derecho_a_horas_extraordinarias                                                    <chr> …
$ montos_y_horas_extraordinarias_diurnas_del_mes_inc_en_rem_bruta                    <chr> …
$ montos_y_horas_extraordinarias_nocturnas_del_mes_inc_en_rem_bruta                  <chr> …
$ montos_y_horas_extraordinarias_festivas_del_mes_inc_en_rem_bruta                   <chr> …
$ fecha_de_inicio_dd_mm_aa                                                           <chr> …
$ fecha_de_termino_dd_mm_aa                                                          <chr> …
$ observaciones                                                                      <chr> …
$ viaticos_del_mes_no_inc_en_rem_bruta                                               <chr> …
$ x22                                                                                <lgl> …
glimpse(contrata)
Rows: 56
Columns: 22
$ ano                                                                                <dbl> …
$ mes                                                                                <chr> …
$ estamento                                                                          <chr> …
$ nombre_completo                                                                    <chr> …
$ cargo_o_funcion                                                                    <chr> …
$ grado_eus_o_jornada                                                                <dbl> …
$ calificacion_profesional_o_formacion                                               <chr> …
$ region                                                                             <chr> …
$ asignaciones_especiales_del_mes_inc_en_rem_bruta                                   <chr> …
$ remuneracion_bruta_del_mes_incluye_bonos_e_incentivos_asig_especiales_horas_extras <chr> …
$ remuneracion_liquida_del_mes                                                       <chr> …
$ rem_adicionales_del_mes_no_inc_en_rem_bruta                                        <chr> …
$ remuneracion_bonos_incentivos_del_mes_inc_en_rem_bruta                             <chr> …
$ derecho_a_horas_extraordinarias                                                    <chr> …
$ montos_y_horas_extraordinarias_diurnas_del_mes_inc_en_rem_bruta                    <chr> …
$ montos_y_horas_extraordinarias_nocturnas_del_mes_inc_en_rem_bruta                  <chr> …
$ montos_y_horas_extraordinarias_festivas_del_mes_inc_en_rem_bruta                   <chr> …
$ fecha_de_inicio_dd_mm_aa                                                           <chr> …
$ fecha_de_termino_dd_mm_aa                                                          <chr> …
$ viaticos_del_mes_no_inc_en_rem_bruta                                               <chr> …
$ observaciones                                                                      <chr> …
$ x22                                                                                <lgl> …
glimpse(cdt)
Rows: 5
Columns: 20
$ ano                                                                                <dbl> …
$ mes                                                                                <chr> …
$ nombre_completo                                                                    <chr> …
$ cargo_o_funcion                                                                    <chr> …
$ grado_eus_o_jornada                                                                <chr> …
$ calificacion_profesional_o_formacion                                               <chr> …
$ region                                                                             <chr> …
$ asignaciones_especiales_del_mes_inc_en_rem_bruta                                   <chr> …
$ remuneracion_bruta_del_mes_incluye_bonos_e_incentivos_asig_especiales_horas_extras <chr> …
$ remuneracion_liquida_del_mes                                                       <chr> …
$ rem_adicionales_del_mes_no_inc_en_rem_bruta                                        <chr> …
$ remuneracion_bonos_incentivos_del_mes_inc_en_rem_bruta                             <chr> …
$ monto_de_desvinculacion_no_inc_en_rem_bruta                                        <chr> …
$ derecho_a_horas_extraordinarias                                                    <chr> …
$ montos_y_horas_extraordinarias_del_mes_inc_en_rem_bruta                            <chr> …
$ viaticos_del_mes_no_inc_en_rem_bruta                                               <chr> …
$ fecha_de_inicio_dd_mm_aaaa                                                         <chr> …
$ fecha_de_termino_dd_mm_aaaa                                                        <chr> …
$ observaciones                                                                      <chr> …
$ x20                                                                                <lgl> …
glimpse(honorarios)
Rows: 204
Columns: 18
$ ano                                  <dbl> 2026, 2026, 2026, 2026, 2026, 202…
$ mes                                  <chr> "Marzo", "Marzo", "Marzo", "Marzo…
$ nombre_completo                      <chr> "Acuña Cerda, Jenifer Haydee", "A…
$ grado_eus_si_corresponde             <chr> "No asimilado a grado", "No asimi…
$ descripcion_de_la_funcion            <chr> "Prestacion de servicios Programa…
$ calificacion_profesional_o_formacion <chr> "Profesora Educación Fisica", "En…
$ region                               <chr> "Región de La Araucanía", "Región…
$ honorario_total_bruto_del_mes        <chr> "$ 1.462.759", "$ 636.008", "$ 22…
$ honorario_total_liquido_del_mes      <chr> "-", "-", "-", "-", "-", "-", "-"…
$ tipo_de_pago                         <chr> "Pago mensual", "Pago mensual", "…
$ descripcion_pago                     <chr> "-", "-", "-", "-", "-", "-", "-"…
$ numero_de_cuotas                     <chr> "-", "-", "-", "-", "-", "-", "-"…
$ fecha_de_inicio                      <chr> "02/01/2026", "01/01/2026", "05/0…
$ fecha_de_termino                     <chr> "31/12/2026", "31/12/2026", "30/0…
$ observaciones                        <chr> "decreto 415; decreto 874 17/02/2…
$ enlace_funciones_desarrolladas       <chr> "No publica", "No publica", "No p…
$ viaticos                             <chr> "No informa", "No informa", "No i…
$ x18                                  <lgl> NA, NA, NA, NA, NA, NA, NA, NA, N…

6. Agregar identificadores

Cada archivo debe conservar información sobre su origen.

Planta

planta <- planta |>
  mutate(
    municipio = "Vilcún",
    unidad = "Municipalidad",
    tipo_personal = "planta",
    anio = 2026,
    mes = "marzo",
    mes_num = 3,
    periodo = "2026-03"
  )

Contrata

contrata <- contrata |>
  mutate(
    municipio = "Vilcún",
    unidad = "Municipalidad",
    tipo_personal = "contrata",
    anio = 2026,
    mes = "marzo",
    mes_num = 3,
    periodo = "2026-03"
  )

Código del Trabajo

cdt <- cdt |>
  mutate(
    municipio = "Vilcún",
    unidad = "Municipalidad",
    tipo_personal = "codigo_trabajo",
    anio = 2026,
    mes = "marzo",
    mes_num = 3,
    periodo = "2026-03"
  )

Honorarios

honorarios <- honorarios |>
  mutate(
    municipio = "Vilcún",
    unidad = "Municipalidad",
    tipo_personal = "honorarios",
    anio = 2026,
    mes = "marzo",
    mes_num = 3,
    periodo = "2026-03"
  )

7. Estandarizar columnas

Los archivos no tienen exactamente las mismas columnas. Por eso construiremos una versión simplificada de cada base.

Planta simplificada

planta_simple <- planta |>
  transmute(
    municipio,
    unidad,
    tipo_personal,
    anio,
    mes,
    mes_num,
    periodo,
    nombre = nombre_completo,
    estamento = estamento,
    funcion = cargo_o_funcion,
    grado_jornada = grado_eus_o_jornada,
    formacion = calificacion_profesional_o_formacion,
    region = region,
    monto_bruto = remuneracion_bruta_del_mes_incluye_bonos_e_incentivos_asig_especiales_horas_extras,
    monto_liquido = remuneracion_liquida_del_mes,
    fecha_inicio = fecha_de_inicio_dd_mm_aa,
    fecha_termino = fecha_de_termino_dd_mm_aa,
    observaciones = observaciones
  )

Contrata simplificada

contrata_simple <- contrata |>
  transmute(
    municipio,
    unidad,
    tipo_personal,
    anio,
    mes,
    mes_num,
    periodo,
    nombre = nombre_completo,
    estamento = estamento,
    funcion = cargo_o_funcion,
    grado_jornada = grado_eus_o_jornada,
    formacion = calificacion_profesional_o_formacion,
    region = region,
    monto_bruto = remuneracion_bruta_del_mes_incluye_bonos_e_incentivos_asig_especiales_horas_extras,
    monto_liquido = remuneracion_liquida_del_mes,
    fecha_inicio = fecha_de_inicio_dd_mm_aa,
    fecha_termino = fecha_de_termino_dd_mm_aa,
    observaciones = observaciones
  )

Código del Trabajo simplificado

cdt_simple <- cdt |>
  transmute(
    municipio,
    unidad,
    tipo_personal,
    anio,
    mes,
    mes_num,
    periodo,
    nombre = nombre_completo,
    estamento = NA_character_,
    funcion = cargo_o_funcion,
    grado_jornada = grado_eus_o_jornada,
    formacion = calificacion_profesional_o_formacion,
    region = region,
    monto_bruto = remuneracion_bruta_del_mes_incluye_bonos_e_incentivos_asig_especiales_horas_extras,
    monto_liquido = remuneracion_liquida_del_mes,
    fecha_inicio = fecha_de_inicio_dd_mm_aaaa,
    fecha_termino = fecha_de_termino_dd_mm_aaaa,
    observaciones = observaciones
  )

Honorarios simplificada

honorarios_simple <- honorarios |>
  transmute(
    municipio,
    unidad,
    tipo_personal,
    anio,
    mes,
    mes_num,
    periodo,
    nombre = nombre_completo,
    estamento = NA_character_,
    funcion = descripcion_de_la_funcion,
    grado_jornada = grado_eus_si_corresponde,
    formacion = calificacion_profesional_o_formacion,
    region = region,
    monto_bruto = honorario_total_bruto_del_mes,
    monto_liquido = honorario_total_liquido_del_mes,
    fecha_inicio = fecha_de_inicio,
    fecha_termino = fecha_de_termino,
    observaciones = observaciones
  )

8. Preparar tipos de columnas antes de unir

Antes de apilar las bases, conviene asegurar que las columnas comunes tengan el mismo tipo. En estos archivos, por ejemplo, grado_jornada puede venir como número en una base y como texto en otra. Eso hace que bind_rows() se detenga.

Primero generaremos un conjunto, con los nombres de las variables que debiesen ser reconocidas como texto

columnas_texto <- c(
  "municipio", "unidad", "tipo_personal", "mes", "periodo",
  "nombre", "estamento", "funcion", "grado_jornada", "formacion",
  "region", "monto_bruto", "monto_liquido", "fecha_inicio",
  "fecha_termino", "observaciones", "archivo_origen"
)

8. Preparar tipos de columnas antes de unir

Luego vamos a aplicar para cada elemento de esa lista de variables, la opción de ser conocida como texto (character)

planta_simple <- planta_simple |>
  mutate(across(any_of(columnas_texto), as.character))

contrata_simple <- contrata_simple |>
  mutate(across(any_of(columnas_texto), as.character))

cdt_simple <- cdt_simple |>
  mutate(across(any_of(columnas_texto), as.character))

honorarios_simple <- honorarios_simple |>
  mutate(across(any_of(columnas_texto), as.character))

9. Unir archivos con bind_rows()

Como queremos apilar registros, usamos bind_rows().

remuneraciones <- bind_rows(
  planta_simple,
  contrata_simple,
  cdt_simple,
  honorarios_simple
)

10. Revisar base unida

glimpse(remuneraciones)
Rows: 337
Columns: 18
$ municipio     <chr> "Vilcún", "Vilcún", "Vilcún", "Vilcún", "Vilcún", "Vilcú…
$ unidad        <chr> "Municipalidad", "Municipalidad", "Municipalidad", "Muni…
$ tipo_personal <chr> "planta", "planta", "planta", "planta", "planta", "plant…
$ anio          <dbl> 2026, 2026, 2026, 2026, 2026, 2026, 2026, 2026, 2026, 20…
$ mes           <chr> "marzo", "marzo", "marzo", "marzo", "marzo", "marzo", "m…
$ mes_num       <dbl> 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,…
$ periodo       <chr> "2026-03", "2026-03", "2026-03", "2026-03", "2026-03", "…
$ nombre        <chr> "ALFARO PULGAR, PABLO  ESTEBAN", "ALVIAL RIVERA, VIVIANA…
$ estamento     <chr> "Técnico", "Profesional", "Técnico", "Administrativo", "…
$ funcion       <chr> "TÉCNICO ADMINISTRACIÓN MUNICIPAL", "PROFESIONAL OBRAS M…
$ grado_jornada <chr> "14", "11", "16", "17", "7", "9", "15", "17", "17", "7",…
$ formacion     <chr> "Técnico en Amplificación", "Ingeniero Civil", "Técnico …
$ region        <chr> "Región de La Araucanía", "Región de La Araucanía", "Reg…
$ monto_bruto   <chr> "$ 1.737.854", "$ 2.426.876", "$ 1.314.239", "$ 892.019"…
$ monto_liquido <chr> "$", "$", "$", "$", "$", "$", "$", "$", "$", "$", "$", "…
$ fecha_inicio  <chr> "01/06/2015", "03/11/2020", "25/03/2019", "17/11/2025", …
$ fecha_termino <chr> "Indefinido", "Indefinido", "Indefinido", "30/06/2026", …
$ observaciones <chr> "Sin Observaciones", "Sin Observaciones", "Sin Observaci…

10. Revisar base unida

remuneraciones |>
  count(tipo_personal)
# A tibble: 4 × 2
  tipo_personal      n
  <chr>          <int>
1 codigo_trabajo     5
2 contrata          56
3 honorarios       204
4 planta            72

Manejo y modificación de datos

Tabla 1: Tabla: Diferencias entre recodificación, variable derivada y corrección manual
Acción Qué hace Ejemplo
Recodificar Cambia o agrupa valores de una variable existente. 1 = Hombre, 2 = Mujer
Crear variable derivada Construye una nueva variable usando información existente. Obtener sexo desde nombre.
Corrección manual Ajusta casos puntuales luego de revisar errores. Cambiar fila 25 de No clasificado a Mujer.

11. Limpiar montos

summary(remuneraciones$monto_bruto)
   Length     Class      Mode 
      337 character character 
remuneraciones |>
  summarise(
    casos = n(),
    sin_monto_bruto = sum(is.na(monto_bruto)),
    sin_monto_liquido = sum(is.na(monto_liquido))
  )
# A tibble: 1 × 3
  casos sin_monto_bruto sin_monto_liquido
  <int>           <int>             <int>
1   337               0                 0

11. Limpiar montos

Los montos vienen como texto. Necesitamos convertirlos a números.

Como este proceso lo tendremos que repetir, generaremos una función, así podemos replicar este proceso solo escribiendo el nombre de la función posteriormente.

limpiar_monto <- function(x) {
  x |>
    str_remove_all("\\$") |>
    str_remove_all("\\.") |>
    str_trim() |>
    na_if("-") |>
    na_if("") |>
    na_if("No informa") |>
    as.numeric()
}

11. Limpiar montos

remuneraciones <- remuneraciones |>
  mutate(
    monto_bruto = limpiar_monto(monto_bruto),
    monto_liquido = limpiar_monto(monto_liquido)
  )

12. Revisar montos limpios

summary(remuneraciones$monto_bruto)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  47334  500000  885765 1015447 1294552 7008009 
remuneraciones |>
  summarise(
    casos = n(),
    sin_monto_bruto = sum(is.na(monto_bruto)),
    sin_monto_liquido = sum(is.na(monto_liquido))
  )
# A tibble: 1 × 3
  casos sin_monto_bruto sin_monto_liquido
  <int>           <int>             <int>
1   337               0               337

Recodificación derivada

  • Vamos a crear una variable sexo para identificar diferencias entre tipos de funcionarios.

  • Con la información que tenemos… ¿cómo podríamos hacerlo?

  • ¡Exacto! A partir de la variable nombre

Recodificación derivada

  • Para este ejemplo, la base (objeto) se llama remuneraciones. La columna con el nombre completo se llama nombre
remuneraciones <- remuneraciones |>
  mutate(
    nombre_limpio = str_to_lower(nombre),
    nombre_limpio = str_squish(nombre_limpio),
    primer_nombre = word(nombre_limpio, 3)
  )
  • Primero crea una variable nombre_limpio, que tiene todas las letras en minúscula. Esto evita que JUAN, Juan y juan sean tratados como valores distintos.

  • Luego, str_squish elimina los espacios innecesarios que pueen evistir al inicio, al final o entre palabras.

  • Por último, word(nombre_limpio, 3)Extrae la tercer palabra de nombre_limpio. Esto nos permitirá clasificar ese nombre según sexo.

Recodificación derivada

  • Ahora podemos crear un pequeño diccionario manual. Para eso, primero vamos a ver cuáles son los primeros nombres que aparecen en nuestros datos:
tabla_primeros_nombres <- remuneraciones |>
  count(primer_nombre, sort = TRUE)

View(tabla_primeros_nombres)

Recodificación derivada

Construir un diccionario de clasificación

diccionario_sexo <- tibble::tribble(
  ~primer_nombre, ~sexo,
  "maria", "Mujer",
  "juan", "Hombre",
  "mario", "Hombre",
  "belen", "Mujer"
)

Recodificación derivada

  • Construir un diccionario de clasificación
remuneraciones <- remuneraciones |>
  left_join(diccionario_sexo, by = "primer_nombre")

Recodificación derivada

  • Revisar casos sin clasificar
remuneraciones |>
  filter(is.na(sexo)) |>
  count(primer_nombre, sort = TRUE)
# A tibble: 184 × 2
   primer_nombre     n
   <chr>         <int>
 1 jose             10
 2 luis             10
 3 claudio           7
 4 jonathan          7
 5 miguel            7
 6 ana               6
 7 camila            6
 8 carlos            6
 9 viviana           5
10 andrea            4
# ℹ 174 more rows

Recodificación derivada

remuneraciones |>
  count(sexo) |>
  mutate(
    porcentaje = round(n / sum(n) * 100, 1)
  )
# A tibble: 3 × 3
  sexo       n porcentaje
  <chr>  <int>      <dbl>
1 Hombre    14        4.2
2 Mujer      6        1.8
3 <NA>     317       94.1

Situaciones especiales

Supongamos quiero corregir un nombre en particular:

remuneraciones <- remuneraciones |>
  mutate(
    sexo = if_else(
      primer_nombre == "carla",
      "Mujer",
      sexo
    )
  )

#Si necesitas hacerlo para más casos
remuneraciones <- remuneraciones |>
  mutate(
    sexo = case_when(
      primer_nombre == "carlos" ~ "Hombre",
      primer_nombre == "constanza" ~ "Mujer",
      primer_nombre == "alexis" ~ "Hombre",
      TRUE ~ sexo
    )
  )

Situaciones especiales

Podemos también hacerlo a filas especificas. Por ejemplo, si quieres cambiar el sexo de la observación 25:

remuneraciones <- remuneraciones |>
  mutate(
    sexo = if_else(
      row_number() == 25,
      "Mujer",
      sexo
    )
  )

La lógica es: Si la fila es la número 25, cambia sexo a “Mujer”. PAra todas las demás filas, deja el valor original de sexo

remuneraciones <- remuneraciones |>
  mutate(
    formacion_limpia = str_to_lower(formacion),
    formacion_limpia = str_squish(formacion_limpia),
    
    nivel_formacion = case_when(
      str_detect(formacion_limpia, "enseñanza básica|ensenanza basica|estudios básicos|estudios basicos") ~ 
        "Enseñanza básica",
      
      str_detect(formacion_limpia, "enseñanza media|ensenanza media|estudios medios|segundo año enseñanza media|media incompleta") ~ 
        "Enseñanza media",
      
      str_detect(formacion_limpia, "abogad|arquitect|asistente social|trabajador social|trabajadora social|cientista político|cientista politico|comunicador social|constructor civil|contador auditor|contador público|contador publico|educadora|fonoaudiolog|geolog|ingenier|kinesiolog|m[eé]dico|medico|odontolog|periodista|planificador social|profesor|profesora|psicolog|psicopedagog|sociolog|terapeuta ocupacional|veterinario|veterinaria") ~ 
        "Profesional universitario",
      
      str_detect(formacion_limpia, "técnico|tecnico|téc\\.|tec\\.|nivel medio|nivel superior") ~ 
        "Técnico",
      
      str_detect(formacion_limpia, "estudiante|egresad|estudios universitarios|estudios de|estudios pedagogía|estudios pedagogia") ~ 
        "Estudiante / estudios incompletos",
      
      str_detect(formacion_limpia, "auxiliar|maestro|monitor|instructor|mecánico|mecanico|soldador|secretaria") ~ 
        "Oficio / apoyo administrativo",
      
      TRUE ~ "Otro / no clasificado"
    ),
    
    tiene_postgrado_diplomado = case_when(
      str_detect(formacion_limpia, "magister|magíster|diplomado|postitulo|postítulo") ~ "Sí",
      TRUE ~ "No"
    )
  )

13. Tabla de frecuencia por tipo de personal

tabla_tipo <- remuneraciones |>
  count(tipo_personal, name = "registros") |>
  mutate(
    porcentaje = round(registros / sum(registros) * 100, 1)
  )

tabla_tipo
# A tibble: 4 × 3
  tipo_personal  registros porcentaje
  <chr>              <int>      <dbl>
1 codigo_trabajo         5        1.5
2 contrata              56       16.6
3 honorarios           204       60.5
4 planta                72       21.4

14. Tabla descriptiva de remuneraciones

tabla_remuneraciones <- remuneraciones |>
  group_by(tipo_personal) |>
  summarise(
    registros = n(),
    promedio_bruto = round(mean(monto_bruto, na.rm = TRUE), 0),
    mediana_bruto = round(median(monto_bruto, na.rm = TRUE), 0),
    minimo_bruto = min(monto_bruto, na.rm = TRUE),
    maximo_bruto = max(monto_bruto, na.rm = TRUE),
    sin_monto = sum(is.na(monto_bruto)),
    .groups = "drop"
  )

14. Tabla descriptiva de remuneraciones

tabla_remuneraciones
# A tibble: 4 × 7
  tipo_personal registros promedio_bruto mediana_bruto minimo_bruto maximo_bruto
  <chr>             <int>          <dbl>         <dbl>        <dbl>        <dbl>
1 codigo_traba…         5         446342        120448        47334       976221
2 contrata             56        1109228        917666       525133      2540591
3 honorarios          204         662758        636008        53340      1890000
4 planta               72        1981312       1582105        86621      7008009
# ℹ 1 more variable: sin_monto <int>

15. Mejorar tabla para presentación

tabla_presentacion <- tabla_remuneraciones |>
  rename(
    `Tipo de personal` = tipo_personal,
    `Registros` = registros,
    `Promedio bruto` = promedio_bruto,
    `Mediana bruto` = mediana_bruto,
    `Mínimo bruto` = minimo_bruto,
    `Máximo bruto` = maximo_bruto,
    `Sin monto informado` = sin_monto
  )

15. Mejorar tabla para presentación

tabla_presentacion
# A tibble: 4 × 7
  `Tipo de personal` Registros `Promedio bruto` `Mediana bruto` `Mínimo bruto`
  <chr>                  <int>            <dbl>           <dbl>          <dbl>
1 codigo_trabajo             5           446342          120448          47334
2 contrata                  56          1109228          917666         525133
3 honorarios               204           662758          636008          53340
4 planta                    72          1981312         1582105          86621
# ℹ 2 more variables: `Máximo bruto` <dbl>, `Sin monto informado` <int>

16. Convertir a flextable

tabla_flex <- tabla_presentacion |>
  flextable() |>
  set_caption("Tabla 1. Remuneraciones brutas por tipo de personal. Municipalidad de Vilcún, marzo 2026") |>
  autofit()

tabla_flex

Tipo de personal

Registros

Promedio bruto

Mediana bruto

Mínimo bruto

Máximo bruto

Sin monto informado

codigo_trabajo

5

446,342

120,448

47,334

976,221

0

contrata

56

1,109,228

917,666

525,133

2,540,591

0

honorarios

204

662,758

636,008

53,340

1,890,000

0

planta

72

1,981,312

1,582,105

86,621

7,008,009

0

17. Exportar a Word

dir.create("outputs", showWarnings = FALSE)

doc <- read_docx()

doc <- doc |>
  body_add_par("Resultados descriptivos preliminares", style = "heading 1") |>
  body_add_par("Tabla 1. Remuneraciones brutas por tipo de personal", style = "heading 2") |>
  body_add_flextable(tabla_flex) |>
  body_add_par(
    "Fuente: elaboración propia a partir de Transparencia Activa, Municipalidad de Vilcún, marzo 2026.",
    style = "Normal"
  )

print(doc, target = "outputs/tabla_remuneraciones_vilcun.docx")

18. Exportar a Excel

write_xlsx(
  list(
    tabla_tipo_personal = tabla_tipo,
    tabla_remuneraciones = tabla_presentacion
  ),
  path = "outputs/tablas_remuneraciones_vilcun.xlsx"
)

19. Como imagen gt

tabla_gt <- tabla_presentacion |>
  gt() |>
  tab_header(
    title = "Remuneraciones brutas por tipo de personal",
    subtitle = "Municipalidad de Vilcún, marzo 2026"
  ) |>
  tab_source_note(
    source_note = "Fuente: Transparencia Activa."
  )

gtsave(tabla_gt, "outputs/tabla.png")

Otra tabla sobre nivel de formación

tabla_nivel_formacion <- remuneraciones |>
  count(nivel_formacion, sort = TRUE) |>
  mutate(
    porcentaje = round(n / sum(n) * 100, 1)
  )

ft_nivel_formacion <- tabla_nivel_formacion |>
  flextable() |>
  set_header_labels(
    nivel_formacion = "Nivel de formación",
    n = "Frecuencia",
    porcentaje = "Porcentaje"
  ) |>
  colformat_num(
    j = "porcentaje",
    digits = 1,
    suffix = "%"
  ) |>
  theme_booktabs() |>
  autofit()

ft_nivel_formacion

Nivel de formación

Frecuencia

Porcentaje

Profesional universitario

137

40.7%

Técnico

102

30.3%

Enseñanza media

67

19.9%

Oficio / apoyo administrativo

12

3.6%

Otro / no clasificado

9

2.7%

Estudiante / estudios incompletos

6

1.8%

Enseñanza básica

4

1.2%

Tabla cruzada entre formación y tipo de personal

tabla_formacion_personal <- remuneraciones |>
  count(nivel_formacion, tipo_personal) |>
  group_by(nivel_formacion) |>
  mutate(
    porcentaje = round(n / sum(n) * 100, 1)
  ) |>
  ungroup()

ft_formacion_personal <- tabla_formacion_personal |>
  flextable() |>
  set_header_labels(
    nivel_formacion = "Nivel de formación",
    tipo_personal = "Tipo de personal",
    n = "Frecuencia",
    porcentaje = "Porcentaje dentro del nivel"
  ) |>
  colformat_num(
    j = "porcentaje",
    digits = 1,
    suffix = "%"
  ) |>
  theme_vanilla() |>
  autofit()

ft_formacion_personal

Nivel de formación

Tipo de personal

Frecuencia

Porcentaje dentro del nivel

Enseñanza básica

contrata

1

25.0%

Enseñanza básica

honorarios

2

50.0%

Enseñanza básica

planta

1

25.0%

Enseñanza media

codigo_trabajo

1

1.5%

Enseñanza media

contrata

20

29.9%

Enseñanza media

honorarios

27

40.3%

Enseñanza media

planta

19

28.4%

Estudiante / estudios incompletos

contrata

1

16.7%

Estudiante / estudios incompletos

honorarios

5

83.3%

Oficio / apoyo administrativo

honorarios

10

83.3%

Oficio / apoyo administrativo

planta

2

16.7%

Otro / no clasificado

honorarios

8

88.9%

Otro / no clasificado

planta

1

11.1%

Profesional universitario

codigo_trabajo

4

2.9%

Profesional universitario

contrata

11

8.0%

Profesional universitario

honorarios

91

66.4%

Profesional universitario

planta

31

22.6%

Técnico

contrata

23

22.5%

Técnico

honorarios

61

59.8%

Técnico

planta

18

17.6%

20. Guardar base limpia

dir.create("datos_clean", showWarnings = FALSE)

saveRDS(
  remuneraciones,
  file = "datos_clean/remuneraciones_vilcun_marzo_2026.rds"
)

21. Ejemplo breve de left_join()

Ahora imaginemos que queremos agregar información externa del municipio.

datos_municipio <- tibble(
  municipio = "Vilcún",
  provincia = "Cautín",
  region_administrativa = "La Araucanía",
  tipo_comuna = "Municipio de referencia para ejercicio"
)

Usamos left_join() porque queremos agregar columnas, no filas.

remuneraciones_contexto <- remuneraciones |>
  left_join(datos_municipio, by = "municipio")

21. Ejemplo breve de left_join()

Revisamos:

remuneraciones_contexto |>
  select(municipio, provincia, region_administrativa, tipo_comuna, tipo_personal) |>
  head()
# A tibble: 6 × 5
  municipio provincia region_administrativa tipo_comuna            tipo_personal
  <chr>     <chr>     <chr>                 <chr>                  <chr>        
1 Vilcún    Cautín    La Araucanía          Municipio de referenc… planta       
2 Vilcún    Cautín    La Araucanía          Municipio de referenc… planta       
3 Vilcún    Cautín    La Araucanía          Municipio de referenc… planta       
4 Vilcún    Cautín    La Araucanía          Municipio de referenc… planta       
5 Vilcún    Cautín    La Araucanía          Municipio de referenc… planta       
6 Vilcún    Cautín    La Araucanía          Municipio de referenc… planta       

22. Diferencia práctica

nrow(remuneraciones)
[1] 337
nrow(remuneraciones_contexto)
[1] 337

El número de filas no cambia, porque left_join() agregó columnas de contexto.

23. Actividad práctica

Cada grupo debe adaptar el script a su municipio.

Debe entregar una carpeta con:

outputs/
├── tabla_remuneraciones_municipio.docx
├── tablas_remuneraciones_municipio.xlsx
└── tabla_remuneraciones_municipio.png

datos_clean/
└── remuneraciones_municipio_marzo_2026.rds

24. Interpretación breve

Escriba un párrafo de 5 a 7 líneas que responda:

  1. ¿qué muestra la tabla?
  2. ¿qué tipo de personal concentra más registros?
  3. ¿qué diferencias se observan en remuneración bruta?
  4. ¿qué limitación debe considerarse?

Cierre

El objetivo de este laboratorio no es solo producir una tabla.

El objetivo es construir una base trazable, ordenada y reproducible, que luego pueda alimentar el póster descriptivo.

¿Dónde estamos en el curso?

La semana anterior trabajamos el paso desde archivos descargados a una base analizable:

Archivos descargados

Variables identificadoras

Base limpia y unida

Tablas descriptivas

Esta semana avanzamos al siguiente paso:

Tablas descriptivas

Gráficos

Hallazgos

Póster

Propósito de la clase

  • El objetivo de esta clase no es solamente aprender comandos de ggplot2.

  • El objetivo es aprender a transformar resultados descriptivos en gráficos claros, interpretables y útiles para un póster.

Idea central

Un gráfico no es decoración.

Un gráfico es una forma de comunicar un patrón de los datos.

Conexión con la Evaluación 2

La Evaluación 2 será un póster científico descriptivo. Debe incluir:

  • contexto comunal
  • pregunta descriptiva orientadora
  • fuente y datos utilizados
  • tabla clave
  • 2 a 3 gráficos
  • hallazgos principales
  • limitaciones
  • respaldo técnico reproducible.

Calendario ajustado

Semana Actividad
Semana 11 Gráficos descriptivos y avance del póster
Semana 12 Avance en la construcción de póster
Semana 13 Entrega digital final del póster y respaldo técnico
Semana 14 Jornada de pósters impresos abierta a la comunidad

Antes de graficar

Antes de hacer cualquier gráfico, debemos tener claro:

  1. qué pregunta descriptiva queremos responder
  2. qué variable o indicador vamos a usar
  3. qué tipo de gráfico corresponde
  4. qué resultado queremos destacar
  5. qué limitación debemos declarar.

Contexto comunal

  • El póster debe partir situando la comuna.

  • No basta con mostrar remuneraciones. También hay que indicar en qué tipo de comuna se está observando el fenómeno.

  • La sección de contexto comunal debe incluir:

    • mapa de ubicación
    • número de habitantes (Fuente: INE)
    • superficie en km² (Fuente: SINIM)
    • ruralidad (Fuente: INE)
    • pobreza comunal (Fuente: CASEN)
    • dependencia del Fondo Común Municipal (Fuente:SINIM)
    • alcalde, tendencia política y continuidad/cambio durante el periodo. (Fuente: SERVEL)

Para qué sirve el contexto

El contexto comunal permite interpretar los datos con más cuidado.

No es lo mismo observar remuneraciones en:

  • una comuna urbana y grande
  • una comuna rural y extensa
  • una comuna con alta dependencia del Fondo Común Municipal
  • una comuna con alta pobreza
  • una comuna que cambió de alcalde durante el periodo.

Importante

Estas variables contextualizan. No deben ser tratadas como causas en este ejercicio.

Tabla o gráfico: ¿cuándo usar cada uno?

Situación Mejor opción
Mostrar cifras exactas Tabla
Comparar categorías Gráfico de barras
Mostrar evolución temporal Gráfico de líneas
Mostrar distribución de montos Histograma o boxplot
Mostrar pocos indicadores contextuales Ficha o tabla breve
Mostrar ubicación territorial Mapa
Sintetizar resultados principales Hallazgos con cifras

Gramática básica de ggplot2

Un gráfico en ggplot2 se construye por capas.

ggplot(datos, aes(x = variable_x, y = variable_y)) +
  geom_tipo_de_grafico() +
  labs(
    title = "Título",
    subtitle = "Subtítulo",
    x = "Eje X",
    y = "Eje Y",
    caption = "Fuente"
  )

Elementos centrales

Elemento Función
data Base de datos que se usará
aes() Variables que se asignan a los ejes o atributos visuales
geom_col() Barras con valores ya calculados
geom_line() Línea para evolución temporal
geom_boxplot() Distribución por grupo
geom_histogram() Distribución de una variable numérica
labs() Títulos, ejes y fuente
ggsave() Exportar gráfico

Gráfico de barras

Sirve para comparar categorías.

Ejemplo de pregunta:

¿Qué tipo de vínculo contractual concentra más registros en la Municipalidad de Vilcún?

library(tidyverse)
remuneraciones |>
  count(tipo_personal) |>
  ggplot(aes(x = tipo_personal, y = n)) +
  geom_col() +
  labs(
    title = "Registros por tipo de personal",
    subtitle = "Municipalidad de Vilcún, marzo 2026",
    x = "Tipo de personal",
    y = "Número de registros",
    caption = "Fuente: Transparencia Activa."
  )

Podemos editar elementos de ggplot2

  • Recomendación, visitar el repositorio de ggplot2 https://r-graph-gallery.com/

  • También podemos modificar los colores.

  • En ggplot2, se puede identificar con el nombre, pero también con un Hex code. Los códigos hexadecimal se ven así: #2765F5

  • Para encontrar su Hex code, pueden visitar: https://htmlcolorcodes.com

library(tidyverse)
library(scales)

grafico_tipo_personal <- remuneraciones |>
  count(tipo_personal) |>
  mutate(
    porcentaje = n / sum(n),
    tipo_personal = fct_reorder(tipo_personal, n)
  ) |>
  ggplot(aes(x = tipo_personal, y = n)) +
  geom_col(
    fill = "#1F4E79",
    width = 0.68
  ) +
  geom_text(
    aes(
      label = paste0(n, " registros\n", percent(porcentaje, accuracy = 0.1))
    ),
    hjust = -0.10,
    size = 3.8,
    lineheight = 0.9
  ) +
  coord_flip() +
  scale_y_continuous(
    expand = expansion(mult = c(0, 0.18))
  ) +
  labs(
    title = "Registros por tipo de personal",
    subtitle = "Municipalidad de Vilcún, marzo 2026",
    x = NULL,
    y = "Número de registros",
    caption = "Fuente: elaboración propia con datos de Transparencia Activa."
  ) +
  theme_minimal(base_size = 13) +
  theme(
    plot.title = element_text(
      face = "bold",
      size = 18
    ),
    plot.subtitle = element_text(
      size = 12,
      color = "grey30"
    ),
    axis.text.y = element_text(
      size = 11,
      face = "bold"
    ),
    axis.text.x = element_text(
      size = 10,
      color = "grey35"
    ),
    axis.title.x = element_text(
      size = 11,
      margin = margin(t = 8)
    ),
    panel.grid.major.y = element_blank(),
    panel.grid.minor = element_blank(),
    plot.caption = element_text(
      size = 9,
      color = "grey35",
      hjust = 0
    ),
    plot.margin = margin(12, 25, 12, 12)
  )

grafico_tipo_personal

geom_col() y geom_bar()

Hay que distinguir:

Comando Cuándo usarlo
geom_bar() Cuando quiero que R cuente automáticamente los casos
geom_col() Cuando ya calculé el valor que quiero graficar

Para este curso, muchas veces usaremos geom_col(), porque antes construiremos tablas con count() o summarise().

Gráfico de barras con porcentaje

Si la pregunta es por composición, no basta con contar.

Conviene calcular porcentajes.

tabla_tipo <- remuneraciones |>
  count(tipo_personal, name = "registros") |>
  mutate(
    porcentaje = registros / sum(registros) * 100
  )

ggplot(tabla_tipo, aes(x = tipo_personal, y = porcentaje)) +
  geom_col() +
  labs(
    title = "Composición porcentual del personal informado",
    subtitle = "Municipalidad de Vilcún, marzo 2026",
    x = "Tipo de personal",
    y = "Porcentaje del total de registros",
    caption = "Fuente: Transparencia Activa."
  )

Interpretación de barras

Un gráfico de barras permite decir:

  • qué categoría tiene más casos
  • qué categoría tiene menos casos
  • qué tan concentrada está la distribución
  • qué proporción representa cada tipo de personal.

No permite decir:

  • que un tipo de contrato causa otro fenómeno
  • que una modalidad es mejor o peor.

Boxplot

Sirve para comparar la distribución de una variable numérica entre grupos.

Ejemplo de pregunta:

¿Cómo se distribuyen los montos brutos informados según tipo de vínculo?

ggplot(remuneraciones, aes(x = tipo_personal, y = monto_bruto)) +
  geom_boxplot() +
    scale_y_continuous(
    labels = label_number(
      prefix = "$",
      big.mark = ".",
      decimal.mark = ",",
      accuracy = 1
    )
  ) +
  labs(
    title = "Distribución de remuneraciones brutas por tipo de personal",
    subtitle = "Municipalidad de Vilcún, marzo 2026",
    x = "Tipo de personal",
    y = "Monto bruto informado",
    caption = "Fuente: Transparencia Activa."
  )
library(tidyverse)
library(scales)

grafico_boxplot_remuneraciones_horizontal <- remuneraciones |>
  mutate(
        tipo_personal = case_when(
      tipo_personal == "codigo_trabajo" ~ "Código del Trabajo",
      tipo_personal == "contrata" ~ "Contrata",
      tipo_personal == "planta" ~ "Planta",
      tipo_personal == "honorarios" ~ "Honorarios",
      TRUE ~ tipo_personal
    ),
    tipo_personal = fct_reorder(tipo_personal, monto_bruto, .fun = median, na.rm = TRUE)
  ) |>
  ggplot(aes(x = tipo_personal, y = monto_bruto)) +
  geom_boxplot(
    fill = "#1F4E79",
    color = "grey20",
    alpha = 0.80,
    width = 0.60,
    outlier.color = "#C00000",
    outlier.alpha = 0.55,
    outlier.size = 1.8
  ) +
  coord_flip() +
  scale_y_continuous(
    labels = label_number(
      prefix = "$",
      big.mark = ".",
      decimal.mark = ",",
      accuracy = 1
    )
  ) +
  labs(
    title = "Distribución de remuneraciones brutas por tipo de personal",
    subtitle = "Municipalidad de Vilcún, marzo 2026",
    x = NULL,
    y = "Monto bruto informado",
    caption = "Fuente: elaboración propia con datos de Transparencia Activa."
  ) +
  theme_minimal(base_size = 13) +
  theme(
    plot.title = element_text(face = "bold", size = 17, color = "grey15"),
    plot.subtitle = element_text(size = 12, color = "grey35", margin = margin(b = 10)),
    axis.text.y = element_text(face = "bold", size = 10, color = "grey25"),
    axis.text.x = element_text(size = 10, color = "grey35"),
    axis.title.x = element_text(size = 11, margin = margin(t = 8)),
    panel.grid.major.y = element_blank(),
    panel.grid.minor = element_blank(),
    plot.caption = element_text(hjust = 0, size = 9, color = "grey40"),
    plot.margin = margin(12, 18, 12, 12)
  )

grafico_boxplot_remuneraciones_horizontal

¿Cómo leer un boxplot?

Un boxplot permite observar:

  • valores centrales
  • dispersión
  • diferencias entre grupos
  • valores extremos.

Advertencia

En este caso, las diferencias entre tipos de personal deben interpretarse con cautela, porque no necesariamente corresponden a cargos, jornadas o funciones equivalentes.

Histograma

Sirve para observar la distribución general de una variable numérica.

ggplot(remuneraciones, aes(x = monto_bruto)) +
  geom_histogram(bins = 30) +
  labs(
    title = "Distribución de remuneraciones brutas informadas",
    subtitle = "Municipalidad de Vilcún, marzo 2026",
    x = "Monto bruto",
    y = "Número de registros",
    caption = "Fuente: Transparencia Activa."
  )
library(tidyverse)
library(scales)

grafico_hist_remuneraciones <- ggplot(remuneraciones, aes(x = monto_bruto)) +
  geom_histogram(
    bins = 30,
    fill = "#7675BF",
    color = "white",
    linewidth = 0.3,
    alpha = 0.85
  ) +
  geom_vline(
    aes(xintercept = median(monto_bruto, na.rm = TRUE)),
    linetype = "dashed",
    linewidth = 0.9,
    color = "#C00000"
  ) +
  scale_x_continuous(
    labels = label_number(
      prefix = "$",
      big.mark = ".",
      decimal.mark = ",",
      accuracy = 1
    )
  ) +
  labs(
    title = "Distribución de remuneraciones brutas informadas",
    subtitle = "Municipalidad de Vilcún, marzo 2026",
    x = "Monto bruto informado",
    y = "Número de registros",
    caption = "Fuente: elaboración propia con datos de Transparencia Activa.\nNota: la línea roja segmentada indica la mediana."
  ) +
  theme_minimal(base_size = 13) +
  theme(
    plot.title = element_text(
      face = "bold",
      size = 17,
      color = "grey15"
    ),
    plot.subtitle = element_text(
      size = 12,
      color = "grey35",
      margin = margin(b = 10)
    ),
    axis.text.x = element_text(
      size = 10,
      color = "grey35",
      angle = 25,
      hjust = 1
    ),
    axis.text.y = element_text(
      size = 10,
      color = "grey35"
    ),
    axis.title.x = element_text(
      size = 11,
      margin = margin(t = 8)
    ),
    axis.title.y = element_text(
      size = 11,
      margin = margin(r = 8)
    ),
    panel.grid.minor = element_blank(),
    panel.grid.major.y = element_line(color = "grey88"),
    panel.grid.major.x = element_blank(),
    plot.caption = element_text(
      hjust = 0,
      size = 9,
      color = "grey40"
    ),
    plot.margin = margin(12, 18, 12, 12)
  )

grafico_hist_remuneraciones

Hallazgos: estructura sugerida

Cada hallazgo debería tener:

  1. resultado observado
  2. cifra concreta
  3. interpretación descriptiva
  4. cautela o límite si corresponde.

Ejemplo:

El personal a contrata concentra XX registros, equivalente al XX% del total informado. Esto sugiere que, dentro de los registros disponibles, este vínculo tiene un peso relevante en la estructura municipal observada. Sin embargo, este resultado depende de los archivos disponibles y del periodo analizado.

Errores comunes en gráficos

Error Por qué es problema
No poner fuente El lector no sabe de dónde vienen los datos
No indicar periodo El resultado queda descontextualizado
Usar gráfico circular con muchas categorías Se vuelve difícil de leer
Graficar montos sin limpiarlos R puede ordenar o calcular mal
Usar nombres técnicos en ejes El público no entiende la variable
Saturar el gráfico con etiquetas Pierde legibilidad
Interpretar causalmente Excede el objetivo descriptivo
Usar colores sin criterio Confunde más de lo que ayuda

Buenas prácticas visuales

Para el póster:

  • usar títulos descriptivos
  • evitar exceso de texto
  • preferir gráficos simples
  • usar máximo 2 o 3 gráficos principales
  • no repetir la misma información en gráfico, tabla y texto
  • agregar fuente y periodo
  • usar notas cuando haya limitaciones importantes
  • mantener coherencia visual entre gráficos.

¿Qué debe tener cada gráfico?

Todo gráfico que vaya al póster debe tener:

Elemento Pregunta que responde
Título ¿Qué muestra el gráfico?
Subtítulo ¿De qué comuna y periodo se trata?
Ejes claros ¿Qué representa cada dimensión?
Fuente ¿De dónde vienen los datos?
Nota si corresponde ¿Qué cuidado debe tener el lector?

Del gráfico al póster

Cada gráfico debe cumplir una función.

Elemento del póster Función
Contexto comunal Situar el caso
Pregunta descriptiva Orientar la lectura
Fuente y datos Explicar de dónde vienen los datos
Tabla clave Mostrar cifras precisas
Gráfico 1 Mostrar composición
Gráfico 2 Mostrar evolución o comparación
Hallazgos Sintetizar mensajes principales
Limitaciones Reconocer alcances del análisis

Estructura esperada del póster

Título
Autores/as
Universidad Católica de Temuco

1. Contexto comunal + mapa
2. Pregunta descriptiva orientadora
3. Fuente y datos utilizados
4. Resultados descriptivos
   - gráfico 1
   - gráfico 2
   - tabla clave
5. Hallazgos principales
6. Limitaciones
7. Fuentes y respaldo técnico

Cierre de cátedra

Mensaje final

Un buen gráfico no es el que se ve más complejo.

Es el que permite entender rápidamente un resultado descriptivo, con fuente clara, periodo definido y una interpretación prudente.

Gráfico de líneas

Sirve para mostrar evolución temporal.

Ejemplo de pregunta:

¿Cómo ha evolucionado la remuneración bruta mediana por tipo de personal entre marzo de 2023 y marzo de 2026?

tabla_tiempo <- remuneraciones |>
  group_by(fecha_periodo, tipo_personal) |>
  summarise(
    mediana_bruto = median(monto_bruto, na.rm = TRUE),
    .groups = "drop"
  )

ggplot(tabla_tiempo, aes(x = fecha_periodo, y = mediana_bruto, group = tipo_personal)) +
  geom_line() +
  geom_point() +
  labs(
    title = "Evolución de la remuneración bruta mediana",
    subtitle = "Según tipo de personal",
    x = "Periodo",
    y = "Monto bruto mediano",
    caption = "Fuente: Transparencia Activa."
  )

Cuidado con el tiempo

Para gráficos de evolución, el periodo debe estar bien ordenado.

No basta con tener mes = "marzo" o periodo = "2026-03" como texto.

Conviene crear una fecha:

remuneraciones <- remuneraciones |>
  mutate(
    fecha_periodo = as.Date(paste0(periodo, "-01"))
  )

Esto permite que R ordene los meses correctamente.

tabla_tiempo <- remuneraciones |>
  group_by(fecha_periodo, tipo_personal) |>
  summarise(
    mediana_bruto = median(monto_bruto, na.rm = TRUE),
    .groups = "drop"
  )

ggplot(tabla_tiempo, aes(x = fecha_periodo, y = mediana_bruto, group = tipo_personal)) +
  geom_line() +
  geom_point() +
  labs(
    title = "Evolución de la remuneración bruta mediana",
    subtitle = "Según tipo de personal",
    x = "Periodo",
    y = "Monto bruto mediano",
    caption = "Fuente: Transparencia Activa."
  )

Cierre de cátedra

Mensaje final

Un buen gráfico no es el que se ve más complejo.

Es el que permite entender rápidamente un resultado descriptivo, con fuente clara, periodo definido y una interpretación prudente.