Resumen:
Este estudio analiza trayectorias laborales en la función pública paraguaya a partir de registros administrativos mensuales entre 2015 y 2024, consolidando la información a nivel persona–mes para evitar duplicaciones por conceptos de pago y controlar la estacionalidad. Trabajamos con variables demográficas (sexo, edad), de carrera (antigüedad) y de contexto institucional (tipo de contrato y categoría de institución), además del salario nominal, y elaboramos series de personas únicas por mes. En el período, el empleo público muestra una tendencia de crecimiento moderado con oscilaciones previsibles por calendario y cambios administrativos; la composición por sexo permanece relativamente estable con un leve aumento de la participación femenina, mientras que por tipo de vínculo persiste el predominio de personal Permanente, con los Contratados creciendo pero en niveles inferiores. Las trayectorias salariales por edad y por antigüedad exhiben perfiles cóncavos: aumentos más fuertes al inicio de la vida laboral, mesetas a mitad de carrera y rendimientos decrecientes conforme avanza la antigüedad, lo que es consistente con escalafones y topes de carrera. A lo largo de todos los tramos etarios y de antigüedad, los Permanentes mantienen salarios promedio superiores a los Contratados, lo que sugiere la existencia de premios asociados a la estabilidad, la carrera y/o la composición ocupacional. En el modelo lineal de determinantes (log salario como variable dependiente), el coeficiente para Mujer resulta pequeño y negativo —del orden de unos pocos puntos porcentuales— una vez controlamos por edad, antigüedad (con término cuadrático), tipo de contrato y categoría institucional; la evidencia estadística de ese coeficiente es, además, tenue, lo que sugiere brechas ajustadas acotadas cuando se consideran estas covariables. Finalmente, la descomposición “pooled” de la brecha de género indica que la mayor parte de la diferencia bruta en log-salario se explica por la composición (distribución por contrato, institución y antigüedad), mientras que el componente no explicado aparece de signo opuesto y de magnitud similar, una señal típica de sensibilidad a la parametrización, pero compatible con la idea de que, una vez neutralizadas las diferencias de composición, la brecha residual es pequeña.
El empleo público en Paraguay cumple un doble rol: por un lado, sostiene la provisión de bienes y servicios esenciales (educación, salud, justicia, seguridad, infraestructura); por otro, representa una fracción relevante del mercado laboral y del gasto del Estado. Contar con evidencia cuantitativa sobre quiénes trabajan en la función pública, cómo evoluciona su trayectoria laboral y cómo se remuneran esas trayectorias es clave para diseñar políticas de empleo, carrera administrativa y equidad salarial.
Este estudio analiza las trayectorias laborales en la función pública paraguaya entre 2015 y 2024 utilizando registros administrativos mensuales. Nos enfocamos en cuatro ejes: (i) evolución del empleo público total y su composición por sexo, tipo de vinculación y ámbito institucional; (ii) progresión salarial a lo largo del ciclo de vida (edad) y la carrera (antigüedad); (iii) brechas salariales de género, con medidas robustas y modelos que controlan por características observables; y (iv) insumos prácticos para gestión: indicadores comparables en el tiempo, tableros y gráficos reproducibles.
Metodológicamente, privilegiamos conteos de personas únicas por mes (persona–mes), medidas salariales resistentes a atípicos (mediana y media recortada) y modelos parsimoniosos. Asimismo, normalizamos nombres institucionales y tipologías de cargo, unificamos categorías de contrato (tratando “Comisionados” como “Permanentes” cuando corresponde) y calculamos indicadores de antigüedad consistentes a partir de fechas de ingreso y de corte.
Caracterizar y explicar las trayectorias laborales y salariales de las personas que trabajan en la función pública del Paraguay (2015–2024), identificando patrones de evolución del empleo, progresión salarial, y brechas por sexo, tipo de contrato e institución.
La literatura internacional sobre empleo público y género enfatiza tres fenómenos persistentes: segregación horizontal (concentración de mujeres y varones en sectores distintos), techos de cristal (menor presencia de mujeres en jerarquías superiores) y brechas salariales aun a igual tarea o contrapesadas por diferencias de contrato y antigüedad. En la región, múltiples trabajos han documentado brechas en el sector público similares o algo menores que en el sector privado, pero no inexistentes.
Para Paraguay, la evidencia sistemática con registros administrativos longitudinales es todavía escasa. Este trabajo contribuye con una base consolidada 2015–2024, reglas de depuración explícitas y una batería de indicadores comparables en el tiempo. Además, el uso de medidas salariales robustas, la consolidación persona–mes y la clasificación institucional estandarizada permiten mejorar la calidad de las comparaciones y reducir sesgos por outliers, duplicaciones o heterogeneidad de denominaciones.
Origen: Registros administrativos mensuales de nómina de la Secretaría de la Función Pública (SFP) publicados como datos abiertos. Se complementa con clasificaciones institucionales armonizadas y, cuando corresponde, con índices de precios al consumidor (IPC) oficiales para deflactar salarios y expresar montos en términos reales.
Cobertura temporal: Enero de 2015 a diciembre de 2024.
Unidad de observación bruta: registro de persona–entidad–concepto de pago por mes.
Unidad analítica: persona–mes (deduplicada). Para cada persona en un mes se consolidan: salario total (suma de conceptos), sexo, tipo de contrato, categoría institucional, edad y antigüedad.
Principales variables:
Criterios de calidad y limpieza:
Este conjunto de datos, una vez depurado y armonizado, permite construir indicadores consistentes y trazables sobre la evolución del empleo público, su composición y sus brechas salariales, así como modelos parsimoniosos que ayudan a interpretar los determinantes de la remuneración en la carrera administrativa.
https://datos.sfp.gov.py/data/funcionarios/download
Ruta de trabajo: "G:/Mi unidad/FUNPUBLICOS"
library(data.table)
system.time({
FPfilc2sd <- as.data.table(arrow::read_parquet("D:/FUNPUBLICOS/FPfilc_final.parquet"))
})
## user system elapsed
## 11.02 2.84 14.42
# 0) Orden estable para que unique(by=...) tome el primer registro del mes-persona
setorder(FPfilc2sd, fecha, nroced)
# 1) Preparar columnas para usar GForce (evitar na.rm=TRUE)
# - salario: NAs -> 0
# - edad / antiguedad: NAs -> sentinela muy negativo (max ignorará ese valor)
sent <- -2147483648 # entero mínimo como sentinela
FPfilc2sd[, salario := fcoalesce(salario, 0L)]
FPfilc2sd[, edad_i := ifelse(is.na(edad), sent, as.integer(edad))]
FPfilc2sd[, ant_i := ifelse(is.na(antiguedad), sent, as.integer(antiguedad))]
# 2) SUMA del salario por persona-mes (GForce súper rápida)
FPsum <- FPfilc2sd[, .(salario = sum(salario)), by = .(fecha, nroced)]
# 3) Categóricos "representativos" del mes-persona
# (toma el primer registro de cada (fecha, nroced) gracias al order)
FPcat <- unique(FPfilc2sd[, .(fecha, nroced, sexo, tipopesonalrec, categoria_cf)],
by = c("fecha","nroced"))
# 4) MAX de edad / antigüedad por persona-mes (GForce, sin na.rm)
FPmax <- FPfilc2sd[, .(edad = max(edad_i), antiguedad = max(ant_i)),
by = .(fecha, nroced)]
# 5) Juntar todo (joins por hashing, muy rápidos)
FPfilc2sd <- FPsum[FPcat, on = .(fecha, nroced)][FPmax, on = .(fecha, nroced)]
# 6) Reponer NAs donde el max fue el sentinela
FPfilc2sd[edad == sent, edad := NA_integer_]
FPfilc2sd[antiguedad == sent, antiguedad := NA_integer_]
# 7) (Opcional) recuperar año/mes a partir de fecha (más barato que agrupar por ellos)
FPfilc2sd[, `:=`(anio = as.integer(format(fecha, "%Y")),
mes = as.integer(format(fecha, "%m")))]
# 8) Índices útiles para joins/filtrados posteriores (no necesarios para group by)
setkey(FPfilc2sd, fecha, nroced)
setindex(FPfilc2sd, sexo)
setindex(FPfilc2sd, tipopesonalrec)
setindex(FPfilc2sd, categoria_cf)
# Filtrar rango temporal y normalizar contrato (Comisionados -> Permanentes)
FPdb <- FPfilc2sd[
anio >= 2015 & anio <= 2024
][
, tipopesonalrec := fifelse(tipopesonalrec == "Comisionados", "Permanentes", tipopesonalrec)
]
# Asegurar fecha (primer día del mes)
FPdb[, fecha := as.IDate(sprintf("%d-%02d-01", anio, mes))]
# Asegurar tipos de columnas clave (para evitar problemas de factor/NA)
if (!is.factor(FPdb$tipopesonalrec)) FPdb[, tipopesonalrec := factor(tipopesonalrec)]
if (!is.factor(FPdb$categoria_cf)) FPdb[, categoria_cf := factor(categoria_cf)]
if (!is.character(FPdb$sexo)) FPdb[, sexo := as.character(sexo)]
FPpm
)Sumamos salario por persona-mes y tomamos atributos estables por grupo (primer no-NA). Esto acelera y evita doble conteo cuando una persona tiene varias filas en el mismo mes.
# Auxiliar: primer no-NA preservando tipo (evita el error de fcoalesce con factor)
first_non_na <- function(x) {
i <- which(!is.na(x))[1L]
if (!is.na(i)) return(x[i])
if (is.factor(x)) return(factor(NA, levels = levels(x)))
if (is.character(x)) return(NA_character_)
if (is.integer(x)) return(NA_integer_)
if (is.numeric(x)) return(NA_real_)
return(NA)
}
# Agregar por persona-mes
system.time({
FPpm <- FPdb[
, .(
salario = sum(salario, na.rm = TRUE),
sexo = first_non_na(sexo),
tipopesonalrec = first_non_na(tipopesonalrec),
categoria_cf = first_non_na(categoria_cf),
edad = suppressWarnings(as.integer(max(edad, na.rm = TRUE))),
antiguedad = suppressWarnings(as.integer(max(antiguedad, na.rm = TRUE)))
),
by = .(fecha, anio, mes, nroced)
]
})
## user system elapsed
## 776.69 1.77 771.14
## anio mes fecha cantidad
## <int> <int> <IDat> <int>
## 1: 2015 1 2015-01-01 231397
## 2: 2015 2 2015-02-01 234004
## 3: 2015 3 2015-03-01 235701
## 4: 2015 4 2015-04-01 237294
## 5: 2015 5 2015-05-01 238137
## ---
## 116: 2024 8 2024-08-01 262838
## 117: 2024 9 2024-09-01 261751
## 118: 2024 10 2024-10-01 264124
## 119: 2024 11 2024-11-01 264871
## 120: 2024 12 2024-12-01 264917
# Asegura que 'anio' sea entero
anios_df <- unique(tab1[, .(anio = as.integer(as.character(anio)))])
# Crear rangos anuales en Date
anios_df[, xmin := as.Date(paste0(anio, "-01-01"))]
anios_df[, xmax := as.Date(paste0(anio, "-12-31"))]
# Punto medio del año
anios_df[, xmid := xmin + (xmax - xmin)/2]
# Alternar color de fondo
anios_df[, fill := rep(c("gray90","white"), length.out = .N)]
g1 <- ggplot(tab1, aes(x = fecha, y = cantidad)) +
geom_rect(
data = anios_df,
aes(xmin = xmin, xmax = xmax, ymin = -Inf, ymax = Inf, fill = fill),
inherit.aes = FALSE, alpha = 0.25
) +
scale_fill_identity() +
geom_line(linewidth = 1.1, color = "#1f77b4") +
geom_point(size = 2.3, alpha = 0.7, color = "#1f77b4") +
scale_y_continuous(labels = comma) +
scale_x_date(date_breaks = "6 months", date_labels = "%Y-%m") +
labs(
title = "Evolución de funcionarios únicos (persona-mes)",
x = "Fecha", y = "Personas únicas"
) +
theme_minimal(base_size = 13) +
theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1))
g1
tab_sexo <- FPpm[, .(cantidad = uniqueN(nroced)), by = .(fecha, anio, sexo)]
ggplot(tab_sexo, aes(x = fecha, y = cantidad, color = sexo)) +
geom_line(linewidth = 1.1) +
geom_point(size = 2.3, alpha = 0.7) +
scale_color_manual(values = c("Hombres" = "#1f77b4", "Mujeres" = "#e377c2")) +
scale_y_continuous(labels = comma) +
scale_x_date(date_breaks = "6 months", date_labels = "%Y-%m") +
labs(
title = "Funcionarios únicos por sexo",
x = "Fecha", y = "Personas únicas", color = "Sexo"
) +
theme_minimal(base_size = 13) +
theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1))
tab_cont <- FPpm[, .(n_distinct = uniqueN(nroced)), by = .(fecha, tipopesonalrec)]
ggplot(tab_cont, aes(x = fecha, y = n_distinct, color = tipopesonalrec)) +
geom_line(linewidth = 1.1) +
geom_point(size = 2, alpha = 0.7) +
scale_color_manual(values = c("Permanentes"="#1f77b4","Contratados"="#e377c2")) +
scale_y_continuous(labels = comma) +
scale_x_date(date_breaks = "6 months", date_labels = "%Y-%m") +
labs(
title = "Funcionarios únicos por tipo de contrato",
x = "Fecha", y = "Personas únicas", color = "Contrato"
) +
theme_minimal(base_size = 13) +
theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1))
tab_inst <- FPpm[, .(n_distinct = uniqueN(nroced)), by = .(fecha, categoria_cf)]
ggplot(tab_inst, aes(x = fecha, y = n_distinct, color = categoria_cf)) +
geom_line(linewidth = 1.1) +
geom_point(size = 2, alpha = 0.7) +
scale_y_continuous(labels = comma) +
scale_x_date(date_breaks = "6 months", date_labels = "%Y-%m") +
labs(
title = "Funcionarios únicos por tipo de institución",
x = "Fecha", y = "Personas únicas", color = "Institución"
) +
theme_minimal(base_size = 13) +
theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1))
Usamos
FPpm
(persona-mes) para promedios salariales limpios (sin duplicaciones por conceptos presupuestarios).
tab_trayectorias <- FPpm[
edad >= 18 & edad <= 80 & salario > 0,
.(salario_trim = mean(salario, trim = 0.05), n = .N),
by = .(edad, sexo)
][n > 50]
ggplot(tab_trayectorias, aes(x = edad, y = salario_trim, color = sexo)) +
geom_point(alpha = 0.5, size = 1.8) +
geom_smooth(method = "loess", span = 0.4, linewidth = 1.2, se = FALSE) +
scale_color_manual(values = c("Hombres" = "#1f77b4", "Mujeres" = "#e377c2")) +
scale_y_continuous(labels = comma) +
labs(
title = "Trayectorias salariales por edad",
subtitle = "Media recortada al 95%",
x = "Edad", y = "Salario promedio (Gs.)", color = "Sexo"
) +
theme_minimal(base_size = 13) +
theme(legend.position = "top")
Al observar la evolución del salario promedio por edad separada entre Permanentes y Contratados, se aprecia que las curvas se elevan con rapidez en los primeros años de vida laboral, se aplanan en edades medias y tienden a estabilizarse o incluso a descender levemente hacia edades cercanas a la jubilación. Esta forma cóncava es coherente con procesos de aprendizaje, formalización de competencias y acceso a escalafones en los tramos iniciales, seguidos por topes y/o menor movilidad ascendente en etapas posteriores. La comparación entre regímenes revela de manera persistente que los Permanentes se ubican por encima de los Contratados a lo largo de casi todo el rango etario, con una brecha que suele ampliarse en edades medias —cuando se han acumulado los rendimientos de la carrera, los ascensos o los incentivos por antigüedad— y que se vuelve menos volátil en edades mayores, donde la selección de quienes permanecen en la administración y la composición de puestos se estabiliza. En términos sustantivos, la lectura es clara: la pertenencia a un régimen con carrera formal y mayor estabilidad contractual se asocia a niveles salariales superiores y más estables a lo largo del ciclo de vida, mientras que prolongar vínculos contractuales sin carrera tiende a anclar a las personas en trayectorias con salarios más bajos y con menor progresión relativa.
tab_trayectorias_tc <- FPpm[
edad >= 18 & edad <= 80 & salario > 0,
.(salario_trim = mean(salario, trim = 0.05), n = .N),
by = .(edad, tipopesonalrec)
][n > 50]
ggplot(tab_trayectorias_tc, aes(x = edad, y = salario_trim, color = tipopesonalrec)) +
geom_line(linewidth = 1.2) +
geom_point(size = 2, alpha = 0.6) +
scale_color_manual(values = c("Permanentes"="#1f77b4","Contratados"="#e377c2")) +
scale_y_continuous(labels = comma) +
labs(
title = "Trayectorias salariales por edad según contrato",
subtitle = "Media recortada al 95%",
x = "Edad", y = "Salario promedio (Gs.)", color = "Contrato"
) +
theme_minimal(base_size = 13) +
theme(legend.position = "top")
tab_trayectorias_inst <- FPpm[
edad >= 18 & edad <= 80 & salario > 0,
.(salario_trim = mean(salario, trim = 0.05), n = .N),
by = .(edad, categoria_cf, sexo)
][n > 50]
ggplot(tab_trayectorias_inst, aes(x = edad, y = salario_trim, color = sexo)) +
geom_line(linewidth = 1.1) +
geom_point(size = 1.8, alpha = 0.6) +
scale_color_manual(values = c("Hombres" = "#1f77b4", "Mujeres" = "#e377c2")) +
scale_y_continuous(labels = comma) +
labs(
title = "Trayectorias salariales por edad según institución",
subtitle = "Media recortada al 95%",
x = "Edad", y = "Salario promedio (Gs.)", color = "Sexo"
) +
facet_wrap(~ categoria_cf, scales = "free_y") +
theme_minimal(base_size = 13) +
theme(legend.position = "top")
tab_ant <- FPpm[
antiguedad >= 0 & antiguedad <= 40 & salario > 0,
.(salario_trim = mean(salario, trim = 0.05), n = .N),
by = .(antiguedad, sexo, tipopesonalrec)
][n > 50]
ggplot(tab_ant, aes(x = antiguedad, y = salario_trim, color = sexo)) +
geom_line(linewidth = 1.2) +
geom_point(size = 2, alpha = 0.6) +
scale_color_manual(values = c("Hombres"="#1f77b4","Mujeres"="#e377c2")) +
scale_y_continuous(labels = comma) +
labs(
title = "Trayectorias salariales por antigüedad",
subtitle = "Segmentado por tipo de contrato (facetas)",
x = "Años de antigüedad", y = "Salario promedio (Gs.)", color = "Sexo"
) +
facet_wrap(~ tipopesonalrec) +
theme_minimal(base_size = 13) +
theme(legend.position = "top")
Cuando el eje se define por años de antigüedad en la administración, el patrón vuelve a ser el de un crecimiento acelerado en los primeros años —aproximadamente hasta el quinto año— y posteriormente una pendiente positiva pero decreciente, que termina por aplanarse hacia los quince o veinte años de servicio. Este resultado es consistente con la existencia de escalones de carrera más densos al comienzo (regularizaciones, primeras promociones, acceso a tramos) y con rendimientos marginales menores en etapas avanzadas, cuando la movilidad horizontal o vertical es más esporádica y el tope de tramo se vuelve un límite efectivo. La comparación entre tipos de contrato muestra que, controlando por la misma antigüedad, los Permanentes obtienen en promedio remuneraciones más altas, lo cual refuerza la idea de que el mecanismo contractual y el acceso a escalafones formales importan tanto como la simple acumulación de años. Para fines de política, estas curvas sugieren que las intervenciones que facilitan el tránsito de Contratados a trayectorias con carrera, especialmente en los primeros años, podrían generar ganancias salariales y de productividad relativamente grandes, mientras que para quienes ya poseen alta antigüedad las mejoras tenderán a ser acotadas si no se abren rutas de progresión adicionales.
tab_ant_inst <- FPpm[
antiguedad >= 0 & antiguedad <= 40 & salario > 0,
.(salario_trim = mean(salario, trim = 0.05), n = .N),
by = .(antiguedad, sexo, categoria_cf)
][n > 50]
ggplot(tab_ant_inst, aes(x = antiguedad, y = salario_trim, color = sexo)) +
geom_line(linewidth = 1.2) +
geom_point(size = 2, alpha = 0.6) +
scale_color_manual(values = c("Hombres"="#1f77b4","Mujeres"="#e377c2")) +
scale_y_continuous(labels = comma) +
labs(
title = "Trayectorias salariales por antigüedad según institución",
subtitle = "Media recortada al 95%",
x = "Años de antigüedad", y = "Salario promedio (Gs.)", color = "Sexo"
) +
facet_wrap(~ categoria_cf) +
theme_minimal(base_size = 13) +
theme(legend.position = "top")
## < table of extent 0 >
## [1] "fecha" "anio" "mes" "nroced"
## [5] "salario" "sexo" "tipopesonalrec" "categoria_cf"
## [9] "edad" "antiguedad"
FPpm[, edad_tramo := cut(
edad,
breaks = c(18, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, Inf),
labels = c("18-19","20-24","25-29","30-34","35-39","40-44",
"45-49","50-54","55-59","60-64","65-69","70+"),
right = FALSE
)]
library(forcats)
library(broom)
# Base del modelo desde FPpm
datos_modelo <- FPpm[
salario > 0 & !is.na(sexo) & !is.na(edad_tramo) &
!is.na(antiguedad) & !is.na(tipopesonalrec) & !is.na(categoria_cf),
.(log_salario = log(salario),
mujer = as.integer(sexo == "Mujeres"),
edad_tramo = as.factor(edad_tramo),
antiguedad = as.integer(antiguedad),
tipopesonalrec, categoria_cf)
]
# Referencias explícitas
datos_modelo[, edad_tramo := fct_relevel(edad_tramo, "25-29")]
datos_modelo[, tipopesonalrec := fct_relevel(tipopesonalrec, "Permanentes")]
set.seed(123)
datos_muestra <- as.data.frame(datos_modelo[sample(.N, min(.N, 10000))])
modelo_salarial <- lm(
log_salario ~ mujer + edad_tramo + antiguedad + I(antiguedad^2) +
tipopesonalrec + categoria_cf,
data = datos_muestra
)
# Etiquetado amigable
tidy_raw <- tidy(modelo_salarial)
ref_edad <- levels(datos_muestra$edad_tramo)[1]
lvls_edad <- levels(datos_muestra$edad_tramo)[-1]
lvls_terms <- paste0("edad_tramo", make.names(lvls_edad))
map_edad <- setNames(paste0("Edad ", lvls_edad, " (ref: ", ref_edad, ")"), lvls_terms)
label_term <- function(x){
dplyr::case_when(
x == "(Intercept)" ~ "(Intercept)",
x == "mujer" ~ "Mujer (ref: Hombre)",
x == "antiguedad" ~ "Antigüedad (años)",
x == "I(antiguedad^2)" ~ "Antigüedad²",
startsWith(x,"edad_tramo") ~ dplyr::coalesce(map_edad[x], x),
startsWith(x,"tipopesonalrec") ~ sub("^tipopesonalrec", "Contrato: ", x),
startsWith(x,"categoria_cf") ~ sub("^categoria_cf", "Institución: ", x),
TRUE ~ x
)
}
modelo_resumen <- tidy_raw %>% mutate(term = label_term(term))
modelo_resumen
## # A tibble: 28 × 5
## term estimate std.error statistic p.value
## <chr> <dbl> <dbl> <dbl> <dbl>
## 1 (Intercept) 14.7 0.0575 255. 0
## 2 Mujer (ref: Hombre) -0.0238 0.0135 -1.77 7.70e- 2
## 3 edad_tramo18-19 -1.82 0.0741 -24.6 1.63e-129
## 4 edad_tramo20-24 -0.519 0.0323 -16.0 3.11e- 57
## 5 edad_tramo30-34 0.0520 0.0247 2.10 3.54e- 2
## 6 edad_tramo35-39 0.0255 0.0258 0.988 3.23e- 1
## 7 edad_tramo40-44 0.0619 0.0279 2.22 2.65e- 2
## 8 edad_tramo45-49 0.00892 0.0306 0.292 7.70e- 1
## 9 edad_tramo50-54 -0.0648 0.0335 -1.93 5.36e- 2
## 10 edad_tramo55-59 -0.197 0.0380 -5.18 2.31e- 7
## # ℹ 18 more rows
ggplot(
modelo_resumen %>% filter(term != "(Intercept)"),
aes(x = estimate, y = reorder(term, estimate))
) +
geom_point() +
geom_errorbarh(aes(xmin = estimate - 1.96*std.error,
xmax = estimate + 1.96*std.error), height = 0.2) +
geom_vline(xintercept = 0, linetype = "dashed", color = "red") +
labs(
title = "Determinantes del salario (log) – modelo lineal",
subtitle = "Muestra aleatoria de persona-mes",
x = "Coeficiente", y = NULL
) +
theme_minimal(base_size = 13)
La descomposición “pooled” de la diferencia media en log-salario entre hombres y mujeres—utilizando un conjunto común de coeficientes estimados sin el sexo como covariable— muestra que la brecha bruta observada es pequeña y que la mayor parte de esa diferencia se explica por la composición de las covariables: mujeres y hombres no están distribuidos de la misma manera por tipo de contrato, institución y niveles de antigüedad, y esas diferencias “de asignación” generan la mayor porción de la brecha. El componente no explicado emerge con signo opuesto y magnitud parecida, de forma que ambos términos se compensan casi exactamente, un patrón que suele aparecer cuando se emplean muchas dummies y elecciones de referencia que vuelven sensible la partición entre “explicado” y “no explicado”. Lejos de ser un artefacto inocuo, esta sensibilidad confirma que el contraste de métodos (por ejemplo, una Oaxaca–Blinder estándar con y sin intercepto en la matriz de diseño, covariables centradas y/o efectos fijos de tiempo) es deseable para robustecer la lectura. La implicación sustantiva, no obstante, es estable: gran parte de la (pequeña) brecha bruta se debe a dónde están mujeres y hombres dentro del aparato estatal —contrato e institución— y cuánta antigüedad acumulan, mientras que, una vez neutralizada esa composición, la diferencia residual es exigua.
# Base limpia para descomposición
datos_desc <- FPpm[
salario > 0 & !is.na(sexo) & !is.na(edad_tramo) &
!is.na(antiguedad) & !is.na(tipopesonalrec) & !is.na(categoria_cf),
.(log_salario = log(salario),
sexo = factor(sexo, levels = c("Hombres","Mujeres")),
edad_tramo = factor(edad_tramo),
antiguedad = as.integer(antiguedad),
tipopesonalrec, categoria_cf)
]
# Referencias
datos_desc[, edad_tramo := fct_relevel(edad_tramo, "25-29")]
datos_desc[, tipopesonalrec := fct_relevel(tipopesonalrec, "Permanentes")]
set.seed(123)
datos_desc <- datos_desc[sample(.N, min(.N, 100000))]
# Modelo pooled (sin sexo)
f_cov <- log_salario ~ edad_tramo + antiguedad + I(antiguedad^2) + tipopesonalrec + categoria_cf
mod_pool <- lm(f_cov, data = as.data.frame(datos_desc))
beta_p <- coef(mod_pool)
# Matriz de diseño y medias por sexo
X <- model.matrix(update(f_cov, . ~ . - log_salario), data = as.data.frame(datos_desc))
mu_m <- colMeans(X[datos_desc$sexo=="Hombres", , drop = FALSE])
mu_w <- colMeans(X[datos_desc$sexo=="Mujeres", , drop = FALSE])
# Brechas en log
brecha_bruta <- with(datos_desc, mean(log_salario[sexo=="Hombres"]) - mean(log_salario[sexo=="Mujeres"]))
brecha_explicada <- sum((mu_m - mu_w) * beta_p)
brecha_no_explicada <- brecha_bruta - brecha_explicada
to_pct <- function(d) (exp(d) - 1)
descomposicion <- data.frame(
Componente = c("Brecha bruta", "Explicada por características", "No explicada"),
Valor_log = c(brecha_bruta, brecha_explicada, brecha_no_explicada),
Valor_pct = to_pct(c(brecha_bruta, brecha_explicada, brecha_no_explicada))
)
ggplot(descomposicion, aes(x = Componente, y = Valor_pct, fill = Componente)) +
geom_col() +
geom_text(aes(label = scales::percent(Valor_pct, accuracy = 0.1)),
vjust = -0.5, size = 3.8) +
scale_y_continuous(labels = percent_format(accuracy = 1)) +
scale_fill_brewer(palette = "Set2") +
labs(
title = "Descomposición de la brecha salarial de género",
subtitle = "Escala original (%). Pooled: edad_tramo, antigüedad, contrato, institución",
y = "Diferencia porcentual (Hombres vs. Mujeres)", x = NULL
) +
theme_minimal(base_size = 13) +
theme(legend.position = "none")
# Chequeo de consistencia
stopifnot(all.equal(brecha_bruta, brecha_explicada + brecha_no_explicada, tolerance = 1e-10))
descomposicion
## Componente Valor_log Valor_pct
## 1 Brecha bruta -0.01653439 -0.01639845
## 2 Explicada por características 2.14339849 7.52837202
## 3 No explicada -2.15993288 -0.88466714
En conjunto, los resultados dibujan una imagen coherente y útil para orientar decisiones: el empleo público creció de forma moderada entre 2015 y 2024 y lo hizo con una composición por sexo estable, mientras que la segmentación por tipo de vínculo y por institución explica gran parte de las diferencias salariales observadas. Las trayectorias por edad y por antigüedad confirman que los retornos más altos se concentran en los primeros años y que, sin rutas claras de progresión, las carreras tienden a mesetas con rendimientos decrecientes; en ese marco, los Permanentes se benefician de reglas de carrera y bandas más favorables, al tiempo que los Contratados quedan rezagados si no transitan hacia trayectos formales. Las brechas de género ajustadas que arroja el modelo son pequeñas, y la descomposición sugiere que lo central pasa por la composición: ampliar el acceso de las mujeres a trayectorias de carrera y a instituciones con mayores retornos probablemente reduzca aún más la brecha residual. Desde la perspectiva de política, esto se traduce en priorizar la profesionalización y apertura de escalafones para quienes hoy se encuentran como Contratados, revisar las bandas y los mecanismos de movilidad en categorías institucionales con rezagos, y mantener un monitoreo regular con salarios deflactados y modelos que incorporen efectos de tiempo, de modo a distinguir mejor entre cambios nominales y mejoras reales. # Referencias
```