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.

library(tidyverse)
library(truncnorm)
library(knitr)
library(kableExtra)
library(writexl)

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

Año <- as.integer(seq(2010, 2020, 1))
t <- seq(0, 10, 1)
Ubicacion <- factor(c("C1", "C2", "C3", "C4"))
Tamaño <- factor(c("Pequeña", "Mediana", "Grande"))
Exporta <- factor(c("si", "no"))
bd <- tibble(Año = Año, t = t) |> 
  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
bd |> filter(Año == 2010 & Ubicacion == "C1") |> 
  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:

rangos_tam <- tribble(
  ~Tamaño,   ~Nmin,   ~Nmax,
  "Pequeña", 118210,  128210,
  "Mediana",   4829,    5902,
  "Grande",    3179,    3886
)
rangos_tam$Tamaño <- as.factor(rangos_tam$Tamaño)
prop_exporta <- tribble(
  ~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
)
prop_exporta$Tamaño <- as.factor(prop_exporta$Tamaño)

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)

bd2 <- bd |> 
  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(
      Exporta == "si" ~ 
        round(Total_Empr_Tamaño * prop_export_si/length(unique(Ubicacion))),
      Exporta == "no" ~ 
        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.

bd2 |> group_by(Año, Tamaño) |> 
  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
n_firmas_tamaño <- bd2 |> 
  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(
      Tamaño == "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),
      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(
        Tamaño == "Mediana" ~ (1 + 0.01)^t,
        Tamaño == "Grande"  ~ (1 + 0.015)^t,
        TRUE ~ 1 # No trend for Pequeña
      ) *
      # 2. Add exporter premium, which itself grows annually
      case_when(
        Exporta == "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),
        TRUE ~ 1 # No premium for non-exporters
      )*
        case_when(
    Ubicacion == "C1" ~ (1 + 0.01)^t,
    TRUE ~ 1
  ))|>
  select(-Productividad_base)

Gráficamente:

bd2 |> group_by(Año,Tamaño,Exporta) |> 
  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)
rangos_trab <- tribble(
  ~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

bd2$Ubicacion <- factor(bd2$Ubicacion,
                           levels = c("C1", "C2", "C3", "C4"),
                           ordered = TRUE
                           )
bd2$Tamaño <- factor(bd2$Tamaño,
                        levels =c("Pequeña", "Mediana", "Grande"),
                        ordered = TRUE)
bd2$Exporta <- factor(bd2$Exporta,
                         levels = 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
Resumen <- bd2 |> 
  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

definiciones <- list(
  Año = "Año de la observación, del 2010 al 2020",
  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)",
  Tamaño = "Clasificación de la empresa: Pequeña, Mediana, o Grande",
  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")