library(tidyverse)
library(truncnorm)
library(knitr)
library(kableExtra)
library(writexl)
Observatorio Productividad Laboral en Costa Rica
Documento 1. Base de datos sintética
6 de agosto 2025
Preparado por Jorge Cornick para OIT
Contacto: jcornick@drpresearch.com
Introduccción
Como un primer paso en el desarrollo del Observatorio de la Productividad Laboral en Costa Rica, y tomando en cuenta que el Banco Central de Costa Rica ha indicado que la elaboración de la base de datos que será la materia prima fundamental del Observatorio demorará varios meses, se ha desarrollado una base de datos sintética, cuya estructura es una versión simplificada de la que tendrá la base de datos real. El propósito es poder utilizar esta base de datos en la elaboración de un segundo documento, una Guía de Análisis de la Base de Datos del OPL-CR, que podrá utilizarse para:
- Entrenar al personal que eventualmente asuma el mantenimiento del Observatorio y la base de datos real
- Ilustrar el tipo de análisis exploratorio que es posible realizar a partir de esta base de datos y a partir de ello servir como base para discutir tanto con la OIT como con la UCCAEP posibles modificaciones a la estructura de la base de datos y al modelo de análisis.
- Facilitar la discusión con el personal del BCCR sobre los aspectos técnicos de la base de datos real a ser construida en los próximos meses.
En este informe se documenta detalladamente el proceso de construcción de la base de datos sintética. Más allá de la utilidad de la base de datos sintética como tal, el estudio del procedimiento utilizado para su construcción facilitará la construcción de escenarios una vez que se cuente con la base de datos real, toda vez que estos escenarios demandarán de la construcción de datos simulados a partir de las hipótesis que se quieran examinar. El estudio de este documento facilitará, a futuros administradores y usuarios, la creación de este tipo de datos.
Advertencia. Este documento está escrito asumiendo que administradores y usuarios tienen una cierta familiaridad con R y Rstudio, las herramientas usadas para construirlo. Explica con detalle las tareas realizadas, pero no constituye una introducción a R.
Nota sobre formatos Este documento está disponible tanto en formato HTML con en formato PDF, pero una parte importante de su funcionalidas se pierde en la version PDF, por lo que se sugiere utilizar siemrpe la versión en HTML, salvo cuando por circunstancias especiales resulte absolutamente imposible.
Tareas preliminares
Empezamos cargando los paquetes de R necesarios para la ejecución del código que sigue. Futuros usuarios deberán instalar y cargar estas librerías no solo para poder correr el código que se presenta en este documento, sino para poder realizar ejercicios de exploración y simulación tanto con la base de datos sintética, que aquí se genera, como con bases de datos reales sobre productividad laboral.
Los lectores que accedan a este documento en formato HTML con frecuencia encontraran un triángulo gris en la página. Esto sucede cuando se ha usado código R para generar algún resultado, pero en la página solo se muestra el resultado. Al hacer “click” sobre el triángulo el código se despliega, de manera que pueda ser inspeccionado por los lectores interesados. El código completo utilizado para generar este documento, en formato .qdm, está disponible para cualquier lector, y puede ser solicitado directamente al autor de este documento.
Como regla general, la opción de desplegar u ocultar el código se ofrece cuando lo que interesa es nada más mostrar un determinado resultado, como una tabla o un gráfico. Cuando el código es indispensable desde un punto de vista didáctico, es decir, cuando es necesario para entender cómo se construye la base de datos, el código se muestra siempre en el texto, sin opción de ocultarlo.
Construcción de la base de datos
Bloque básico (bd)
El punto de partida es una base de datos con cinco variables: - “Año”, del 2010 al 2020 - “t”, un indicador de período, de 0 a 10 - “Ubicación”, que pude entenderse como “cantón”, de C1 a C4 - “Exporta”, que indica si la empresa exporta o no
<- as.integer(seq(2010, 2020, 1))
Año <- seq(0, 10, 1)
t <- factor(c("C1", "C2", "C3", "C4"))
Ubicacion <- factor(c("Pequeña", "Mediana", "Grande"))
Tamaño <- factor(c("si", "no"))
Exporta <- tibble(Año = Año, t = t) |>
bd expand(nesting(Año, t), Ubicacion, Tamaño, Exporta)
La tabla siguiente, con datos para un año y un cantón, muestra la estructura de este primer bloque de nuestra base de datos:
Code
|> filter(Año == 2010 & Ubicacion == "C1") |>
bd kable() |>
kable_styling(
full_width = FALSE,
bootstrap_options = c("striped", "hover", "condensed"))
Año | t | Ubicacion | Tamaño | Exporta |
---|---|---|---|---|
2010 | 0 | C1 | Grande | no |
2010 | 0 | C1 | Grande | si |
2010 | 0 | C1 | Mediana | no |
2010 | 0 | C1 | Mediana | si |
2010 | 0 | C1 | Pequeña | no |
2010 | 0 | C1 | Pequeña | si |
Inclusión de número de empresas (bd2)
En Costa Rica, la distribución de empresas por tamaño, de acuerdo al MEIC era la siguiente en 2019 era la siguiente:
Tamaño | Número | Proporción |
---|---|---|
Micro + pequeña | 128.210 | 93.5% |
Mediana | 5,365 | 3.9% |
Grande | 3,533 | 2.6% |
Total | 137,378 | 100% |
Al momento de elaboración de este documento no se disponía de información actualizada sobre el número de empresas exportadoras según tamaño.
Procederemos, entonces, a añadir dos variables a nuetra base de datos: el número de empresas por tamaño, basado en datos reales, y por ahora distribuido en partes iguales entre cantones, y la distribución de empresas de cada tamaño según condición exportadora, basada en supuestos que pueden ser fácilmente modificados más adelante.
Primero, definimos rangos de número de empresas por tamaño:
<- tribble(
rangos_tam ~Tamaño, ~Nmin, ~Nmax,
"Pequeña", 118210, 128210,
"Mediana", 4829, 5902,
"Grande", 3179, 3886
)$Tamaño <- as.factor(rangos_tam$Tamaño) rangos_tam
<- tribble(
prop_exporta ~Tamaño, ~prop_export_si, ~prop_export_no,
"Pequeña", 0.01, 0.99,
"Mediana", 0.10, 0.90,
"Grande", 0.20, 0.80 # 90% exporta=si, 10% exporta=no
)$Tamaño <- as.factor(prop_exporta$Tamaño) prop_exporta
Tercero, creamos una nueva base de datos, uniendo bd con los rangos de tamañao de las empresas y la proporcion de empresas exportadoras o no, por tamaño.
set.seed(123)
<- bd |>
bd2 left_join(rangos_tam, by = "Tamaño") |>
left_join(prop_exporta, by = "Tamaño") |>
group_by(Año, Ubicacion, Tamaño) |>
mutate(Total_Empr_Tamaño = floor(runif(n = 1, min = Nmin, max = Nmax + 1))) |>
ungroup() |>
mutate(
Empr = case_when(
== "si" ~
Exporta round(Total_Empr_Tamaño * prop_export_si/length(unique(Ubicacion))),
== "no" ~
Exporta round(Total_Empr_Tamaño * prop_export_no/length(unique(Ubicacion))),
TRUE ~ 0
)|>
) select(-Nmin, -Nmax, -prop_export_si, -prop_export_no, -Total_Empr_Tamaño)
El siguiente gráfico muestra el número de empresa por tamañao en nuestra base de datos, que oscila aleatoriamente, pero que no muestra tendencia ni a crecer ni a disminuir.
|> group_by(Año, Tamaño) |>
bd2 summarize(Empresas = sum(Empr), .groups = 'drop') |>
ggplot(aes(Año,Empresas, color = Tamaño))+
geom_line()
La tabla siguiente nos permite comprobar que el número de empresas, por tamaño, corresponde aproximadamente a la distribución real de empresas por tamaño en Costa Rica.
Code
<- bd2 |>
n_firmas_tamaño group_by(Tamaño) |>
summarize(
=
Total_empresas_por_tamaño as.integer(round(sum(Empr)/length(unique(bd2$Año)))),
.groups = 'drop'
)
kable(n_firmas_tamaño,
digits = 0,
format.args = list(big.mark = ",")) |>
kable_styling(
full_width = FALSE,
bootstrap_options = c("striped", "hover", "condensed")
)
Tamaño | Total_empresas_por_tamaño |
---|---|
Grande | 3,565 |
Mediana | 5,371 |
Pequeña | 122,867 |
Inclusion de datos de productividad
Ahora añadiremos datos de productividad laboral por tamaño de empresa. Se usará para cada tamaño de empresa una distribución normal truncada, con promedio igual a los datos de productividad por tamaño de empresa indicados en la Tabla 1. Se establece una productividad base por tamaño de empresa, y luego incrementos de 1%, 2.5% y 5% para empresas exportadoras pequeñas, medianas y grandes, respectivametne.
set.seed(123)
<- bd2 |>
bd2 group_by(Año, Ubicacion, Tamaño) |>
mutate(
Productividad_base = case_when(
== "Pequeña" ~ rtruncnorm(1, a = 0.15, b = 0.35, mean = 0.25, sd = 0.05),
Tamaño == "Mediana" ~ rtruncnorm(1, a = 0.30, b = 0.70, mean = 0.50, sd = 0.08),
Tamaño == "Grande" ~ rtruncnorm(1, a = 1, b = 2, mean = 1.5, sd = 0.15),
Tamaño TRUE ~ NA_real_
)|>
) ungroup() |> # Ungroup after creating base productivity to calculate the rest row-wise
mutate(
Productividad = Productividad_base *
# 1. Add general productivity growth trend by firm size
case_when(
== "Mediana" ~ (1 + 0.01)^t,
Tamaño == "Grande" ~ (1 + 0.015)^t,
Tamaño TRUE ~ 1 # No trend for Pequeña
*
) # 2. Add exporter premium, which itself grows annually
case_when(
== "si" & Tamaño == "Pequeña" ~ 1 + (0.01 * (1 + 0.03)^t),
Exporta == "si" & Tamaño == "Mediana" ~ 1 + (0.025 * (1 + 0.06)^t),
Exporta == "si" & Tamaño == "Grande" ~ 1 + (0.10 * (1 + 0.09)^t),
Exporta TRUE ~ 1 # No premium for non-exporters
*
)case_when(
== "C1" ~ (1 + 0.01)^t,
Ubicacion TRUE ~ 1
|>
))select(-Productividad_base)
Gráficamente:
|> group_by(Año,Tamaño,Exporta) |>
bd2 summarize(ProdMedia = mean(Productividad),.groups = 'drop') |>
ggplot(aes(Año, ProdMedia, color= Tamaño, linetype = Exporta))+
theme_bw()+
scale_color_brewer(palette = "Dark2")+
geom_line()+
scale_x_continuous(breaks = 2010:2020)
Inclusión de número de trabajadores
Ahora añadiremos datos sobre número de trabajadores, usando la misma técnica que utilizamos para generar datos de número de empresas: generaremos rangos de número de trabajadores según tamaño de empresa, centrados alrededor de los valores en Tabla1.
Primero, generamos rangos de número de trabajadores por tamaño de empresa:
set.seed(456)
<- tribble(
rangos_trab ~Tamaño, ~Nmin, ~Nmax, ~Nmean, ~Nsd,
"Pequeña", 1, 50, 20, 10,
"Mediana", 51, 200, 100, 50,
"Grande", 201, 1200, 600, 166
)
<- bd2 |>
bd2 # unir los rangos a las firmas
left_join(rangos_trab, by = "Tamaño") |>
# procesar cada fila de manera individual
rowwise() |>
# Generatr'Trabs' usando distribucion normal truncada
mutate(Trabs = round(rtruncnorm(n = 1, a = Nmin, b = Nmax, mean = Nmean, sd = Nsd))) |>
ungroup() |>
# Eliminar columnas temporales
select(-Nmin, -Nmax, -Nmean, -Nsd)
|>
bd2 group_by(Año, Tamaño) |>
summarize(Trabs_Prom = mean(Trabs)) |>
ggplot(aes(Año, Trabs_Prom, color = Tamaño))+
theme_bw()+
scale_color_brewer(palette = "Dark2")+
geom_line()+
scale_x_continuous(breaks = 2010:2020)
`summarise()` has grouped output by 'Año'. You can override using the `.groups`
argument.
Valor agregado laboral por observacion
<- bd2 |>
bd2 mutate(
VA = round(Productividad*Trabs*Empr/1000)
)
Clase de cada variable
Seguidamente verificamos la “clase” correspondiente a cada variable, y nos asesguramos de que las variables Ubicacion, Tamaño y Exporta sean factores ordenados
str(bd2)
tibble [264 × 9] (S3: tbl_df/tbl/data.frame)
$ Año : int [1:264] 2010 2010 2010 2010 2010 2010 2010 2010 2010 2010 ...
$ t : num [1:264] 0 0 0 0 0 0 0 0 0 0 ...
$ Ubicacion : Factor w/ 4 levels "C1","C2","C3",..: 1 1 1 1 1 1 2 2 2 2 ...
$ Tamaño : chr [1:264] "Grande" "Grande" "Mediana" "Mediana" ...
$ Exporta : Factor w/ 2 levels "no","si": 1 2 1 2 1 2 1 2 1 2 ...
$ Empr : num [1:264] 676 169 1277 142 30269 ...
$ Productividad: num [1:264] 1.734 1.907 0.51 0.523 0.273 ...
$ Trabs : num [1:264] 377 703 140 64 17 27 642 767 129 54 ...
$ VA : num [1:264] 442 227 91 5 141 2 759 249 86 4 ...
Las variables indicadas no son factores ordenados, así que las transformamos
$Ubicacion <- factor(bd2$Ubicacion,
bd2levels = c("C1", "C2", "C3", "C4"),
ordered = TRUE
)$Tamaño <- factor(bd2$Tamaño,
bd2levels =c("Pequeña", "Mediana", "Grande"),
ordered = TRUE)
$Exporta <- factor(bd2$Exporta,
bd2levels = c("si", "no"),
ordered = TRUE)
str(bd2)
tibble [264 × 9] (S3: tbl_df/tbl/data.frame)
$ Año : int [1:264] 2010 2010 2010 2010 2010 2010 2010 2010 2010 2010 ...
$ t : num [1:264] 0 0 0 0 0 0 0 0 0 0 ...
$ Ubicacion : Ord.factor w/ 4 levels "C1"<"C2"<"C3"<..: 1 1 1 1 1 1 2 2 2 2 ...
$ Tamaño : Ord.factor w/ 3 levels "Pequeña"<"Mediana"<..: 3 3 2 2 1 1 3 3 2 2 ...
$ Exporta : Ord.factor w/ 2 levels "si"<"no": 2 1 2 1 2 1 2 1 2 1 ...
$ Empr : num [1:264] 676 169 1277 142 30269 ...
$ Productividad: num [1:264] 1.734 1.907 0.51 0.523 0.273 ...
$ Trabs : num [1:264] 377 703 140 64 17 27 642 767 129 54 ...
$ VA : num [1:264] 442 227 91 5 141 2 759 249 86 4 ...
Estructura de la base de datos
Nuestra base de datos sintética suministra información para un período de 11 años, agrupada por cantones y tamaño de empresa.
Para cada una de estas combinaciones, se presenta el número de trabajadores, la productividad, condición exportadora (o no) condición de proveedor (o no) de empresas exportadoras
El resumen de la información contenida en la base de datos hasta este momento se presenta en la tabla siguiente:
Code
<- bd2 |>
Resumen group_by(Tamaño,Exporta) |>
summarize(
Empresas = sum(Empr)/length(unique(Año)),
Trabajadores = mean(Trabs),
Productividad = mean(Productividad),
)
kable(Resumen,
digits = 2,
format.args = list(big.mark = ","))
Tamaño | Exporta | Empresas | Trabajadores | Productividad |
---|---|---|---|---|
Pequeña | si | 1,228.64 | 23.16 | 0.25 |
Pequeña | no | 121,638.18 | 20.91 | 0.25 |
Mediana | si | 537.09 | 112.16 | 0.56 |
Mediana | no | 4,833.91 | 111.43 | 0.54 |
Grande | si | 713.18 | 573.18 | 1.94 |
Grande | no | 2,851.73 | 624.18 | 1.67 |
Agregar definición de variables a la base de datos
<- list(
definiciones = "Año de la observación, del 2010 al 2020",
Año t = "Índice de tiempo que inicia en 0 para el año 2010",
Ubicacion = "Cantón al que corresponde la observación (C1 a C4)",
= "Clasificación de la empresa: Pequeña, Mediana, o Grande",
Tamaño Exporta = "Indica si la empresa exporta ('si') o no ('no')",
Empr = "Número de empresas en la observación",
Productividad = "Índice de productividad de la empresa",
Trabs = "Número de trabajadores de la empresa",
VA = "Valor Agregado Laboral (Productividad * Trabs * Empr)/1000"
)
attr(bd2, "variable_definitions") <- definiciones
Guardar las bases de datos para futuro uso
Finalmente, salvamos nuestras dos bases de dato para uso futuro. La base de datos final, bd2, se salva tanto en formato R como en formato Excel
save(bd, file="bd.RData")
save(bd2, file = "bd2.RData")
write_xlsx(bd2,"bd2Ex.xlsx")