Construyendo una Receta de Demografia ECH (ES)
Source:vignettes/ech-demographics-recipe-es.Rmd
ech-demographics-recipe-es.RmdDos caminos: transpilar rapido o construir bien
metasurvey ofrece dos formas de crear recetas a partir de codigo STATA existente:
-
Transpilar automaticamente con
transpile_stata()– convierte archivos.doa recetas en segundos. Ideal para migrar codigo legacy rapidamente (vervignette("stata-transpiler-es")). - Construir desde cero en R – mas trabajo inicial, pero el resultado es mas limpio, usa modismos propios de R y se controla cada detalle.
El transpilador es un atajo pragmatico: lee cientos de lineas de
STATA y produce una receta funcional, pero la salida hereda la
estructura del codigo original – largas cadenas
gen/replace se convierten en largas llamadas a
step_recode, las variables temporales sobreviven, y
patrones especificos de STATA (como mvencode) se traducen
literalmente en lugar de repensarse.
Una receta artesanal, en cambio, permite redisenar la logica en R desde cero. Se eligen nombres de variables significativos, se combinan transformaciones relacionadas en un solo step, y se omiten variables intermedias que solo existian porque STATA las necesitaba. El resultado es mas corto, mas legible y mas facil de mantener.
Esta vignette construye una receta de demografia desde cero en unas
20 lineas de R. Una version transpilada del mismo pipeline tomaria 80+
steps y arrastraria nombres de variables como bc_pe2 y
bc_pe3 que no significan nada fuera del archivo
.do original.
Configurando la encuesta
Comenzamos con un objeto Survey vacio. Esto declara el tipo de encuesta y la edicion sin cargar datos todavia – la receta funcionara con cualquier dato que le proporcionemos despues.
library(metasurvey)
svy <- survey_empty(type = "ech", edition = "2023")
svyAhora adjuntamos algunos datos de ejemplo. En produccion estos
vendrian de anda_download_microdata("2023") o un archivo
local; aqui los simulamos.
set.seed(42)
n <- 200
dt <- data.table::data.table(
id = rep(1:50, each = 4),
nper = rep(1:4, 50),
pesoano = runif(n, 50, 300),
e26 = sample(1:2, n, replace = TRUE),
e27 = sample(0:90, n, replace = TRUE),
e30 = sample(1:7, n, replace = TRUE),
e51_2 = sample(c(0:6, -9), n, replace = TRUE),
region_4 = sample(1:4, n, replace = TRUE)
)
svy <- svy |> set_data(dt)Construyendo el pipeline
Cada transformacion es un step. Por defecto, los steps son lazy: registran que hacer sin ejecutarlo. Esto permite inspeccionar y modificar el pipeline antes de materializar los resultados.
Comparar con el enfoque del transpilador:
transpile_stata() produciria un step por cada comando
STATA, preservando fielmente cada gen y
replace. Aqui pensamos en terminos de las variables de
salida que queremos, no de los comandos que necesitamos
escribir.
Renombrar identificadores
svy <- svy |>
step_rename(
hh_id = "id", person_id = "nper",
comment = "Estandarizar identificadores"
)Nada le paso a los datos todavia:
Los nombres de columna originales siguen ahi porque el step esta pendiente. Sigamos agregando steps.
Recodificar sexo
En STATA esto seria una secuencia gen + replace + replace (3
comandos). Con step_recode es un unico mapeo declarativo
que produce etiquetas legibles:
svy <- svy |>
step_recode(sex,
e26 == 1 ~ "Hombre",
e26 == 2 ~ "Mujer",
.default = NA_character_,
comment = "Sexo desde e26"
)Grupos de edad
El equivalente en STATA usa cinco lineas replace con
inrange(). Aqui escribimos la misma logica como un unico
recode con condiciones legibles:
svy <- svy |>
step_recode(age_group,
e27 >= 0 & e27 <= 13 ~ "Ninio",
e27 >= 14 & e27 <= 17 ~ "Adolescente",
e27 >= 18 & e27 <= 29 ~ "Joven adulto",
e27 >= 30 & e27 <= 64 ~ "Adulto",
e27 >= 65 ~ "Adulto mayor",
.default = NA_character_,
comment = "Grupos de edad desde e27"
)Relacion con el jefe de hogar
svy <- svy |>
step_recode(relationship,
e30 == 1 ~ "Jefe",
e30 == 2 ~ "Conyuge",
e30 >= 3 & e30 <= 5 ~ "Hijo",
e30 == 6 ~ "Otro familiar",
e30 == 7 ~ "No familiar",
.default = "Desconocido",
comment = "Relacion desde e30"
)Nivel educativo
svy <- svy |>
step_recode(edu_level,
e51_2 == 0 ~ "Sin instruccion",
e51_2 >= 1 & e51_2 <= 2 ~ "Primaria",
e51_2 >= 3 & e51_2 <= 4 ~ "Secundaria",
e51_2 >= 5 & e51_2 <= 6 ~ "Terciaria",
.default = NA_character_,
comment = "Nivel educativo desde e51_2"
)Area geografica
svy <- svy |>
step_recode(area,
region_4 == 1 ~ "Montevideo",
region_4 == 2 ~ "Urbano >5k",
region_4 == 3 ~ "Urbano <5k",
region_4 == 4 ~ "Rural",
.default = NA_character_,
comment = "Area geografica desde region_4"
)Notar que todas nuestras variables de salida tienen etiquetas significativas en lugar de codigos numericos. Una receta transpilada mantendria los codigos enteros originales (1, 2, 3…) porque eso es lo que usaba el codigo STATA. Construir desde cero permite elegir la representacion que facilita el analisis.
Remover variables crudas
svy <- svy |>
step_remove(e26, e27, e30, e51_2, region_4,
comment = "Eliminar variables crudas de ECH"
)Inspeccionando el pipeline antes de ejecutar
En este punto tenemos siete steps pendientes. Veamos que se registro:
Esta es una de las ventajas clave de construir desde cero: 7 steps que cada uno hace una cosa clara. Una version transpilada del modulo completo de demografia del IECON tiene 80+ steps porque preserva cada comando STATA intermedio.
El pipeline es un DAG (grafo aciclico dirigido) de transformaciones.
view_graph() lo renderiza como una red interactiva – cada
nodo es un step, y las aristas muestran dependencias de variables:
view_graph(svy)Con solo 7 nodos el grafo es limpio y navegable. Comparar con una receta transpilada donde el DAG puede tener 100+ nodos – aun util para auditoria, pero mucho mas dificil de leer de un vistazo.
Para salida estatica podemos inspeccionar la lista de steps:
for (s in get_steps(svy)) {
cat(sprintf("[%s] %s\n", s$type, s$comment %||% ""))
}
#> [step_rename] Estandarizar identificadores
#> [recode] Sexo desde e26
#> [recode] Grupos de edad desde e27
#> [recode] Relacion desde e30
#> [recode] Nivel educativo desde e51_2
#> [recode] Area geografica desde region_4
#> [step_remove] Eliminar variables crudas de ECHEmpaquetando como receta (antes de bake)
Una receta empaqueta los steps con metadatos para que cualquiera pueda reproducir el mismo pipeline con datos diferentes. Creamos la receta antes de hacer bake – los steps lazy son el pipeline:
rec <- steps_to_recipe(
name = "ECH Demografia (minima)",
user = "equipo_investigacion",
svy = svy,
steps = get_steps(svy),
description = paste(
"Demografia armonizada: sexo, grupo de edad, relacion,",
"nivel educativo y area geografica."
),
topic = "demographics"
)
rec
#>
#> ── Recipe: ECH Demografia (minima) ──
#> Author: equipo_investigacion
#> Survey: ech / 2023
#> Version: 1.0.0
#> Topic: demographics
#> Description: Demografia armonizada: sexo, grupo de edad, relacion, nivel educativo y area geografica.
#> Certification: community
#>
#> ── Requires (5 variables) ──
#> e26, e27, e30, e51_2, region_4
#>
#> ── Pipeline (7 steps) ──
#> 1. [step_rename] -> mapping "Estandarizar identificadores"
#> 2. [recode] -> sex "Sexo desde e26"
#> 3. [recode] -> age_group "Grupos de edad desde e27"
#> 4. [recode] -> relationship "Relacion desde e30"
#> 5. [recode] -> edu_level "Nivel educativo desde e51_2"
#> 6. [recode] -> area "Area geografica desde region_4"
#> 7. [step_remove] -> (no output) "Eliminar variables crudas de ECH"
#>
#> ── Produces (6 variables) ──
#> sex [categorical], age_group [categorical], relationship [categorical], edu_level [categorical], area [categorical], mapping [inherited]La receta auto-genera documentacion a partir de los steps:
doc <- rec$doc()
cat("Variables de entrada: ", paste(doc$input_variables, collapse = ", "), "\n")
#> Variables de entrada: e26, e27, e30, e51_2, region_4
cat("Variables de salida: ", paste(doc$output_variables, collapse = ", "), "\n")
#> Variables de salida: mapping, sex, age_group, relationship, edu_level, area
cat("Steps del pipeline: ", length(doc$pipeline), "\n")
#> Steps del pipeline: 7Bake: materializando el pipeline
Ahora ejecutemos los steps. bake_steps() ejecuta todos
los steps pendientes en orden y devuelve la encuesta transformada:
svy <- bake_steps(svy)Los datos tienen las nuevas columnas con etiquetas legibles:
head(get_data(svy)[, .(
hh_id, person_id, sex, age_group, relationship,
edu_level, area
)])
#> hh_id person_id sex age_group relationship edu_level area
#> <int> <int> <char> <char> <char> <char> <char>
#> 1: 1 1 Mujer Adulto mayor No familiar Sin instruccion Urbano <5k
#> 2: 1 2 Mujer Adulto Jefe Primaria Montevideo
#> 3: 1 3 Hombre Joven adulto Conyuge Terciaria Urbano <5k
#> 4: 1 4 Mujer Adulto Hijo Primaria Rural
#> 5: 2 1 Hombre Adulto mayor Hijo Sin instruccion Urbano >5k
#> 6: 2 2 Hombre Joven adulto Conyuge Terciaria RuralLas variables crudas ya no estan:
Guardar y cargar
Las recetas se serializan a JSON para control de versiones y compartir:
f <- tempfile(fileext = ".json")
save_recipe(rec, f)
rec2 <- read_recipe(f)
rec2$name
#> [1] "ECH Demografia (minima)"
length(rec2$steps)
#> [1] 7El JSON es legible y diffeable en git:
cat(readLines(f, n = 15), sep = "\n")
#> {
#> "name": "ECH Demografia (minima)",
#> "user": "equipo_investigacion",
#> "survey_type": "ech",
#> "edition": 2023,
#> "description": "Demografia armonizada: sexo, grupo de edad, relacion, nivel educativo y area geografica.",
#> "topic": "demographics",
#> "doi": {},
#> "id": "r_1772020912_663",
#> "version": "1.0.0",
#> "downloads": 0,
#> "categories": [],
#> "certification": {
#> "level": "community",
#> "certified_at": "2026-02-25 12:01:52.876419"Aplicando a una nueva edicion
La misma receta funciona con cualquier edicion. Cargar la receta desde JSON, adjuntarla a nuevos datos y hacer bake:
rec_loaded <- read_recipe(f)
svy_2024 <- survey_empty(type = "ech", edition = "2024") |>
set_data(data.table::data.table(
id = rep(1:30, each = 3),
nper = rep(1:3, 30),
pesoano = runif(90, 50, 300),
e26 = sample(1:2, 90, replace = TRUE),
e27 = sample(0:90, 90, replace = TRUE),
e30 = sample(1:7, 90, replace = TRUE),
e51_2 = sample(c(0:6, -9), 90, replace = TRUE),
region_4 = sample(1:4, 90, replace = TRUE)
)) |>
add_recipe(rec_loaded) |>
bake_recipes()
head(get_data(svy_2024)[, .(hh_id, person_id, sex, age_group, area)])
#> hh_id person_id sex age_group area
#> <int> <int> <char> <char> <char>
#> 1: 1 1 Hombre Adulto mayor Rural
#> 2: 1 2 Hombre Adulto Urbano <5k
#> 3: 1 3 Mujer Joven adulto Urbano >5k
#> 4: 2 1 Hombre Adulto Montevideo
#> 5: 2 2 Mujer Adulto mayor Rural
#> 6: 2 3 Hombre Adulto mayor Urbano <5kNo se necesitan cambios en el codigo. La receta codifica la logica, no los datos.
Transpilador vs artesanal: cuando usar cada uno
Transpilador (transpile_stata()) |
Receta artesanal | |
|---|---|---|
| Velocidad | Segundos – migracion instantanea | Horas – requiere entender la logica |
| Steps | 80-200 por modulo (uno por linea STATA) | 5-20 (uno por concepto) |
| Nombres de variables | Hereda nombres STATA (bc_pe2, bc_pe3) |
Nombres propios (sex, age_group) |
| Etiquetas | Codigos numericos (1, 2,
3) |
Etiquetas legibles ("Hombre",
"Mujer") |
| Legibilidad | Fiel al original, verboso | Limpio, auto-documentado |
| Mantenimiento | Dificil modificar steps individuales | Facil cambiar cualquier mapeo |
| Visualizacion DAG | Grande, dificil de leer | Compacto, nodos significativos |
| Mejor para | Migrar codigo legacy rapido | Proyectos nuevos, pipelines criticos |
Flujo de trabajo recomendado: usar
transpile_stata() para migrar los archivos .do
existentes inmediatamente para tener un baseline funcional. Luego
reemplazar gradualmente las recetas transpiladas con artesanales a
medida que se revisa cada modulo. La version transpilada mantiene todo
funcionando; la version artesanal es donde se quiere llegar.
Lo que metasurvey ofrece
| Scripts STATA manuales | Receta metasurvey |
|---|---|
Copiar-pegar archivos .do por anio |
Una receta, cualquier edicion |
| Nombres de variables sin documentar | Documentacion auto-generada de entrada/salida |
| Sin seguimiento de dependencias | Visualizacion DAG con view_graph()
|
| Scripts planos, sin validacion |
validate() verifica variables requeridas |
Enviar archivos .do por email |
publish_recipe() a registro compartido |
| Re-ejecutar todo el script para probar | Steps lazy: inspeccionar antes de bake |
Proximos pasos
-
Agregar mas variables: ingresos, condicion de
actividad, condiciones de vivienda – cada una puede ser una receta
separada con
depends_on_recipes -
Certificar la receta: usar
certify_recipe()para marcarla como revisada u oficial -
Publicar:
publish_recipe(rec)sube al registro compartido donde otros pueden encontrarla consearch_recipes(topic = "demographics") -
Partir desde STATA: si ya se tienen archivos
.do, usartranspile_stata()para generar un baseline funcional inmediatamente – vervignette("stata-transpiler-es")– y luego refinar la salida en una receta artesanal como la de esta vignette