Estudio de caso ECH: De STATA a R (ES)
Source:vignettes/ech-case-study-es.Rmd
ech-case-study-es.RmdEl problema de la compatibilización de la ECH
La Encuesta Continua de Hogares (ECH) de Uruguay es publicada anualmente por el Instituto Nacional de Estadística (INE). A lo largo de los años, el INE ha modificado nombres de variables, definiciones del libro de códigos y la estructura de módulos entre ediciones. Quienes investigan con datos de la ECH deben compatibilizar variables —es decir, mapear las distintas convenciones de nombres a un esquema común— antes de que cualquier análisis inter-anual sea posible.
La compatibilización de la ECH ha sido un trabajo histórico del Instituto de Economía (IECON) de la Universidad de la República. El dataset resultante está disponible en FCEA - ECH Compatibilizadas (Instituto de Economía, 2020). metasurvey busca complementar y facilitar este tipo de trabajo ofreciendo herramientas reproducibles en R.
En la academia uruguaya, este trabajo se ha realizado en STATA durante más de 30 años. Un pipeline de compatibilización típico consiste en aproximadamente 8 archivos .do por año, cubriendo:
-
2_correc_datos.do– Carga de datos crudos, unión de archivos de personas y hogares, corrección de nombres de variables -
3_compatibilizacion_mod_1_4.do– Armonización de módulos demográficos, de salud, educación y trabajo -
4_ingreso_ht11.do– Construcción del ingreso del hogar (ht11) -
5_descomp_fuentes.do– Descomposición del ingreso por fuente -
6_ingreso_ht11_sss.do– Ajustes de seguridad social -
7_check_ingr.do– Validación de variables de ingreso -
8_arregla_base_comp.do– Preparación final del dataset -
9_labels.do– Aplicación de etiquetas de valores
Multiplicado por más de 30 años, eso significa más de 240 scripts de STATA haciendo esencialmente la misma tarea: mapear variables crudas de la ECH a un esquema común.
metasurvey resuelve esto con recipes. En lugar de
mantener cientos de archivos .do, se escribe un único
recipe que codifica la lógica de transformación y puede aplicarse a
cualquier edición de la ECH.
STATA vs metasurvey: Comparación lado a lado
A continuación se presenta un fragmento de un script de compatibilización típico en STATA para sexo, edad y parentesco:
* STATA: Typical ECH compatibility script
* sexo
g bc_pe2 = e26
* edad
g bc_pe3 = e27
* parentesco (e30 en ECH 2023, era e31 en ediciones anteriores)
g bc_pe4 = -9
replace bc_pe4 = 1 if e30 == 1
replace bc_pe4 = 2 if e30 == 2
replace bc_pe4 = 3 if e30 == 3 | e30 == 4 | e30 == 5
replace bc_pe4 = 4 if e30 == 7 | e30 == 8
replace bc_pe4 = 5 if e30 == 6 | e30 == 9 | e30 == 10 | e30 == 11 | e30 == 12
replace bc_pe4 = 6 if e30 == 13
replace bc_pe4 = 7 if e30 == 14El equivalente en metasurvey:
svy <- step_rename(svy, sex = e26, age = e27)
svy <- step_recode(svy, relationship,
e30 == 1 ~ "Head",
e30 == 2 ~ "Spouse",
e30 %in% 3:5 ~ "Child",
e30 %in% c(7, 8) ~ "Parent",
e30 %in% c(6, 9:12) ~ "Other relative",
e30 == 13 ~ "Domestic service",
e30 == 14 ~ "Non-relative",
.default = NA_character_,
comment = "Relationship to head of household"
)Las diferencias clave:
| Aspecto | Archivo .do de STATA |
Recipe de metasurvey |
|---|---|---|
| Formato | Script plano con rutas hardcodeadas | JSON portable con metadatos |
| Validación | Verificaciones manuales con assert
|
Método automático validate()
|
| Documentación | Comentarios en el código | Método auto-generado doc()
|
| Compartir | Copiar archivos por email/servidor | Registry con búsqueda y versionado |
| Reproducibilidad | Depende de rutas de archivos y entorno | Autocontenido, cualquier máquina |
| Inter-ediciones | Duplicar script por año | Un recipe, múltiples ediciones |
Carga de microdatos reales de la ECH
Utilizamos una muestra de microdatos reales de la ECH 2023, publicada por el INE. La muestra contiene 200 hogares (~500 personas) con las variables clave necesarias para el analisis del mercado laboral.
library(metasurvey)
library(data.table)
# Cargar muestra real de la ECH 2023
dt <- fread(system.file("extdata", "ech_2023_sample.csv", package = "metasurvey"))
svy <- Survey$new(
data = dt,
edition = "2023",
type = "ech",
engine = "data.table",
weight = add_weight(annual = "W_ANO")
)
head(get_data(svy), 3)
#> ID nper anio mes region dpto nom_dpto e26 e27 e30 e51_2
#> <int> <int> <int> <int> <int> <int> <char> <int> <int> <int> <int>
#> 1: 34561 1 2023 1 1 1 Montevideo 2 26 1 6
#> 2: 34561 2 2023 1 1 1 Montevideo 2 45 7 6
#> 3: 34561 3 2023 1 1 1 Montevideo 2 7 4 1
#> POBPCOAC SUBEMPLEO HT11 pobre06 W_ANO
#> <int> <int> <num> <int> <int>
#> 1: 2 0 55429.6 0 57
#> 2: 4 0 55429.6 0 57
#> 3: 1 0 55429.6 0 57Paso 1: Variables demográficas
Recodificar los códigos crudos del INE a nombres legibles y recodificar variables categóricas:
# Recode sex from INE codes (e26: 1=Male, 2=Female)
svy <- step_recode(svy, sex,
e26 == 1 ~ "Male",
e26 == 2 ~ "Female",
.default = NA_character_,
comment = "Sex: 1=Male, 2=Female (INE e26)"
)
# Recode age groups (standard ECH grouping, e27 = age)
svy <- step_recode(svy, age_group,
e27 < 14 ~ "Child (0-13)",
e27 < 25 ~ "Youth (14-24)",
e27 < 45 ~ "Adult (25-44)",
e27 < 65 ~ "Mature (45-64)",
.default = "Senior (65+)",
.to_factor = TRUE,
ordered = TRUE,
comment = "Standard age groups for labor statistics"
)Paso 2: Clasificación de la fuerza laboral
La variable POBPCOAC (Población por condición de
actividad) es la clasificación central del estado laboral en la ECH.
Códigos del INE:
- 1 = Menor de 14
- 2 = Ocupado
- 3-5 = Desocupado (diversas subcategorías)
- 6-10 = Inactivo
- 11 = No aplica
Esto replica el marco estándar de fuerza laboral de la OIT:
svy <- step_recode(svy, labor_status,
POBPCOAC == 2 ~ "Employed",
POBPCOAC %in% 3:5 ~ "Unemployed",
POBPCOAC %in% 6:10 ~ "Inactive",
.default = NA_character_,
comment = "ILO labor force status from POBPCOAC"
)
# Create binary indicators
svy <- step_compute(svy,
employed = ifelse(POBPCOAC == 2, 1L, 0L),
unemployed = ifelse(POBPCOAC %in% 3:5, 1L, 0L),
active = ifelse(POBPCOAC %in% 2:5, 1L, 0L),
working_age = ifelse(e27 >= 14, 1L, 0L),
comment = "Labor force binary indicators"
)Paso 3: Variables de ingreso
Construir indicadores de ingreso siguiendo la metodología estándar utilizada con datos de la ECH:
svy <- step_compute(svy,
income_pc = HT11 / nper,
income_thousands = HT11 / 1000,
log_income = log(HT11 + 1),
comment = "Income transformations"
)Paso 4: Clasificación geográfica
Los microdatos reales de la ECH ya incluyen nom_dpto
(nombre del departamento) y region (1-3). Demostramos un
join con lineas de pobreza por region:
poverty_lines <- data.table(
region = 1:3,
poverty_line = c(19000, 12500, 11000),
region_name = c("Montevideo", "Interior loc. >= 5000", "Interior loc. < 5000")
)
svy <- step_join(svy,
poverty_lines,
by = "region",
type = "left",
comment = "Add poverty lines by region"
)Construcción del recipe
Convertir todas las transformaciones en un recipe portable:
ech_recipe <- steps_to_recipe(
name = "ECH Labor Market Indicators",
user = "Research Team",
svy = svy,
description = paste(
"Standard labor market indicators for the ECH.",
"Includes demographic recoding, ILO labor classification,",
"income transformations, and geographic joins."
),
steps = get_steps(svy),
topic = "labor"
)
ech_recipe
#>
#> ── Recipe: ECH Labor Market Indicators ──
#> Author: Research Team
#> Survey: ech / 2023
#> Version: 1.0.0
#> Topic: labor
#> Description: Standard labor market indicators for the ECH. Includes demographic recoding, ILO labor classification, income transformations, and geographic joins.
#> Certification: community
#>
#> ── Requires (6 variables) ──
#> e26, e27, POBPCOAC, HT11, nper, region
#>
#> ── Pipeline (6 steps) ──
#> 1. [recode] -> sex "Sex: 1=Male, 2=Female (INE e26)"
#> 2. [recode] -> age_group "Standard age groups for labor statistics"
#> 3. [recode] -> labor_status "ILO labor force status from POBPCOAC"
#> 4. [compute] -> employed, unemployed, active, working_age "Labor force binary indicators"
#> 5. [compute] -> income_pc, income_thousands, log_income "Income transformations"
#> 6. [step_join] -> (no output) "Add poverty lines by region"
#>
#> ── Produces (10 variables) ──
#> sex [categorical], age_group [categorical], labor_status [categorical], employed [numeric], unemployed [numeric], active [numeric], working_age [numeric], income_pc [numeric], income_thousands [numeric], log_income [numeric]Documentación automática
doc <- ech_recipe$doc()
# What variables does the recipe need?
doc$input_variables
#> [1] "e26" "e27" "POBPCOAC" "HT11" "nper" "region"
# What variables does it create?
doc$output_variables
#> [1] "sex" "age_group" "labor_status" "employed"
#> [5] "unemployed" "active" "working_age" "income_pc"
#> [9] "income_thousands" "log_income"Publicación en el registry
Publicar el recipe para que otras personas puedan descubrirlo y reutilizarlo:
# Set up a local registry
set_backend("local", path = tempfile(fileext = ".json"))
publish_recipe(ech_recipe)
# Ahora cualquiera puede recuperarla por ID
r <- get_recipe("ech_labor")
print(r)
#> NULLEstimación con workflow()
Ahora calculamos indicadores estándar del mercado laboral:
# Mean household income
result_income <- workflow(
list(svy),
survey::svymean(~HT11, na.rm = TRUE),
estimation_type = "annual"
)
result_income
#> stat value se cv confint_lower
#> <char> <num> <num> <num> <num>
#> 1: survey::svymean: HT11 107869.1 3473.836 0.03220417 101060.5
#> confint_upper
#> <num>
#> 1: 114677.7
# Employment rate (proportion employed among total population)
result_employment <- workflow(
list(svy),
survey::svymean(~employed, na.rm = TRUE),
estimation_type = "annual"
)
result_employment
#> stat value se cv confint_lower
#> <char> <num> <num> <num> <num>
#> 1: survey::svymean: employed 0.4422188 0.02343197 0.05298728 0.396293
#> confint_upper
#> <num>
#> 1: 0.4881447Estimación por dominio
Calcular estimaciones por subpoblación:
# Mean income by region name
income_region <- workflow(
list(svy),
survey::svyby(~HT11, ~region_name, survey::svymean, na.rm = TRUE),
estimation_type = "annual"
)
income_region
#> stat value se
#> <char> <num> <num>
#> 1: survey::svyby: HT11 [region_name=Interior loc. < 5000] 90397.57 4632.957
#> 2: survey::svyby: HT11 [region_name=Interior loc. >= 5000] 103877.64 5949.558
#> 3: survey::svyby: HT11 [region_name=Montevideo] 118302.36 5412.484
#> cv confint_lower confint_upper region_name
#> <num> <num> <num> <char>
#> 1: 0.05125091 81317.14 99478.0 Interior loc. < 5000
#> 2: 0.05727467 92216.72 115538.6 Interior loc. >= 5000
#> 3: 0.04575127 107694.09 128910.6 Montevideo
# Employment by sex
employment_sex <- workflow(
list(svy),
survey::svyby(~employed, ~sex, survey::svymean, na.rm = TRUE),
estimation_type = "annual"
)
employment_sex
#> stat value se cv
#> <char> <num> <num> <num>
#> 1: survey::svyby: employed [sex=Female] 0.3824018 0.03146234 0.08227562
#> 2: survey::svyby: employed [sex=Male] 0.5076963 0.03449008 0.06793446
#> confint_lower confint_upper sex
#> <num> <num> <char>
#> 1: 0.3207367 0.4440669 Female
#> 2: 0.4400970 0.5752956 MaleEvaluación de calidad
results_all <- workflow(
list(svy),
survey::svymean(~HT11, na.rm = TRUE),
survey::svymean(~employed, na.rm = TRUE),
estimation_type = "annual"
)
for (i in seq_len(nrow(results_all))) {
cv_pct <- results_all$cv[i] * 100
cat(
results_all$stat[i], ":",
round(cv_pct, 1), "% CV -",
evaluate_cv(cv_pct), "\n"
)
}
#> survey::svymean: HT11 : 3.2 % CV - Excellent
#> survey::svymean: employed : 5.3 % CV - Very goodReproducibilidad: mismo recipe, distinta edición
El poder de los recipes radica en aplicarlos sin modificaciones a datos nuevos. En un flujo de trabajo real, se cargarían los datos de otra edicion y se aplicaría el mismo recipe:
# Cargar microdatos de la ECH 2024 (cuando esten disponibles)
svy_2024 <- load_survey(
path = "ECH_2024.csv",
type = "ech", edition = "2024",
weight = add_weight(annual = "W_ANO")
)
# Aplicar el mismo recipe
svy_2024 <- add_recipe(svy_2024, ech_recipe)
svy_2024 <- bake_recipes(svy_2024)
# Estimar con metodología consistente
result_2024 <- workflow(
list(svy_2024),
survey::svymean(~HT11, na.rm = TRUE),
survey::svymean(~employed, na.rm = TRUE),
estimation_type = "annual"
)Mismo recipe, datos diferentes, metodología consistente.
Para usuarios de STATA: referencia rápida
Si estás haciendo la transición de STATA a R para análisis de encuestas, aquí hay un mapeo de operaciones comunes:
| STATA | metasurvey | Notas |
|---|---|---|
gen var = expr |
step_compute(svy, var = expr) |
Lazy por defecto; llamar a bake_steps() para
ejecutar |
replace var = x if cond |
step_compute(svy, var = ifelse(cond, x, var)) |
Asignación condicional |
recode var (old=new) |
step_recode(svy, new_var, old == val ~ "label") |
Crea una nueva variable |
rename old new |
step_rename(svy, new = old) |
|
drop var1 var2 |
step_remove(svy, var1, var2) |
|
merge using file |
step_join(svy, data, by = "key") |
Left join por defecto |
svy: mean var |
workflow(list(svy), svymean(~var)) |
Devuelve data.table con SE, CV |
svy: total var |
workflow(list(svy), svytotal(~var)) |
|
svy: mean var, over(group) |
workflow(list(svy), svyby(~var, ~group, svymean)) |
|
.do file |
steps_to_recipe() + publish |
Portable, descubrible, con control de versiones |
use "data.dta" |
load_survey(path = "data.dta") |
Lee STATA, CSV, RDS, etc. |
Diferencias clave
Evaluación lazy: En STATA, los comandos se ejecutan inmediatamente. En metasurvey, los steps se registran y se ejecutan juntos con
bake_steps(). Esto permite validación y optimización antes de la ejecución.Inmutabilidad: metasurvey crea nuevas variables en lugar de modificar las existentes.
step_recode()crea una nueva columna; no sobreescribe la variable fuente.Conciencia del diseño: Los pesos muestrales y el diseño están asociados al objeto
Survey. No es necesario prefijar comandos consvy:ni recordar configurar el diseño —workflow()lo maneja automáticamente.Recipes vs archivos .do: Los recipes son auto-documentados (vía
doc()), auto-validados (víavalidate()), y descubribles (vía el registry). Un archivo.does simplemente un script; un recipe es un objeto estructurado y portable.
Datos y agradecimientos
Los datos de ejemplo utilizados en esta vinieta provienen de la Encuesta Continua de Hogares (ECH) 2023, publicada por el Instituto Nacional de Estadistica (INE) de Uruguay. Los microdatos completos estan disponibles en INE.
El paquete ech de Gabriela
Mathieu y Richard Detomasi fue una inspiracion importante para
metasurvey. Mientras que ech provee funciones listas para
usar para calcular indicadores de la ECH, metasurvey toma un enfoque
diferente: permite a los usuarios definir, compartir y reproducir sus
propios pipelines de procesamiento como recipes.
Próximos pasos
- Creación y publicación de recipes – Aprende sobre registries de recipes, certificación y descubrimiento
-
Flujos de
estimación – Profundización en
workflow()yRecipeWorkflow - Paneles rotativos y PoolSurvey – Trabajar con la estructura de panel rotativo de la ECH
- Primeros pasos – Revisar los conceptos básicos