ENAHOPY 🇵🇪
Librería Python para análisis de microdatos ENAHO del INEI (Perú)
Herramienta completa y robusta para descargar, procesar, fusionar y analizar microdatos de la Encuesta Nacional de Hogares (ENAHO). Diseñada específicamente para investigadores, analistas y profesionales que trabajan con datos sociales y económicos del Perú.
✨ Características Principales
- 📥 Descarga Automática: Descarga directa desde servidores oficiales del INEI
- 📊 Multi-formato: Compatible con DTA (Stata), SAV (SPSS), CSV, Parquet
- ✅ Validación Inteligente: Validación automática de columnas y mapeo de variables
- 🔗 Fusión de Módulos: Sistema avanzado para combinar módulos ENAHO (hogar, personas, ingresos)
- 🗺️ Análisis Geográfico: Integración con datos UBIGEO (departamento/provincia/distrito)
- 🕳️ Análisis de Valores Nulos: Detección de patrones missing y estrategias de imputación
- ⚡ Sistema de Cache: Optimización automática de descargas repetidas
- 🚀 Alto Rendimiento: Procesamiento eficiente con bajo uso de memoria
- 📈 Visualizaciones: Gráficos especializados para datos de encuestas sociales
📦 Instalación
Instalación básica
pip install enahopy
Instalación con todas las características
pip install enahopy[all]
🚀 Inicio Rápido
1. Descargar datos ENAHO
from enahopy.loader import ENAHODataDownloader
# Inicializar downloader
downloader = ENAHODataDownloader(verbose=True)
# Descargar múltiples módulos en paralelo
data_multi = downloader.download(
modules=['01', '02', '05', '34'], # Hogar, Persona, Empleo, Sumaria
years=['2024'],
output_dir='./data',
decompress=True,
load_dta=True,
parallel=True,
max_workers=3
)
# Extraer datasets
df_hogar = data_multi[('2024', '01')]['enaho01-2024-100']
df_persona = data_multi[('2024', '02')]['enaho01-2024-200']
df_empleo = data_multi[('2024', '05')]['enaho01a-2024-500']
df_sumaria = data_multi[('2024', '34')]['sumaria-2024']
print(f"✓ Hogares: {len(df_hogar):,} registros")
print(f"✓ Personas: {len(df_persona):,} registros")
2. Fusionar módulos
from enahopy.merger import ENAHOModuleMerger
from enahopy.merger.config import ModuleMergeConfig, ModuleMergeLevel
# Configurar merger para nivel persona
config = ModuleMergeConfig(merge_level=ModuleMergeLevel.PERSONA)
merger = ENAHOModuleMerger(config)
# Fusionar módulos individuales
modules_dict = {
'02': df_persona,
'05': df_empleo
}
result = merger.merge_multiple_modules(
modules_dict=modules_dict,
base_module='02',
merge_config=config
)
df_merged = result.merged_df
print(f"✓ Fusionado: {df_merged.shape}")
3. Análisis geográfico
from enahopy.merger.geographic.merger import GeographicMerger
import pandas as pd
# Cargar tabla UBIGEO
df_ubigeo = pd.read_excel('UBIGEO_2024.xlsx')
df_ubigeo = df_ubigeo[['IDDIST', 'NOMBDEP', 'NOMBPROV', 'NOMBDIST']]
df_ubigeo = df_ubigeo.rename(columns={'IDDIST': 'ubigeo'})
# Fusionar con datos geográficos
merger_geo = GeographicMerger()
df_geo, report = merger_geo.merge(df_sumaria, df_ubigeo, columna_union='ubigeo')
print(f"✓ Registros con geografía: {report['output_rows']}")
4. Análisis de valores nulos
from enahopy.null_analysis import ENAHONullAnalyzer
# Análisis completo de valores faltantes
analyzer = ENAHONullAnalyzer(complexity='advanced')
result = analyzer.analyze(df_merged)
# Generar reporte HTML
analyzer.export_report(result, 'reporte_nulos.html')
💡 Ejemplos Prácticos
🏆 Análisis Completo de Pobreza Monetaria y Laboral (Avanzado)
Este ejemplo muestra un pipeline completo de análisis fusionando 6 módulos ENAHO para estudiar la relación entre pobreza monetaria y características laborales.
Archivos del ejemplo:
- 📓
examples/investigacion/analisis_pob_mon_lab.ipynb- Notebook interactivo con análisis completo - 🐍
examples/investigacion/analisis_pob_mon_lab.py- Módulo Python con funciones de ingeniería de características
📝 Nota: El módulo
.pycontiene toda la lógica de procesamiento, cálculo de variables derivadas, y transformaciones. El notebook lo importa para mantener el código organizado y reutilizable.
Funciones principales del módulo analisis_pob_mon_lab.py:
pipeline_completo(): Ejecuta todo el flujo de procesamiento end-to-endcrear_caracteristicas_individuales(): Calcula variables derivadas (informalidad, seguro de salud, grupos etarios, etc.)extraer_jefe_hogar_completo(): Extrae características del jefe de hogar (educación, ocupación, sector económico)agregar_a_nivel_hogar(): Agrega datos individuales a nivel hogarcalcular_variables_objetivo(): Calcula pobreza monetaria y laboralanalisis_descriptivo_ponderado(): Análisis con pesos muestrales (factor de expansión)
from enahopy.loader import ENAHODataDownloader
from enahopy.merger import ENAHOModuleMerger
from enahopy.merger.config import ModuleMergeConfig, ModuleMergeLevel
import pandas as pd
# Importar funciones del módulo auxiliar
from analisis_pob_mon_lab import pipeline_completo, analisis_descriptivo_ponderado
# ==================================================
# PASO 1: Descargar múltiples módulos en paralelo
# ==================================================
downloader = ENAHODataDownloader(verbose=True)
# Descargar 6 módulos simultáneamente
data_multi = downloader.download(
modules=['01', '02', '03', '04', '05', '34'], # Hogar, Persona, Educación, Salud, Empleo, Sumaria
years=['2024'],
output_dir='./data',
parallel=True,
max_workers=3,
load_dta=True
)
# Extraer datasets individuales
df_hogar = data_multi[('2024', '01')]['enaho01-2024-100']
df_persona = data_multi[('2024', '02')]['enaho01-2024-200']
df_educacion = data_multi[('2024', '03')]['enaho01a-2024-300']
df_salud = data_multi[('2024', '04')]['enaho01a-2024-400']
df_empleo = data_multi[('2024', '05')]['enaho01a-2024-500']
df_sumaria = data_multi[('2024', '34')]['sumaria-2024']
print(f"✓ Datasets descargados:")
print(f" • Hogares: {df_hogar.shape[0]:,} registros")
print(f" • Personas: {df_persona.shape[0]:,} registros")
# ==================================================
# PASO 2: Fusionar módulos a nivel PERSONA
# ==================================================
# Configurar merger para nivel persona
config_persona = ModuleMergeConfig(merge_level=ModuleMergeLevel.PERSONA)
merger_persona = ENAHOModuleMerger(config_persona)
# Fusionar módulos individuales (persona + educación + salud + empleo)
modules_dict = {
'02': df_persona,
'03': df_educacion,
'04': df_salud,
'05': df_empleo
}
merge_result = merger_persona.merge_multiple_modules(
modules_dict=modules_dict,
base_module='02',
merge_config=config_persona
)
df_individuos = merge_result.merged_df
print(f"✓ Módulos individuales fusionados: {df_individuos.shape}")
# ==================================================
# PASO 3: Fusionar módulos a nivel HOGAR
# ==================================================
config_hogar = ModuleMergeConfig(merge_level=ModuleMergeLevel.HOGAR)
merger_hogar = ENAHOModuleMerger(config_hogar)
# Fusionar sumaria + características de hogar
merge_hogar = merger_hogar.merge_modules(
left_df=df_sumaria,
right_df=df_hogar,
left_module='34',
right_module='01',
merge_config=config_hogar
)
df_hogares = merge_hogar.merged_df
print(f"✓ Módulos de hogar fusionados: {df_hogares.shape}")
# ==================================================
# PASO 4: Análisis de pobreza monetaria y laboral
# ==================================================
# Calcular informalidad laboral (ocupados sin contrato ni beneficios)
df_individuos['es_informal'] = (
(df_individuos['ocu500'] == 1) & # Ocupado
(df_individuos['p507'].isin([7, 8, 9])) # Sin contrato
).astype(int)
# Agregar a nivel hogar
df_hogar_stats = df_individuos.groupby(['conglome', 'vivienda', 'hogar']).agg({
'codperso': 'count', # Personas en el hogar
'es_informal': 'sum' # Informales en el hogar
}).rename(columns={'codperso': 'n_personas', 'es_informal': 'n_informales'})
# Fusionar con datos de hogar
df_final = df_hogares.merge(df_hogar_stats, on=['conglome', 'vivienda', 'hogar'])
# Calcular tasa de informalidad por hogar
df_final['tasa_informalidad'] = df_final['n_informales'] / df_final['n_personas']
# Clasificar pobreza monetaria (usando línea de pobreza INEI)
df_final['es_pobre_monetario'] = (df_final['pobreza'] <= 2).astype(int) # 1=pobre extremo, 2=pobre
# Clasificar pobreza laboral (alta informalidad + bajos ingresos)
df_final['pobreza_laboral'] = (
(df_final['tasa_informalidad'] >= 0.5) &
(df_final['inghog2d'] < df_final['inghog2d'].quantile(0.4))
).astype(int)
# ==================================================
# PASO 5: Análisis descriptivo
# ==================================================
# Resumen general
print(f"\n{'='*60}")
print(f"RESULTADOS DEL ANÁLISIS")
print(f"{'='*60}")
print(f"Total de hogares analizados: {len(df_final):,}")
print(f"\nPobreza Monetaria:")
print(f" • Hogares pobres: {df_final['es_pobre_monetario'].sum():,} ({df_final['es_pobre_monetario'].mean()*100:.1f}%)")
print(f"\nPobreza Laboral:")
print(f" • Hogares con pobreza laboral: {df_final['pobreza_laboral'].sum():,} ({df_final['pobreza_laboral'].mean()*100:.1f}%)")
print(f"\nInformalidad:")
print(f" • Tasa promedio de informalidad: {df_final['tasa_informalidad'].mean()*100:.1f}%")
# Análisis por dominio geográfico
analisis_geografico = df_final.groupby('dominio').agg({
'es_pobre_monetario': 'mean',
'pobreza_laboral': 'mean',
'tasa_informalidad': 'mean',
'inghog2d': 'median'
}).round(3) * 100
print(f"\n{'='*60}")
print("Indicadores por Dominio Geográfico (%)")
print(f"{'='*60}")
print(analisis_geografico)
# Exportar dataset final
df_final.to_csv('analisis_pobreza_completo_2024.csv', index=False)
print(f"\n✓ Dataset final guardado: {df_final.shape[0]:,} hogares × {df_final.shape[1]} variables")
Output esperado:
✓ Datasets descargados:
• Hogares: 33,691 registros
• Personas: 117,755 registros
✓ Módulos individuales fusionados: (117755, 45)
✓ Módulos de hogar fusionados: (33691, 28)
============================================================
RESULTADOS DEL ANÁLISIS
============================================================
Total de hogares analizados: 33,691
Pobreza Monetaria:
• Hogares pobres: 6,812 (20.2%)
Pobreza Laboral:
• Hogares con pobreza laboral: 16,352 (48.5%)
Informalidad:
• Tasa promedio de informalidad: 72.9%
✓ Dataset final guardado: 33,691 hogares × 68 variables
Código completo del ejemplo: Ver examples/investigacion/analisis_pob_mon_lab.ipynb y examples/investigacion/analisis_pob_mon_lab.py
🗺️ Merge Geográfico con UBIGEO
Ejemplo simple de cómo agregar información geográfica (departamento, provincia, distrito) usando la tabla UBIGEO oficial del INEI.
Archivo: examples/investigacion/merger_enahopy_geografico.py
from enahopy.merger.geographic.merger import GeographicMerger
import pandas as pd
# Cargar datos finales del análisis
df = pd.read_csv('dataframe_final_2024.csv')
# Cargar tabla UBIGEO oficial (1,891 distritos)
df_ubigeo = pd.read_excel('UBIGEO_2022_1891_distritos.xlsx')
df_ubigeo = df_ubigeo[['IDDIST', 'NOMBDEP', 'NOMBPROV', 'NOMBDIST']]
df_ubigeo = df_ubigeo.rename(columns={'IDDIST': 'ubigeo'})
df_ubigeo = df_ubigeo.dropna()
# Fusionar con GeographicMerger
merger = GeographicMerger()
df_geo, report = merger.merge(df, df_ubigeo, columna_union='ubigeo')
print(f"✓ Registros con geografía: {report['output_rows']}")
print(f"✓ Columnas agregadas: NOMBDEP, NOMBPROV, NOMBDIST")
# Guardar resultado
df_geo.to_csv('dataframe_final_completo_geografico_2024.csv', index=False)
Output:
✓ Registros con geografía: 33,691
✓ Columnas agregadas: NOMBDEP, NOMBPROV, NOMBDIST
🏗️ Arquitectura del Paquete
enahopy/
├── loader/ # Descarga y lectura de datos ENAHO
│ ├── core/ # Configuración y excepciones
│ ├── io/ # Readers (DTA, SAV, CSV, Parquet) y downloaders
│ └── utils/ # Utilidades y funciones auxiliares
├── merger/ # Fusión de módulos y datos geográficos
│ ├── geographic/ # Manejo de UBIGEO y validación geográfica
│ ├── modules/ # Fusión entre módulos ENAHO (01, 02, 05, 34, sumaria)
│ └── strategies/ # Estrategias de fusión (hogar, persona, panel)
└── null_analysis/ # Análisis de valores faltantes
├── core/ # Motor de análisis y clasificación
├── patterns/ # Detección de patrones (MCAR, MAR, MNAR)
├── strategies/ # Estrategias de imputación (media, KNN, ML)
└── reports/ # Generación de reportes y visualizaciones
📚 Módulos ENAHO Soportados
| Módulo | Descripción | Nivel |
|---|---|---|
01 |
Características de la vivienda y del hogar | Hogar |
02 |
Características de los miembros del hogar | Persona |
03 |
Educación | Persona |
04 |
Salud | Persona |
05 |
Empleo e ingresos | Persona |
34 |
Programas sociales | Hogar |
37 |
Gastos del hogar | Hogar |
sumaria |
Indicadores agregados (gasto, ingreso, pobreza) | Hogar |
🔧 Configuración Avanzada
Cache y Performance
from enahopy.loader import ENAHOConfig, ENAHODataDownloader
# Configuración con cache habilitado
config = ENAHOConfig(
cache_dir='.enaho_cache', # Directorio de cache
enable_cache=True, # Habilitar sistema de cache
max_workers=4, # Workers para descarga paralela
chunk_size=50000, # Tamaño de chunks para lectura
enable_validation=True # Validar columnas al cargar
)
downloader = ENAHODataDownloader(config=config)
# Primera descarga: ~30 segundos (descarga desde INEI)
# Segunda descarga: <1 segundo (lee desde cache local)
df = downloader.download_module(year=2023, module='sumaria', format='dta')
Validación Estricta en Mergers
from enahopy.merger import MergerConfig, ENAHOMerger
# Configuración con validación estricta
config = MergerConfig(
validate_merge=True, # Validar antes de fusionar
strict_mode=True, # Modo estricto (falla si hay errores)
allow_duplicates=False, # No permitir duplicados
validate_ubigeo=True # Validar códigos UBIGEO
)
merger = ENAHOMerger(config=config)
📊 Ejemplos y Tutoriales
El repositorio incluye scripts de demostración completos:
01_complete_poverty_analysis.py- Análisis end-to-end de pobreza monetaria02_geographic_inequality_analysis.py- Desigualdad territorial con UBIGEO03_multimodule_analysis.py- Fusión avanzada de múltiples módulos04_advanced_ml_imputation_demo.py- Imputación con Machine Learning
También hay Jupyter notebooks en examples/:
tutorial_01_loader.ipynb- Descarga y lectura básicatutorial_02_merger.ipynb- Fusión de módulostutorial_03_null_analysis.ipynb- Análisis de valores nulos
🤝 Contribuir
¡Las contribuciones son bienvenidas! Ver CONTRIBUTING.md para detalles completos.
Setup de desarrollo:
# Clonar repositorio
git clone https://github.com/elpapx/enahopy.git
cd enahopy
# Instalar en modo desarrollo con dependencias
pip install -e .[dev]
# Instalar pre-commit hooks
pre-commit install
# Ejecutar tests
pytest tests/ -v
# Verificar estilo de código
black enahopy/ tests/
flake8 enahopy/
isort enahopy/ tests/
# Generar reporte de cobertura
pytest tests/ --cov=enahopy --cov-report=html
Estado del CI/CD
Todos los PRs son validados automáticamente:
- ✅ Quality Checks: black, flake8, isort
- ✅ Multi-platform Tests: Ubuntu, Windows, macOS
- ✅ Python Matrix: 3.8, 3.9, 3.10, 3.11, 3.12
- ✅ Coverage: Cobertura mínima 40%
- ✅ Build Validation: Empaquetado PyPI
📈 Roadmap
Próximas características:
- Soporte para ENDES (Encuesta Demográfica y de Salud Familiar)
- Integración con ENAPRES (Encuesta Nacional de Programas Presupuestales)
- Dashboard interactivo con Streamlit
- Exportación a formatos R (RData, feather)
- Análisis longitudinal (paneles multi-año)
- API REST para servicios web
📄 Licencia
Este proyecto está bajo la Licencia MIT - ver LICENSE para detalles.
📞 Soporte
- 📧 Email: pcamacho447@gmail.com
- 🐛 Issues: GitHub Issues
- 💬 Discusiones: GitHub Discussions
🙏 Agradecimientos
- INEI (Instituto Nacional de Estadística e Informática) por la disponibilización de microdatos públicos
- Comunidad de investigadores y analistas de datos sociales en Perú
- Todos los contribuidores y usuarios del proyecto
Desarrollado con ❤️ para la comunidad de investigación social y económica en Perú
Metadata
Release files for enahopy 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| enahopy-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Release files / enahopy-1.0.0-py3-none-any.whl
| Download URL | enahopy-1.0.0-py3-none-any.whl |
|---|---|
| Size | 367.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
75e8f109efe0196a7b1a95ee9c9cc59af311bb9aae975ee4f9d5f2608dd2bbd0
|
|
BLAKE2b-256 checksum How to use checksums |
daba0297ddf93a232aa8369c2538137ff5e96dbe7e3282ba9f5608136c625ba7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.9
|