# --- Resolución de nombres de variables por patrón ---------------------------
elegir_variable <- function(nombres, exactos = character(), patrones = character()) {
for (e in exactos) if (e %in% nombres) return(e)
for (p in patrones) {
coincide <- grep(p, nombres, value = TRUE, ignore.case = TRUE)
if (length(coincide) > 0) return(coincide[1])
}
NA_character_
}
exigir_variable <- function(valor, concepto, nombres) {
if (is.na(valor)) {
stop(
"No fue posible identificar la variable correspondiente a '", concepto,
"' en la base leída. Nombres disponibles (primeros 60): ",
paste(utils::head(nombres, 60), collapse = ", "),
call. = FALSE
)
}
valor
}
# --- Caché, descarga y extracción --------------------------------------------
dir_cache <- here::here("cache_enemdu")
dir.create(dir_cache, showWarnings = FALSE, recursive = TRUE)
nombre_seguro <- function(x) {
x <- utils::URLdecode(basename(x))
gsub("[^A-Za-z0-9._-]+", "_", x)
}
descargar_zip <- function(url, destino) {
if (file.exists(destino) && file.size(destino) > 10240) return(invisible(destino))
temporal <- paste0(destino, ".parcial")
resultado <- tryCatch({
utils::download.file(url, destfile = temporal, mode = "wb", quiet = TRUE)
"ok"
}, error = function(e) conditionMessage(e),
warning = function(w) conditionMessage(w))
descarga_valida <- identical(resultado, "ok") &&
file.exists(temporal) && file.size(temporal) > 10240
if (!descarga_valida) {
stop(
"Falló la descarga de los microdatos.\n",
" URL solicitada : ", url, "\n",
" Detalle : ", resultado, "\n",
"Verifique la conexión a internet, que la dirección siga vigente en el portal del INEC ",
"y que el archivo corresponda a un ZIP de microdatos. ",
"Como alternativa, coloque manualmente el ZIP en la carpeta '", dir_cache,
"' con el nombre '", basename(destino), "' y vuelva a compilar el documento.",
call. = FALSE
)
}
file.rename(temporal, destino)
invisible(destino)
}
# Los ZIP del INEC contienen nombres de archivo con tildes codificados en CP-1252.
# Se listan y filtran siempre a nivel de bytes para evitar errores de conversión.
listar_archivos <- function(carpeta, patron) {
todos <- list.files(carpeta, recursive = TRUE, full.names = TRUE)
if (length(todos) == 0) return(character(0))
todos[grepl(patron, basename(todos), ignore.case = TRUE, useBytes = TRUE)]
}
hay_microdatos <- function(carpeta) length(listar_archivos(carpeta, "\\.(sav|dta)$")) > 0
es_zip_valido <- function(ruta) {
if (!file.exists(ruta) || file.size(ruta) < 100) return(FALSE)
firma <- readBin(ruta, what = "raw", n = 4)
identical(as.integer(firma), c(0x50L, 0x4BL, 0x03L, 0x04L))
}
descomprimir_con_locale <- function(ruta_zip, carpeta_destino, locale_ctype) {
if (!is.null(locale_ctype)) {
original <- Sys.getlocale("LC_CTYPE")
aplicado <- suppressWarnings(try(Sys.setlocale("LC_CTYPE", locale_ctype), silent = TRUE))
if (inherits(aplicado, "try-error") || !nzchar(aplicado)) return(FALSE)
on.exit(suppressWarnings(Sys.setlocale("LC_CTYPE", original)), add = TRUE)
}
intento <- suppressWarnings(try(
utils::unzip(ruta_zip, exdir = carpeta_destino), silent = TRUE))
!inherits(intento, "try-error")
}
descomprimir_con_powershell <- function(ruta_zip, carpeta_destino) {
if (.Platform$OS.type != "windows") return(FALSE)
zip_abs <- normalizePath(ruta_zip, winslash = "\\", mustWork = FALSE)
dir_abs <- normalizePath(carpeta_destino, winslash = "\\", mustWork = FALSE)
orden <- sprintf(
"Expand-Archive -LiteralPath '%s' -DestinationPath '%s' -Force", zip_abs, dir_abs)
estado <- suppressWarnings(try(
system2("powershell",
args = c("-NoProfile", "-NonInteractive", "-Command", shQuote(orden, type = "cmd")),
stdout = FALSE, stderr = FALSE),
silent = TRUE))
!inherits(estado, "try-error") && identical(as.integer(estado), 0L)
}
descomprimir <- function(ruta_zip, carpeta_destino) {
if (!dir.exists(carpeta_destino)) dir.create(carpeta_destino, recursive = TRUE)
if (hay_microdatos(carpeta_destino)) return(TRUE)
locales <- list(NULL, "C", "Spanish_Ecuador.1252", "Spanish_Spain.1252",
"English_United States.1252", "es_EC.ISO8859-1", "C.UTF-8")
for (lc in locales) {
if (descomprimir_con_locale(ruta_zip, carpeta_destino, lc) &&
hay_microdatos(carpeta_destino)) return(TRUE)
}
if (descomprimir_con_powershell(ruta_zip, carpeta_destino) &&
hay_microdatos(carpeta_destino)) return(TRUE)
hay_microdatos(carpeta_destino)
}
extraer_zip <- function(ruta_zip, carpeta_destino) {
if (!dir.exists(carpeta_destino)) dir.create(carpeta_destino, recursive = TRUE)
if (hay_microdatos(carpeta_destino)) return(invisible(carpeta_destino))
if (!es_zip_valido(ruta_zip)) {
stop("El archivo descargado no tiene la firma de un ZIP: ", ruta_zip,
". Es posible que la descarga esté corrupta o que la dirección devuelva una página ",
"HTML en lugar del archivo de microdatos. Elimine ese archivo de la carpeta de caché ",
"y vuelva a intentarlo.", call. = FALSE)
}
logrado <- descomprimir(ruta_zip, carpeta_destino)
# Algunas entregas del INEC empaquetan un ZIP dentro de otro
anidados <- listar_archivos(carpeta_destino, "\\.zip$")
for (z in anidados) {
if (es_zip_valido(z)) descomprimir(z, dirname(z))
}
if (!hay_microdatos(carpeta_destino)) {
stop("No fue posible descomprimir los microdatos de ", basename(ruta_zip),
". El archivo contiene nombres con caracteres que la configuración regional actual ",
"no puede interpretar y ninguna de las alternativas de extracción funcionó. ",
"Descomprima el ZIP manualmente dentro de la carpeta '", carpeta_destino,
"' y vuelva a compilar el documento.", call. = FALSE)
}
invisible(carpeta_destino)
}
localizar_base_personas <- function(carpeta) {
archivos <- listar_archivos(carpeta, "\\.(sav|dta)$")
if (length(archivos) == 0) {
stop("No se encontraron archivos .sav ni .dta dentro de ", carpeta,
". Revise el contenido del ZIP descargado.", call. = FALSE)
}
nombres <- basename(archivos)
personas <- archivos[grepl("person", nombres, ignore.case = TRUE, useBytes = TRUE)]
if (length(personas) == 0) {
candidatos <- archivos[!grepl("vivienda|hogar", nombres, ignore.case = TRUE, useBytes = TRUE)]
if (length(candidatos) == 0) candidatos <- archivos
personas <- candidatos
}
personas[which.max(file.size(personas))]
}
leer_microdatos <- function(ruta) {
extension <- tolower(sub("^.*\\.([^.]+)$", "\\1", basename(ruta), useBytes = TRUE))
base <- switch(
extension,
sav = haven::read_sav(ruta),
dta = haven::read_dta(ruta),
stop("Formato de microdatos no soportado: .", extension,
". Se esperaba .sav o .dta.", call. = FALSE)
)
janitor::clean_names(base)
}
a_numerico <- function(x) as.numeric(haven::zap_labels(x))
etiqueta_periodo <- function(url, alternativa) {
nombre <- utils::URLdecode(basename(url))
captura <- regmatches(nombre, regexpr("(19|20)[0-9]{2}([_-][0-9]{1,2})?", nombre))
if (length(captura) == 0) return(alternativa)
gsub("_", "-", captura[1])
}
obtener_base <- function(url, etiqueta) {
archivo_zip <- file.path(dir_cache, nombre_seguro(url))
if (!grepl("\\.zip$", archivo_zip, ignore.case = TRUE)) archivo_zip <- paste0(archivo_zip, ".zip")
carpeta <- file.path(dir_cache, paste0(tools::file_path_sans_ext(basename(archivo_zip)), "_extraido"))
descargar_zip(url, archivo_zip)
extraer_zip(archivo_zip, carpeta)
ruta_base <- localizar_base_personas(carpeta)
list(
datos = leer_microdatos(ruta_base),
archivo = basename(ruta_base),
etiqueta = etiqueta
)
}# Construcción de las variables de condición de actividad según la definición del INEC
preparar_condicion_actividad <- function(base, periodo) {
nombres <- names(base)
v_fexp <- exigir_variable(
elegir_variable(nombres, c("fexp", "f_exp", "fexp_anual"),
c("^fexp", "^f_?exp", "factor.*expan")),
"factor de expansión", nombres)
v_upm <- exigir_variable(
elegir_variable(nombres, c("upm", "conglomerado", "id_upm"),
c("^upm$", "^id_?upm$", "^conglom", "upm")),
"conglomerado / unidad primaria de muestreo", nombres)
v_estrato <- exigir_variable(
elegir_variable(nombres, c("estrato", "id_estrato"),
c("^estrato", "estrat")),
"estrato de muestreo", nombres)
v_edad <- exigir_variable(
elegir_variable(nombres, c("p03", "edad"), c("^p03$", "^edad")),
"edad de la persona", nombres)
v_condact <- exigir_variable(
elegir_variable(nombres, c("condact", "condicion_actividad"),
c("^condact", "cond.*activ")),
"condición de actividad", nombres)
v_sexo <- elegir_variable(nombres, c("p02", "sexo"), c("^p02$", "^sexo"))
v_area <- elegir_variable(nombres, c("area"), c("^area$"))
tabla <- tibble(
periodo = periodo,
fexp = a_numerico(base[[v_fexp]]),
upm = as.character(base[[v_upm]]),
estrato = as.character(base[[v_estrato]]),
edad = a_numerico(base[[v_edad]]),
condact = a_numerico(base[[v_condact]]),
sexo = if (is.na(v_sexo)) NA_real_ else a_numerico(base[[v_sexo]]),
area = if (is.na(v_area)) NA_real_ else a_numerico(base[[v_area]])
)
tabla <- tabla %>%
mutate(
pet = edad >= 15,
ocupado = condact %in% 1:6,
desocupado = condact %in% 7:8,
pea = condact %in% 1:8,
pei = condact == 9,
adecuado = condact == 1,
subempleo = condact %in% 2:3,
otro_no_pleno = condact == 4,
no_remunerado = condact == 5,
no_clasificado = condact == 6,
sexo_etq = case_when(sexo == 1 ~ "Hombre", sexo == 2 ~ "Mujer", TRUE ~ NA_character_),
area_etq = case_when(area == 1 ~ "Urbana", area == 2 ~ "Rural", TRUE ~ NA_character_),
categoria_pea = case_when(
adecuado ~ "Empleo adecuado",
subempleo ~ "Subempleo",
otro_no_pleno ~ "Otro empleo no pleno",
no_remunerado ~ "Empleo no remunerado",
no_clasificado ~ "Empleo no clasificado",
desocupado ~ "Desempleo",
TRUE ~ NA_character_
)
) %>%
mutate(
i_participacion = as.numeric(pea),
i_adecuado = as.numeric(adecuado),
i_subempleo = as.numeric(subempleo),
i_otro_no_pleno = as.numeric(otro_no_pleno),
i_no_remunerado = as.numeric(no_remunerado),
i_desempleo = as.numeric(desocupado)
) %>%
filter(!is.na(fexp), fexp > 0, !is.na(edad))
attr(tabla, "variables_usadas") <- c(
"factor de expansión" = v_fexp,
"conglomerado (UPM)" = v_upm,
"estrato" = v_estrato,
"edad" = v_edad,
"condición de actividad" = v_condact,
"sexo" = ifelse(is.na(v_sexo), "no disponible", v_sexo),
"área" = ifelse(is.na(v_area), "no disponible", v_area)
)
tabla
}etq_pre <- etiqueta_periodo(params$url_pre, "período previo")
etq_post <- etiqueta_periodo(params$url_post, "período posterior")
cruda_pre <- obtener_base(params$url_pre, etq_pre)
cruda_post <- obtener_base(params$url_post, etq_post)
datos_pre <- preparar_condicion_actividad(cruda_pre$datos, "Previo")
datos_post <- preparar_condicion_actividad(cruda_post$datos, "Posterior")
vars_pre <- attr(datos_pre, "variables_usadas")
vars_post <- attr(datos_post, "variables_usadas")
# Liberar memoria de las bases completas una vez extraídas las variables de interés
rm(cruda_pre, cruda_post); invisible(gc(verbose = FALSE))
datos <- bind_rows(datos_pre, datos_post) %>%
mutate(
periodo = factor(periodo, levels = c("Previo", "Posterior")),
estrato_c = paste(periodo, estrato, sep = "_"),
upm_c = paste(periodo, estrato, upm, sep = "_")
)
etiquetas_periodo <- c("Previo" = paste0("Previo (", etq_pre, ")"),
"Posterior" = paste0("Posterior (", etq_post, ")"))
resumen_muestra <- datos %>%
group_by(periodo) %>%
summarise(
registros = n(),
upm = n_distinct(upm_c),
estratos = n_distinct(estrato_c),
poblacion = sum(fexp),
pet_n = sum(pet, na.rm = TRUE),
pea_n = sum(pea, na.rm = TRUE)
)diseno <- datos %>%
as_survey_design(
ids = upm_c,
strata = estrato_c,
weights = fexp,
nest = TRUE
)
diseno_pet <- diseno %>% filter(pet)
diseno_pea <- diseno %>% filter(pea)estimar_tasa <- function(design, variable, etiqueta, denominador) {
design %>%
group_by(periodo) %>%
summarise(valor = survey_mean(!!sym(variable), vartype = c("se", "ci"), na.rm = TRUE)) %>%
transmute(
indicador = etiqueta,
denominador = denominador,
periodo,
tasa = valor,
ee = valor_se,
li = valor_low,
ls = valor_upp
)
}
indicadores <- tribble(
~variable, ~etiqueta, ~base, ~denominador,
"i_participacion", "Tasa de participación global", "pet", "PET",
"i_adecuado", "Tasa de empleo adecuado", "pea", "PEA",
"i_subempleo", "Tasa de subempleo", "pea", "PEA",
"i_otro_no_pleno", "Tasa de otro empleo no pleno", "pea", "PEA",
"i_no_remunerado", "Tasa de empleo no remunerado", "pea", "PEA",
"i_desempleo", "Tasa de desempleo", "pea", "PEA"
)
estimaciones <- pmap_dfr(
list(indicadores$variable, indicadores$etiqueta, indicadores$base, indicadores$denominador),
function(v, e, b, d) {
d_obj <- if (b == "pet") diseno_pet else diseno_pea
estimar_tasa(d_obj, v, e, d)
}
) %>%
mutate(indicador = factor(indicador, levels = indicadores$etiqueta))contrastar <- function(design, variable, etiqueta) {
formula_test <- stats::as.formula(paste(variable, "~ periodo"))
prueba <- survey::svyttest(formula_test, design)
ic <- as.numeric(prueba$conf.int)
gl <- as.numeric(prueba$parameter)
ee <- (ic[2] - ic[1]) / (2 * stats::qt(0.975, gl))
tibble(
indicador = etiqueta,
diferencia = as.numeric(prueba$estimate),
ee = ee,
li = ic[1],
ls = ic[2],
gl = gl,
estadistico_t = as.numeric(prueba$statistic),
valor_p = as.numeric(prueba$p.value),
dme = (stats::qt(0.975, gl) + stats::qt(0.80, gl)) * ee
)
}
contrastes <- pmap_dfr(
list(indicadores$variable, indicadores$etiqueta, indicadores$base),
function(v, e, b) {
d_obj <- if (b == "pet") diseno_pet else diseno_pea
contrastar(d_obj, v, e)
}
) %>%
mutate(indicador = factor(indicador, levels = indicadores$etiqueta))Este informe evalúa si el conjunto de regulaciones laborales adoptadas en Ecuador durante la emergencia sanitaria por COVID-19 —en particular la Ley Orgánica de Apoyo Humanitario y su figura del contrato especial emergente, la reducción emergente de la jornada y la ampliación de las causales de terminación contractual— se asocia a un cambio detectable en la estructura del mercado laboral ecuatoriano. Para ello se comparan dos rondas de la Encuesta Nacional de Empleo, Desempleo y Subempleo (ENEMDU): una correspondiente al período previo (2019-12) y otra al período posterior (2022-12).
Las estimaciones se construyen respetando el diseño muestral complejo de la encuesta (estratificación, conglomeración y ponderación), y el contraste entre períodos se realiza mediante pruebas t que incorporan la varianza correcta del diseño. Se reportan la estimación puntual de cada tasa, su error estándar, su intervalo de confianza al 95 %, la diferencia entre períodos con su intervalo y el valor p asociado.
n_sig_res <- sum(contrastes$valor_p < 0.05)
n_tot_res <- nrow(contrastes)
lista_sig <- paste(as.character(contrastes$indicador[contrastes$valor_p < 0.05]), collapse = ", ")
cat(paste0(
"En el contraste realizado, ",
if (n_sig_res == 0) {
paste0("ninguno de los ", n_tot_res, " indicadores examinados presenta una diferencia ",
"estadísticamente significativa al 5 %. El informe desarrolla por qué ese resultado no ",
"equivale a haber demostrado la ausencia de efecto, y qué magnitud de efecto habría sido ",
"detectable con la precisión disponible.")
} else {
paste0(n_sig_res, " de los ", n_tot_res, " indicadores examinados presentan una diferencia ",
"estadísticamente significativa al 5 % (", lista_sig, "), mientras que los restantes no ",
"permiten rechazar la hipótesis nula. El informe discute ambos tipos de resultado: por qué ",
"un valor p pequeño no mide la magnitud ni el origen del efecto, y por qué la ausencia de ",
"rechazo tampoco demuestra la ausencia de cambio.")
}
))En el contraste realizado, 2 de los 6 indicadores examinados presentan una diferencia estadísticamente significativa al 5 % (Tasa de empleo adecuado, Tasa de otro empleo no pleno), mientras que los restantes no permiten rechazar la hipótesis nula. El informe discute ambos tipos de resultado: por qué un valor p pequeño no mide la magnitud ni el origen del efecto, y por qué la ausencia de rechazo tampoco demuestra la ausencia de cambio.
El análisis es explícitamente descriptivo-inferencial y no causal. Una comparación antes-después sobre dos cortes transversales no aísla el efecto de la regulación: cualquier diferencia observada mezcla el efecto normativo con el shock sanitario, el ciclo económico, los cambios en el operativo de campo de la encuesta y la evolución demográfica. La sección de limitaciones desarrolla este punto y detalla qué haría falta para acercarse a una identificación causal creíble.
La ENEMDU es la operación estadística oficial del Instituto Nacional de Estadística y Censos (INEC) para la medición del mercado laboral ecuatoriano. Se trata de una encuesta por muestreo probabilístico de hogares, con marco basado en el Marco Maestro de Muestreo, diseño estratificado bietápico y representatividad nacional, por área (urbana y rural) y para las ciudades autorrepresentadas.
Los microdatos se descargan directamente desde el portal del INEC a
partir de las dos direcciones declaradas en los parámetros del
documento. La descarga se almacena en una carpeta local de caché
(cache_enemdu, relativa al directorio del proyecto) de modo
que compilaciones sucesivas no repitan la transferencia. El documento
detecta automáticamente la extensión del archivo de microdatos
(.sav o .dta) y lo lee con el motor
correspondiente de haven; dentro del ZIP se identifica
la base de personas por el nombre del archivo, descartando las bases de
vivienda y hogar.
tibble(
Período = c(etiquetas_periodo[["Previo"]], etiquetas_periodo[["Posterior"]]),
Dirección = c(params$url_pre, params$url_post)
) %>%
gt() %>%
tab_header(title = "Orígenes de los microdatos utilizados") %>%
cols_align(align = "left") %>%
tab_options(table.font.size = px(12), data_row.padding = px(5))| Orígenes de los microdatos utilizados | |
| Período | Dirección |
|---|---|
| Previo (2019-12) | https://www.ecuadorencifras.gob.ec/documentos/web-inec/EMPLEO/2019/Diciembre/BDD_ENEMDU_2019_12_SPSS.zip |
| Posterior (2022-12) | https://www.ecuadorencifras.gob.ec/documentos/web-inec/EMPLEO/2022/Diciembre_2022/1_BDD_ENEMDU_2022_12_SPSS.zip |
Los nombres de las variables de la ENEMDU han cambiado entre
versiones de la base. Para evitar supuestos frágiles, el documento
resuelve cada concepto buscando por patrón sobre los nombres reales de
cada archivo, previa normalización con
janitor::clean_names(). La siguiente tabla documenta qué
variable original se utilizó en cada período.
tibble(
Concepto = names(vars_pre),
`Período previo` = as.character(vars_pre),
`Período posterior` = as.character(vars_post[names(vars_pre)])
) %>%
gt() %>%
tab_header(
title = "Correspondencia entre conceptos y variables originales",
subtitle = "Resolución automática por patrón sobre los nombres de cada base"
) %>%
cols_align(align = "left") %>%
tab_options(table.font.size = px(12), data_row.padding = px(5))| Correspondencia entre conceptos y variables originales | ||
| Resolución automática por patrón sobre los nombres de cada base | ||
| Concepto | Período previo | Período posterior |
|---|---|---|
| factor de expansión | fexp | fexp |
| conglomerado (UPM) | upm | upm |
| estrato | estrato | estrato |
| edad | p03 | p03 |
| condición de actividad | condact | condact |
| sexo | p02 | p02 |
| área | area | area |
La clasificación sigue la definición oficial del INEC, basada en las recomendaciones de la Organización Internacional del Trabajo y operacionalizada en la variable derivada de condición de actividad que el propio Instituto incluye en la base de personas. Sobre ella se construyen los agregados del siguiente modo:
Las tasas se calculan con los denominadores oficiales: la tasa de participación global sobre la PET y las tasas de empleo adecuado, subempleo, otro empleo no pleno, empleo no remunerado y desempleo sobre la PEA.
resumen_muestra %>%
mutate(periodo = recode(as.character(periodo), !!!etiquetas_periodo)) %>%
gt() %>%
tab_header(title = "Tamaño muestral y estructura del diseño por período") %>%
cols_label(
periodo = "Período", registros = "Registros", upm = "UPM",
estratos = "Estratos", poblacion = "Población expandida",
pet_n = "Registros PET", pea_n = "Registros PEA"
) %>%
fmt_number(columns = c(registros, upm, estratos, pet_n, pea_n), decimals = 0, sep_mark = ".") %>%
fmt_number(columns = poblacion, decimals = 0, sep_mark = ".") %>%
tab_options(table.font.size = px(12), data_row.padding = px(5))| Tamaño muestral y estructura del diseño por período | ||||||
| Período | Registros | UPM | Estratos | Población expandida | Registros PET | Registros PEA |
|---|---|---|---|---|---|---|
| Previo (2019-12) | 59.208 | 2.438 | 163 | 17.454.560 | 45.082 | 29.404 |
| Posterior (2022-12) | 28.993 | 1.288 | 150 | 18.183.483 | 22.637 | 14.633 |
La ENEMDU no es una muestra aleatoria simple: combina estratificación geográfica, selección de unidades primarias de muestreo (UPM) dentro de cada estrato y ponderadores que corrigen probabilidades desiguales de selección y no respuesta. Ignorar esa estructura produce errores estándar sesgados a la baja y, por tanto, intervalos de confianza artificialmente estrechos y pruebas de hipótesis excesivamente liberales.
El diseño se declara con srvyr::as_survey_design()
indicando el conglomerado como identificador de primera etapa, el
estrato como variable de estratificación y el factor de expansión como
ponderador, con anidamiento de las UPM dentro de los estratos. Como las
dos rondas provienen de muestras independientes, los identificadores de
estrato y UPM se hacen únicos por período antes de apilar las bases; de
este modo el período queda absorbido dentro de la estructura de
estratificación y las varianzas de cada ronda se estiman por separado,
sin inducir correlaciones espurias entre conglomerados de rondas
distintas. Los estratos con una sola UPM se tratan con la opción de
ajuste centrada en la media global, que es la práctica habitual y
conservadora en este contexto.
Todas las cifras reportadas —proporciones, errores estándar, intervalos y contrastes— se calculan sobre este objeto de diseño. No se emplean promedios ni proporciones simples en ningún punto del análisis.
Las tasas laborales son estimaciones de dominio: la tasa de desempleo, por ejemplo, se define sobre la PEA y no sobre toda la muestra. El tratamiento correcto consiste en subconjuntar el objeto de diseño —no el data frame subyacente— de modo que las UPM y estratos del dominio conserven la información necesaria para la estimación de varianza. Eso es lo que hace el filtrado aplicado sobre el diseño para construir los dominios PET y PEA.
Para cada indicador se estima la diferencia entre el período
posterior y el previo mediante survey::svyttest(), que
ajusta un modelo lineal ponderado del indicador binario sobre el período
y contrasta el coeficiente asociado usando la varianza del diseño. La
diferencia estimada es, por construcción, la diferencia de proporciones
entre ambos períodos; el intervalo de confianza y el valor p provienen
de la distribución t con los grados de libertad del diseño,
calculados como el número de UPM menos el número de estratos.
Adicionalmente se reporta la diferencia mínima detectable (DMD): el tamaño de efecto que el diseño habría detectado con una potencia del 80 % a un nivel de significación del 5 %, calculado como \((t_{0{,}975;\,gl} + t_{0{,}80;\,gl}) \times EE_{\text{diferencia}}\). Esta magnitud es imprescindible para interpretar correctamente un resultado no significativo: sin ella, la ausencia de evidencia no puede distinguirse de la falta de precisión.
estadisticos <- c("Tasa", "Error estándar", "IC 95 % inf", "IC 95 % sup")
orden_columnas <- c(
"indicador", "denominador",
as.vector(t(outer(unname(etiquetas_periodo), estadisticos, paste, sep = "__")))
)
estimaciones %>%
mutate(periodo_etq = recode(as.character(periodo), !!!etiquetas_periodo)) %>%
select(indicador, denominador, periodo_etq,
Tasa = tasa, `Error estándar` = ee, `IC 95 % inf` = li, `IC 95 % sup` = ls) %>%
pivot_wider(
names_from = periodo_etq,
values_from = all_of(estadisticos),
names_glue = "{periodo_etq}__{.value}"
) %>%
select(all_of(orden_columnas)) %>%
gt() %>%
tab_header(
title = "Indicadores del mercado laboral por período",
subtitle = "Estimaciones con diseño muestral complejo, error estándar e intervalo de confianza al 95 %"
) %>%
cols_label(indicador = "Indicador", denominador = "Denominador") %>%
tab_spanner_delim(delim = "__") %>%
fmt_percent(columns = where(is.numeric), decimals = 2,
dec_mark = ",", sep_mark = ".") %>%
cols_align(align = "left", columns = c(indicador, denominador)) %>%
tab_options(table.font.size = px(11), data_row.padding = px(4))| Indicadores del mercado laboral por período | |||||||||
| Estimaciones con diseño muestral complejo, error estándar e intervalo de confianza al 95 % | |||||||||
| Indicador | Denominador |
Previo (2019-12)
|
Posterior (2022-12)
|
||||||
|---|---|---|---|---|---|---|---|---|---|
| Tasa | Error estándar | IC 95 % inf | IC 95 % sup | Tasa | Error estándar | IC 95 % inf | IC 95 % sup | ||
| Tasa de participación global | PET | 65,30% | 0,39% | 64,54% | 66,07% | 64,58% | 0,85% | 62,91% | 66,26% |
| Tasa de empleo adecuado | PEA | 38,85% | 0,66% | 37,56% | 40,14% | 36,04% | 1,20% | 33,69% | 38,38% |
| Tasa de subempleo | PEA | 17,79% | 0,49% | 16,82% | 18,76% | 19,37% | 0,87% | 17,67% | 21,07% |
| Tasa de otro empleo no pleno | PEA | 28,00% | 0,43% | 27,16% | 28,85% | 30,51% | 0,92% | 28,70% | 32,32% |
| Tasa de empleo no remunerado | PEA | 10,92% | 0,56% | 9,83% | 12,02% | 10,70% | 1,20% | 8,35% | 13,04% |
| Tasa de desempleo | PEA | 3,84% | 0,23% | 3,39% | 4,30% | 3,19% | 0,25% | 2,70% | 3,69% |
Los niveles estimados reproducen el orden de magnitud de las cifras oficiales publicadas por el INEC para cada ronda, lo que ofrece una verificación externa de que la construcción de variables y la declaración del diseño son correctas.
estimaciones %>%
ggplot(aes(x = tasa, y = fct_rev(indicador), colour = periodo)) +
geom_linerange(aes(xmin = li, xmax = ls), linewidth = 1.1,
position = position_dodge(width = 0.55)) +
geom_point(size = 2.8, position = position_dodge(width = 0.55)) +
scale_colour_manual(values = paleta, labels = unname(etiquetas_periodo)) +
scale_x_continuous(labels = label_percent(accuracy = 1)) +
labs(
title = "Indicadores del mercado laboral ecuatoriano",
subtitle = "Estimación puntual e intervalo de confianza al 95 %",
x = "Tasa estimada", y = NULL,
caption = "Fuente: elaboración propia con microdatos ENEMDU (INEC). Estimaciones bajo diseño muestral complejo."
)Tasas laborales estimadas por período con intervalos de confianza al 95 % bajo diseño muestral complejo.
composicion <- diseno_pea %>%
filter(!is.na(categoria_pea)) %>%
group_by(periodo, categoria_pea) %>%
summarise(p = survey_mean(vartype = "ci")) %>%
ungroup() %>%
mutate(
categoria_pea = fct_reorder(categoria_pea, p, .fun = max),
periodo_etq = factor(recode(as.character(periodo), !!!etiquetas_periodo),
levels = rev(unname(etiquetas_periodo)))
)
composicion %>%
ggplot(aes(x = p, y = periodo_etq, fill = categoria_pea)) +
geom_col(width = 0.55, colour = "white", linewidth = 0.3) +
scale_x_continuous(labels = label_percent(accuracy = 1), expand = expansion(mult = c(0, 0.01))) +
scale_fill_brewer(palette = "Set2", direction = -1) +
labs(
title = "Composición de la PEA por período",
subtitle = "Participación de cada categoría de condición de actividad dentro de la PEA",
x = "Participación en la PEA", y = NULL,
caption = "Fuente: elaboración propia con microdatos ENEMDU (INEC)."
) +
guides(fill = guide_legend(nrow = 2, reverse = TRUE))Composición de la población económicamente activa según categoría de condición de actividad.
contrastes %>%
select(indicador, diferencia, ee, li, ls, gl, valor_p, dme) %>%
gt() %>%
tab_header(
title = "Diferencia entre el período posterior y el previo",
subtitle = "Prueba t con varianza del diseño muestral complejo"
) %>%
cols_label(
indicador = "Indicador", diferencia = "Diferencia (p.p.)", ee = "Error estándar (p.p.)",
li = "IC 95 % inferior", ls = "IC 95 % superior", gl = "Grados de libertad",
valor_p = "Valor p", dme = "Diferencia mínima detectable (p.p.)"
) %>%
fmt(columns = c(diferencia, ee, li, ls, dme),
fns = function(x) fmt_n(x * 100, 2)) %>%
fmt_number(columns = gl, decimals = 0, sep_mark = ".") %>%
fmt(columns = valor_p, fns = fmt_p) %>%
cols_align(align = "left", columns = indicador) %>%
tab_options(table.font.size = px(11), data_row.padding = px(4))| Diferencia entre el período posterior y el previo | |||||||
| Prueba t con varianza del diseño muestral complejo | |||||||
| Indicador | Diferencia (p.p.) | Error estándar (p.p.) | IC 95 % inferior | IC 95 % superior | Grados de libertad | Valor p | Diferencia mínima detectable (p.p.) |
|---|---|---|---|---|---|---|---|
| Tasa de participación global | -0,72 | 0,94 | -2,56 | 1,12 | 3.412 | 0,444 | 2,63 |
| Tasa de empleo adecuado | -2,81 | 1,37 | -5,49 | -0,13 | 3.412 | 0,040 | 3,83 |
| Tasa de subempleo | 1,58 | 1,00 | -0,38 | 3,53 | 3.412 | 0,113 | 2,79 |
| Tasa de otro empleo no pleno | 2,50 | 1,02 | 0,51 | 4,50 | 3.412 | 0,014 | 2,85 |
| Tasa de empleo no remunerado | -0,23 | 1,32 | -2,82 | 2,36 | 3.412 | 0,864 | 3,70 |
| Tasa de desempleo | -0,65 | 0,34 | -1,32 | 0,02 | 3.412 | 0,058 | 0,96 |
contrastes %>%
ggplot(aes(x = diferencia, y = fct_rev(indicador))) +
geom_vline(xintercept = 0, linetype = "dashed", colour = "grey40") +
geom_linerange(aes(xmin = li, xmax = ls), linewidth = 1.1, colour = "#2C6E9B") +
geom_point(size = 2.8, colour = "#2C6E9B") +
scale_x_continuous(labels = label_percent(accuracy = 0.1)) +
labs(
title = "Diferencia entre el período posterior y el previo",
subtitle = "Estimación puntual e intervalo de confianza al 95 % (puntos porcentuales)",
x = "Diferencia", y = NULL,
caption = "Fuente: elaboración propia con microdatos ENEMDU (INEC)."
)Diferencias estimadas entre períodos con intervalos de confianza al 95 %. La línea vertical marca la ausencia de diferencia.
n_sig <- sum(contrastes$valor_p < 0.05)
n_tot <- nrow(contrastes)
sig_txt <- if (n_sig == 0) {
"ninguno de los indicadores presenta una diferencia estadísticamente significativa al 5 %"
} else {
paste0(n_sig, " de ", n_tot, " indicadores presentan una diferencia estadísticamente significativa al 5 % (",
paste(as.character(contrastes$indicador[contrastes$valor_p < 0.05]), collapse = "; "), ")")
}
dme_media <- mean(contrastes$dme) * 100
cat(paste0(
"Bajo el diseño declarado y con el nivel de significación convencional, ", sig_txt,
". La diferencia mínima detectable promedio del conjunto de contrastes es de aproximadamente ",
fmt_n(dme_media, 2), " puntos porcentuales, con un rango entre ",
fmt_n(min(contrastes$dme) * 100, 2), " y ", fmt_n(max(contrastes$dme) * 100, 2),
" puntos porcentuales según el indicador."
))Bajo el diseño declarado y con el nivel de significación convencional, 2 de 6 indicadores presentan una diferencia estadísticamente significativa al 5 % (Tasa de empleo adecuado; Tasa de otro empleo no pleno). La diferencia mínima detectable promedio del conjunto de contrastes es de aproximadamente 2,79 puntos porcentuales, con un rango entre 0,96 y 3,83 puntos porcentuales según el indicador.
sentido <- function(x) if (x > 0) "al alza" else "a la baja"
descr_ind <- function(fila) {
paste0(
"**", as.character(fila$indicador), "**: ", fmt_n(abs(fila$diferencia) * 100, 2),
" p.p. ", sentido(fila$diferencia),
" (IC 95 %: ", fmt_n(fila$li * 100, 2), " a ", fmt_n(fila$ls * 100, 2),
" p.p.; p = ", fmt_p(fila$valor_p), ")"
)
}
sig <- contrastes %>% filter(valor_p < 0.05) %>% arrange(desc(abs(diferencia)))
nosig <- contrastes %>% filter(valor_p >= 0.05) %>% arrange(desc(abs(diferencia)))
parrafos <- character(0)
if (nrow(sig) > 0) {
parrafos <- c(parrafos, paste0(
"Los indicadores cuyo cambio entre períodos resulta incompatible con la hipótesis nula al 5 % son: ",
paste(purrr::map_chr(seq_len(nrow(sig)), ~ descr_ind(sig[.x, ])), collapse = "; "),
". En estos casos el intervalo de confianza excluye el cero, de modo que los datos sí permiten ",
"descartar la igualdad entre períodos. Conviene separar dos afirmaciones distintas: que la ",
"diferencia es estadísticamente detectable, que es lo que muestra el contraste, y que la ",
"diferencia sea atribuible al cambio regulatorio, que el diseño empleado no permite sostener."
))
}
if (nrow(nosig) > 0) {
parrafos <- c(parrafos, paste0(
"Los indicadores para los que no se rechaza la hipótesis nula al 5 % son: ",
paste(purrr::map_chr(seq_len(nrow(nosig)), ~ descr_ind(nosig[.x, ])), collapse = "; "),
". Para estos, el intervalo de confianza contiene el cero, lo que no equivale a haber ",
"demostrado ausencia de cambio."
))
}
if (nrow(sig) > 0 && nrow(nosig) > 0) {
parrafos <- c(parrafos, paste0(
"El patrón conjunto es informativo por sí mismo: el volumen de la fuerza de trabajo se mantiene ",
"relativamente estable mientras la composición interna del empleo se desplaza. Es decir, el ",
"movimiento no se produce tanto en el margen de entrada y salida del mercado laboral como en la ",
"calidad de la ocupación de quienes ya estaban dentro."
))
}
cat(paste(parrafos, collapse = "\n\n"))Los indicadores cuyo cambio entre períodos resulta incompatible con la hipótesis nula al 5 % son: Tasa de empleo adecuado: 2,81 p.p. a la baja (IC 95 %: -5,49 a -0,13 p.p.; p = 0,040); Tasa de otro empleo no pleno: 2,50 p.p. al alza (IC 95 %: 0,51 a 4,50 p.p.; p = 0,014). En estos casos el intervalo de confianza excluye el cero, de modo que los datos sí permiten descartar la igualdad entre períodos. Conviene separar dos afirmaciones distintas: que la diferencia es estadísticamente detectable, que es lo que muestra el contraste, y que la diferencia sea atribuible al cambio regulatorio, que el diseño empleado no permite sostener.
Los indicadores para los que no se rechaza la hipótesis nula al 5 % son: Tasa de subempleo: 1,58 p.p. al alza (IC 95 %: -0,38 a 3,53 p.p.; p = 0,113); Tasa de participación global: 0,72 p.p. a la baja (IC 95 %: -2,56 a 1,12 p.p.; p = 0,444); Tasa de desempleo: 0,65 p.p. a la baja (IC 95 %: -1,32 a 0,02 p.p.; p = 0,058); Tasa de empleo no remunerado: 0,23 p.p. a la baja (IC 95 %: -2,82 a 2,36 p.p.; p = 0,864). Para estos, el intervalo de confianza contiene el cero, lo que no equivale a haber demostrado ausencia de cambio.
El patrón conjunto es informativo por sí mismo: el volumen de la fuerza de trabajo se mantiene relativamente estable mientras la composición interna del empleo se desplaza. Es decir, el movimiento no se produce tanto en el margen de entrada y salida del mercado laboral como en la calidad de la ocupación de quienes ya estaban dentro.
Cuando el intervalo de confianza de la diferencia contiene el cero, la lectura correcta es que los datos son compatibles con la hipótesis nula de igualdad entre períodos, no que la igualdad haya quedado demostrada. La prueba de hipótesis frecuentista es asimétrica: permite acumular evidencia en contra de la nula, nunca a favor de ella. No rechazar significa únicamente que la evidencia disponible resulta insuficiente para descartar que la diferencia poblacional sea cero; la misma evidencia es también compatible con diferencias pequeñas pero reales, y con cualquier valor contenido dentro del intervalo.
Por eso el objeto informativo relevante no es el valor p sino el intervalo de confianza. Un intervalo que se extiende, por ejemplo, entre −1,5 y +1,2 puntos porcentuales dice algo sustantivo: efectos de más de un punto y medio en cualquier dirección son improbables a la luz de los datos. Un intervalo que se extiende entre −6 y +5 puntos porcentuales, en cambio, es prácticamente ininformativo: no descarta efectos de magnitud económica considerable. Los intervalos reportados en la tabla de contrastes deben leerse con ese criterio, indicador por indicador.
La simetría del argumento también obliga a moderar la lectura en sentido contrario. Un valor p pequeño indica que los datos son poco probables bajo la hipótesis nula, pero no mide la magnitud del efecto ni su relevancia sustantiva, y menos aún su origen causal. Con muestras grandes, como las que resultan de acumular rondas de la ENEMDU, diferencias de magnitud modesta alcanzan significación estadística con facilidad; la pregunta pertinente pasa entonces a ser si el tamaño estimado es económicamente relevante, no si el valor p cruza un umbral convencional.
La diferencia mínima detectable cuantifica la capacidad discriminatoria del ejercicio. Con la precisión alcanzada, el análisis habría detectado con probabilidad del 80 % diferencias del orden de la magnitud reportada en la última columna de la tabla de contrastes. Efectos menores que ese umbral tienen alta probabilidad de pasar inadvertidos aunque existan: en esos casos, el no rechazo es un resultado esperable incluso bajo un efecto real no nulo.
Esta distinción importa para la lectura de política pública. Las regulaciones laborales de emergencia se diseñaron para operar sobre márgenes específicos —preservación del vínculo laboral mediante reducción de jornada, flexibilización de la contratación en sectores afectados— cuyos efectos agregados plausibles sobre tasas nacionales son de pocos puntos porcentuales o menos. Si la diferencia mínima detectable del ejercicio es del mismo orden o mayor que el efecto teóricamente esperable, el diseño no tiene resolución suficiente para pronunciarse, y concluir “no hubo efecto” sería una inferencia injustificada.
tam_max <- max(resumen_muestra$registros)
tam_min <- min(resumen_muestra$registros)
razon <- tam_max / tam_min
cat(paste0(
"En este ejercicio la diferencia mínima detectable se sitúa entre ",
fmt_n(min(contrastes$dme) * 100, 2), " y ", fmt_n(max(contrastes$dme) * 100, 2),
" puntos porcentuales según el indicador. La precisión no es simétrica entre períodos: ",
"la ronda con mayor número de registros aporta errores estándar sensiblemente menores, y la ",
"varianza de la diferencia queda dominada por la ronda más pequeña. ",
if (razon >= 3) paste0(
"La razón entre los tamaños muestrales de ambas rondas es de aproximadamente ",
fmt_n(razon, 1), " a 1, lo que indica que se están comparando rondas de distinta naturaleza ",
"—por ejemplo, una ronda mensual frente a una base acumulada—. Esa asimetría no invalida el ",
"contraste, pero introduce diferencias de cobertura temporal y de construcción de ponderadores ",
"que se suman a cualquier efecto sustantivo."
) else ""
))En este ejercicio la diferencia mínima detectable se sitúa entre 0,96 y 3,83 puntos porcentuales según el indicador. La precisión no es simétrica entre períodos: la ronda con mayor número de registros aporta errores estándar sensiblemente menores, y la varianza de la diferencia queda dominada por la ronda más pequeña.
limite <- contrastes %>% filter(valor_p < 0.05, abs(diferencia) < dme)
anchos <- contrastes %>% filter(valor_p >= 0.05, dme > 0.03)
if (nrow(limite) > 0) {
cat(paste0(
"\n\nUn matiz que conviene explicitar: en ",
paste(as.character(limite$indicador), collapse = " y en "),
" la diferencia estimada es menor que la propia diferencia mínima detectable. ",
"Ambas cosas son compatibles —el umbral corresponde a una potencia del 80 %, no a un mínimo ",
"por debajo del cual el rechazo sea imposible—, pero implican que el rechazo se produce en el ",
"límite de la resolución del diseño. Un estudio con esta precisión tenía menos de 80 % de ",
"probabilidad de detectar un efecto de la magnitud que efectivamente estimó, de modo que la ",
"estimación puntual es frágil: conviene tratarla como evidencia de que hubo movimiento, no ",
"como una medición fiable de su tamaño."
))
}Un matiz que conviene explicitar: en Tasa de empleo adecuado y en Tasa de otro empleo no pleno la diferencia estimada es menor que la propia diferencia mínima detectable. Ambas cosas son compatibles —el umbral corresponde a una potencia del 80 %, no a un mínimo por debajo del cual el rechazo sea imposible—, pero implican que el rechazo se produce en el límite de la resolución del diseño. Un estudio con esta precisión tenía menos de 80 % de probabilidad de detectar un efecto de la magnitud que efectivamente estimó, de modo que la estimación puntual es frágil: conviene tratarla como evidencia de que hubo movimiento, no como una medición fiable de su tamaño.
if (nrow(anchos) > 0) {
cat(paste0(
"\n\nEn el otro extremo, para ",
paste(as.character(anchos$indicador), collapse = ", "),
" el intervalo de confianza es lo bastante ancho como para no descartar efectos de magnitud ",
"económicamente relevante. En estos casos el no rechazo refleja falta de precisión antes que ",
"evidencia de estabilidad, y no debe leerse como ausencia de cambio."
))
}En el otro extremo, para Tasa de empleo no remunerado el intervalo de confianza es lo bastante ancho como para no descartar efectos de magnitud económicamente relevante. En estos casos el no rechazo refleja falta de precisión antes que evidencia de estabilidad, y no debe leerse como ausencia de cambio.
Existen tres caminos para ganar potencia sin cambiar la pregunta. El primero es acumular rondas: las bases trimestrales y anuales de la ENEMDU multiplican el tamaño muestral efectivo respecto de una ronda mensual. El segundo es restringir el análisis a los subgrupos donde el efecto esperado es mayor —por rama de actividad, tamaño de establecimiento o formalidad— aceptando el costo de una menor precisión por celda y el riesgo de comparaciones múltiples. El tercero es reemplazar el contraste de medias por un modelo que absorba variación mediante covariables, lo que reduce la varianza residual sin alterar la estimación de interés.
hay_sig <- nrow(sig) > 0
texto_apertura <- if (!hay_sig) {
paste0(
"Al nivel de agregación nacional y con la resolución disponible, la estructura de la condición ",
"de actividad no muestra un desplazamiento detectable entre ambos períodos."
)
} else if (nrow(nosig) == 0) {
paste0(
"Todos los indicadores examinados se desplazan de forma estadísticamente detectable entre ambos ",
"períodos, lo que describe un mercado laboral cuya estructura no es la misma al inicio y al ",
"final del intervalo considerado."
)
} else {
paste0(
"El cuadro que emerge es mixto: parte de los indicadores se desplaza de forma estadísticamente ",
"detectable y parte no. La estructura de la ocupación cambia, mientras que los márgenes ligados ",
"a la participación en la fuerza de trabajo se mantienen dentro de los límites de la ",
"incertidumbre muestral."
)
}
cat(paste0(
texto_apertura, " Lo que el ejercicio no puede hacer, en ningún escenario, es atribuir ese ",
"resultado a las regulaciones laborales de emergencia. Las diferencias estimadas son compatibles ",
"con varias historias distintas y mutuamente no excluyentes: que el efecto neto de la regulación ",
"haya sido de la magnitud observada; que el desplazamiento provenga del shock sanitario y de la ",
"contracción económica, con la regulación jugando un papel menor o incluso amortiguador; que ",
"efectos de signo opuesto se cancelen parcialmente en el agregado —por ejemplo, preservación de ",
"empleos formales acompañada de deterioro en su calidad—; que parte del movimiento corresponda a ",
"los cambios metodológicos del operativo de campo entre 2020 y 2021; o que el efecto se concentre ",
"en márgenes que las tasas de condición de actividad no capturan, como horas trabajadas, ",
"modalidad contractual o ingresos laborales reales.\n\n",
"Ninguna de esas explicaciones puede descartarse ni confirmarse con la evidencia presentada. ",
"El ejercicio establece con solidez *qué* cambió y con cuánta precisión se estima ese cambio; ",
"no establece *por qué* cambió, y presentar lo segundo como si se derivara de lo primero sería ",
"el error de lectura más costoso que este informe podría inducir."
))El cuadro que emerge es mixto: parte de los indicadores se desplaza de forma estadísticamente detectable y parte no. La estructura de la ocupación cambia, mientras que los márgenes ligados a la participación en la fuerza de trabajo se mantienen dentro de los límites de la incertidumbre muestral. Lo que el ejercicio no puede hacer, en ningún escenario, es atribuir ese resultado a las regulaciones laborales de emergencia. Las diferencias estimadas son compatibles con varias historias distintas y mutuamente no excluyentes: que el efecto neto de la regulación haya sido de la magnitud observada; que el desplazamiento provenga del shock sanitario y de la contracción económica, con la regulación jugando un papel menor o incluso amortiguador; que efectos de signo opuesto se cancelen parcialmente en el agregado —por ejemplo, preservación de empleos formales acompañada de deterioro en su calidad—; que parte del movimiento corresponda a los cambios metodológicos del operativo de campo entre 2020 y 2021; o que el efecto se concentre en márgenes que las tasas de condición de actividad no capturan, como horas trabajadas, modalidad contractual o ingresos laborales reales.
Ninguna de esas explicaciones puede descartarse ni confirmarse con la evidencia presentada. El ejercicio establece con solidez qué cambió y con cuánta precisión se estima ese cambio; no establece por qué cambió, y presentar lo segundo como si se derivara de lo primero sería el error de lectura más costoso que este informe podría inducir.
El diseño empleado es una comparación de dos cortes transversales separados en el tiempo. Su validez causal descansa en un supuesto que es, en este contexto, manifiestamente falso: que en ausencia del cambio regulatorio los indicadores del período posterior habrían sido iguales a los del período previo. Entre ambos momentos ocurrieron, de manera simultánea e inseparable:
Cualquier diferencia estimada es la suma de todos estos componentes más el efecto de la regulación laboral. El ejercicio no ofrece manera de separarlos.
mes_de <- function(etq) {
partes <- strsplit(etq, "-", fixed = TRUE)[[1]]
if (length(partes) == 2) as.integer(partes[2]) else NA_integer_
}
mes_pre <- mes_de(etq_pre)
mes_post <- mes_de(etq_post)
mismo_mes <- !is.na(mes_pre) && !is.na(mes_post) && mes_pre == mes_post
cat(paste0(
"\n- **Comparabilidad de las rondas**: comparar rondas de distinta naturaleza —mensual frente a ",
"acumulada trimestral o anual— implica diferencias en tamaño muestral, cobertura temporal y ",
"construcción de ponderadores que se suman a cualquier efecto sustantivo. ",
if (razon >= 3) paste0(
"En la compilación actual los tamaños muestrales difieren en una razón de aproximadamente ",
fmt_n(razon, 1), " a 1, lo que sugiere que las rondas seleccionadas no son del mismo tipo. ",
"Se recomienda repetir el ejercicio con rondas homólogas."
) else paste0(
"En la compilación actual los tamaños muestrales de ambas rondas son del mismo orden, ",
"lo que indica que se están comparando rondas del mismo tipo."
),
"\n",
"- **Estacionalidad**: los indicadores laborales ecuatorianos presentan patrones estacionales ",
"marcados, en particular por el empleo temporal de fin de año. ",
if (mismo_mes) paste0(
"En la compilación actual ambas rondas corresponden al mismo mes del año, de modo que la ",
"estacionalidad queda controlada por construcción y no explica las diferencias estimadas."
) else paste0(
"En la compilación actual las rondas no corresponden al mismo mes del año, de modo que las ",
"diferencias estimadas confunden el efecto de interés con el componente estacional."
)
))Ninguna estrategia disponible con datos de encuesta de hogares resuelve el problema por completo, pero varias lo mejoran sustancialmente:
Cada una de estas alternativas requiere supuestos propios, y ninguna es automáticamente superior. Lo que sí es claro es que la comparación de dos medias entre dos cortes transversales, aun ejecutada correctamente desde el punto de vista de la inferencia por muestreo, no constituye evidencia causal sobre el efecto de la regulación.
survey: Analysis of Complex Survey
Samples. Paquete de R.srvyr:
‘dplyr’-Like Syntax for Summary Statistics of Survey Data.
Paquete de R.#> R version 4.4.2 (2024-10-31 ucrt)
#> Platform: x86_64-w64-mingw32/x64
#> Running under: Windows 11 x64 (build 26100)
#>
#> Matrix products: default
#>
#>
#> locale:
#> [1] LC_COLLATE=Spanish_Ecuador.utf8 LC_CTYPE=Spanish_Ecuador.utf8
#> [3] LC_MONETARY=Spanish_Ecuador.utf8 LC_NUMERIC=C
#> [5] LC_TIME=Spanish_Ecuador.utf8
#>
#> time zone: America/Guayaquil
#> tzcode source: internal
#>
#> attached base packages:
#> [1] grid stats graphics grDevices utils datasets methods
#> [8] base
#>
#> other attached packages:
#> [1] here_1.0.1 knitr_1.50 gt_1.3.0 scales_1.4.0
#> [5] janitor_2.2.1 srvyr_1.3.1 survey_4.4-2 survival_3.7-0
#> [9] Matrix_1.7-1 haven_2.5.4 lubridate_1.9.4 forcats_1.0.0
#> [13] stringr_1.5.1 dplyr_1.1.4 purrr_1.0.4 readr_2.1.5
#> [17] tidyr_1.3.1 tibble_3.2.1 ggplot2_4.0.3 tidyverse_2.0.0
#>
#> loaded via a namespace (and not attached):
#> [1] sass_0.4.9 generics_0.1.3 xml2_1.3.8 stringi_1.8.4
#> [5] lattice_0.22-6 hms_1.1.3 digest_0.6.37 magrittr_2.0.3
#> [9] evaluate_1.0.3 timechange_0.3.0 RColorBrewer_1.1-3 fastmap_1.2.0
#> [13] rprojroot_2.0.4 jsonlite_1.8.9 DBI_1.2.3 jquerylib_0.1.4
#> [17] cli_3.6.3 mitools_2.4 rlang_1.1.4 splines_4.4.2
#> [21] withr_3.0.2 cachem_1.1.0 yaml_2.3.10 tools_4.4.2
#> [25] tzdb_0.4.0 vctrs_0.6.5 R6_2.6.1 lifecycle_1.0.4
#> [29] snakecase_0.11.1 fs_1.6.5 pkgconfig_2.0.3 pillar_1.10.2
#> [33] bslib_0.9.0 gtable_0.3.6 glue_1.8.0 Rcpp_1.0.14
#> [37] xfun_0.52 tidyselect_1.2.1 rstudioapi_0.18.0 farver_2.1.2
#> [41] htmltools_0.5.8.1 labeling_0.4.3 rmarkdown_2.30 compiler_4.4.2
#> [45] S7_0.2.0