1. Introducción

El presente trabajo tiene como objetivo analizar la variación de ingresos de los hogares paraguayos según datos recabados de la Encuesta Permanente de Hogares Continua (EPHC) publicada por el Instituto Nacional de Estadística (INE).

2. Datos

2.1 Fuente de datos

Los datos provienen del portal de microdatos del INE:

  • INGREFAM_EPHC_ANUAL_2022.csv

  • INGREFAM_EPHC_ANUAL_2023.csv

  • INGREFAM_EPHC_ANUAL_2024.csv

  • REG01_EPHC_ANUAL_2022.csv

  • REG01_EPHC_ANUAL_2023.csv

  • REG01_EPHC_ANUAL_2024.csv

https://www.ine.gov.py/microdato/encuesta-permanente-de-hogares-continua-ephc-2022-2023-2024-anual

2.2 Variables principales

  • Demográficas: Departamento, Sexo y edad del jefe/a de hogar, Número de personas

  • Socioeconómicas: Nivel educativo, Tipo de ocupación, Ingreso total del hogar

3. Metodología

  1. Consolidación de datos de 3 años

  2. Análisis exploratorio

  3. Modelado predictivo

  4. Evaluación de modelos

4. Procesamiento

4.1 Carga y unificación

import pandas as pd
import numpy as np

df_INGREFAM_2022 = pd.read_csv("INGREFAM_EPHC_ANUAL_2022.csv", sep=";", decimal=",")
df_INGREFAM_2023 = pd.read_csv("INGREFAM_EPHC_ANUAL_2023.csv", sep=";", decimal=",")
df_INGREFAM_2024 = pd.read_csv("INGREFAM_EPHC_ANUAL_2024.csv", sep=";", decimal=",")

df_INGREFAM_completo = pd.concat([df_INGREFAM_2022, df_INGREFAM_2023, df_INGREFAM_2024], ignore_index=True)

df_INGREFAM_completo.columns = df_INGREFAM_completo.columns.str.strip()

df_REG01_2022 = pd.read_csv("REG01_EPHC_ANUAL_2022.csv", sep=";", decimal=",")
df_REG01_2023 = pd.read_csv("REG01_EPHC_ANUAL_2023.csv", sep=";", decimal=",")
df_REG01_2024 = pd.read_csv("REG01_EPHC_ANUAL_2024.csv", sep=";", decimal=",")

df_REG01_completo = pd.concat([df_REG01_2022, df_REG01_2023, df_REG01_2024], ignore_index=True)

df_REG01_completo.columns = df_REG01_completo.columns.str.strip()

df_REG01_completo = df_REG01_completo.rename(columns={
    "UPM": "upm",
    "NVIVI": "nvivi",
    "NHOGA": "nhoga",
    "DPTO": "dpto",
    "AREA": "area",
    "AÑO": "año"  
})

# Hacer el left join
df_INGREFAM_REG01 = pd.merge(
    df_INGREFAM_completo,
    df_REG01_completo,
    on=["upm", "nvivi", "nhoga", "dpto", "area", "año"],
    how="left"
)

columnas_deseadas = [
    "upm", "nvivi", "nhoga", "dpto", "area", "año", "ingrem", "ipcm",
    "THOGAV", "POBREZAI", "POBNOPOI", "HOMBRES", "MUJERES", "TOTAL", "V01"
    ]

df_final = df_INGREFAM_REG01[columnas_deseadas]

print(df_final.head())
##    upm  nvivi  nhoga  dpto  area  ...  POBNOPOI  HOMBRES  MUJERES  TOTAL  V01
## 0   14     10      1     0     1  ...         0        0        2      2    1
## 1   14     20      1     0     1  ...         0        2        0      2    1
## 2   14     22      1     0     1  ...         0        0        5      5    1
## 3   14     24      1     0     1  ...         0        6        4     10    1
## 4   14     25      1     0     1  ...         0        1        0      1    3
## 
## [5 rows x 15 columns]

4.2 Limpieza y transformación

Se limpian aquellos registros que no contienen datos en columnas clave

df_limpio = df_final.copy()
columnas_clave = ["ingrem", "ipcm", "THOGAV", "POBREZAI", "POBNOPOI"]
df_limpio = df_limpio.dropna(subset=columnas_clave)

df_limpio = df_limpio[
    (df_limpio["TOTAL"] > 0) & (df_limpio["TOTAL"] <= 20) &
    (df_limpio["ingrem"] > 0) & (df_limpio["ingrem"] < 200000000) &
    (df_limpio["ipcm"] > 0) & (df_limpio["ipcm"] < 100000000)
    ]

categorias_validas = {
    "THOGAV": [1, 2, 3, 4, 5],
    "POBREZAI": [1, 2, 3],
    "POBNOPOI": [0, 1],
    }

for col, valores_validos in categorias_validas.items():
    df_limpio = df_limpio[df_limpio[col].isin(valores_validos)]

print(df_limpio.describe(include='all'))
##                 upm         nvivi  ...         TOTAL           V01
## count  52216.000000  52216.000000  ...  52216.000000  52216.000000
## mean   14688.359871     30.851252  ...      3.399935      1.065765
## std     8618.891981     22.843031  ...      1.798800      0.325323
## min       14.000000      1.000000  ...      1.000000      1.000000
## 25%     7236.000000     14.000000  ...      2.000000      1.000000
## 50%    14118.000000     28.000000  ...      3.000000      1.000000
## 75%    22420.000000     44.000000  ...      4.000000      1.000000
## max    28774.000000    975.000000  ...     19.000000      9.000000
## 
## [8 rows x 15 columns]

4.3 Análisis exploratorio

Agregamos el nombre de los departamentos, áreas y graficamos los datos para explorar posibles patrones

import matplotlib.pyplot as plt
import seaborn as sns
from matplotlib.ticker import FuncFormatter

mapa_dptos = {
    0: "Asunción",
    1: "Concepción",
    2: "San Pedro",
    3: "Cordillera",
    4: "Guairá",
    5: "Caaguazú",
    6: "Caazapá",
    7: "Itapúa",
    8: "Misiones",
    9: "Paraguarí",
    10: "Alto Paraná",
    11: "Central",
    12: "Ñeembucú",
    13: "Amambay",
    14: "Canindeyú",
    15: "Presidente Hayes"
}
df_limpio["dpto_nombre"] = df_limpio["dpto"].map(mapa_dptos)

mapa_area = {
    1: "Urbana",
    6: "Rural"
}
df_limpio["area_nombre"] = df_limpio["area"].map(mapa_area)

# Obtener ingreso promedio por departamento

df_barras_dpto = df_limpio.groupby("dpto_nombre")["ingrem"].mean().sort_values()

colores = sns.color_palette("Set3", n_colors=len(df_barras_dpto))  

plt.figure(figsize=(12, 6))
df_barras_dpto.plot(kind="bar", color=colores)

formatter = FuncFormatter(lambda x, _: f'{x / 1_000_000:.1f}M')
plt.gca().yaxis.set_major_formatter(formatter)

plt.title("Ingreso Familiar Promedio por Departamento")
plt.ylabel("Ingreso mensual promedio (millones de Gs.)")
plt.xlabel("Departamento")
plt.xticks(rotation=45, ha="right")
## (array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15]), [Text(0, 0, 'Caazapá'), Text(1, 0, 'Guairá'), Text(2, 0, 'Paraguarí'), Text(3, 0, 'San Pedro'), Text(4, 0, 'Itapúa'), Text(5, 0, 'Concepción'), Text(6, 0, 'Ñeembucú'), Text(7, 0, 'Caaguazú'), Text(8, 0, 'Cordillera'), Text(9, 0, 'Misiones'), Text(10, 0, 'Canindeyú'), Text(11, 0, 'Amambay'), Text(12, 0, 'Presidente Hayes'), Text(13, 0, 'Alto Paraná'), Text(14, 0, 'Central'), Text(15, 0, 'Asunción')])
plt.tight_layout()
plt.show()

# Obtener ingreso promedio por área

df_barras = df_limpio.groupby(["dpto_nombre", "area_nombre"])["ingrem"].mean().reset_index()
plt.figure(figsize=(14, 6))
sns.barplot(
    data=df_barras,
    x="dpto_nombre",
    y="ingrem",
    hue="area_nombre",
    palette={"Urbana": "hotpink", "Rural": "mediumseagreen"}
)

formatter = FuncFormatter(lambda x, _: f'{x / 1_000_000:.1f}M')
plt.gca().yaxis.set_major_formatter(formatter)

plt.title("Ingreso Familiar Promedio por Departamento y Área")
plt.xlabel("Departamento")
plt.ylabel("Ingreso mensual promedio (millones Gs.)")
plt.xticks(rotation=45, ha="right")
## ([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], [Text(0, 0, 'Alto Paraná'), Text(1, 0, 'Amambay'), Text(2, 0, 'Asunción'), Text(3, 0, 'Caaguazú'), Text(4, 0, 'Caazapá'), Text(5, 0, 'Canindeyú'), Text(6, 0, 'Central'), Text(7, 0, 'Concepción'), Text(8, 0, 'Cordillera'), Text(9, 0, 'Guairá'), Text(10, 0, 'Itapúa'), Text(11, 0, 'Misiones'), Text(12, 0, 'Paraguarí'), Text(13, 0, 'Presidente Hayes'), Text(14, 0, 'San Pedro'), Text(15, 0, 'Ñeembucú')])
plt.legend(title="Área")
plt.tight_layout()
plt.show()

5. Modelado predictivo

##5.1 Preparación y entrenamiento

Se busco predecir el ingreso de las personas en base a las demas columnas tenidas en cuenta en el modelo:

from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error, r2_score

variables_num = ["HOMBRES", "MUJERES", "TOTAL", "ingrem"]
variables_cat = ["THOGAV", "POBREZAI"]
df_modelo = df_limpio[variables_num + variables_cat + ["ipcm"]].copy()
df_modelo = pd.get_dummies(df_modelo, columns=variables_cat, drop_first=True)
X = df_modelo.drop(columns=["ipcm"])
y = df_modelo["ipcm"]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
modelo = LinearRegression()
modelo.fit(X_train, y_train)
LinearRegression()
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
y_pred = modelo.predict(X_test)

print("R² Score:", r2_score(y_test, y_pred))
## R² Score: 0.735363245857246
print("MSE:", mean_squared_error(y_test, y_pred))
## MSE: 1872099301367.1987
# Coeficientes con nombres
coeficientes = pd.Series(modelo.coef_, index=X.columns)
print("\nCoeficientes del modelo:")
## 
## Coeficientes del modelo:
print(coeficientes.sort_values(ascending=False))
## ingrem        3.398407e-01
## HOMBRES      -1.609876e+05
## MUJERES      -1.729578e+05
## TOTAL        -3.339454e+05
## POBREZAI_2   -3.786836e+05
## POBREZAI_3   -6.830092e+05
## THOGAV_5     -8.343074e+05
## THOGAV_3     -9.973729e+05
## THOGAV_2     -1.052131e+06
## THOGAV_4     -1.068728e+06
## dtype: float64

5.2 Evaluación

Se utilizaron gráficos para evaluar la proyección y los factores que influyen a los ingresos de las personas:

# Gráfico 1: Real vs Predicho
formatter_millones = FuncFormatter(lambda x, _: f'{x / 1_000_000:.1f}M')
a = modelo.coef_[0]  # Asumiendo que es el coef de la primera variable, puede que necesites ajustar
b = modelo.intercept_
y_test_array = y_test.values.reshape(-1, 1)
y_pred_array = y_pred.reshape(-1, 1)
ajuste_lineal = LinearRegression().fit(y_test_array, y_pred_array)
pendiente = ajuste_lineal.coef_[0][0]
intercepto = ajuste_lineal.intercept_[0]

formula_texto = f"y = {pendiente:.2f}x + {intercepto:.2f}"

plt.figure(figsize=(10, 6))
sns.scatterplot(x=y_test, y=y_pred, alpha=0.5, color='gray')
plt.plot([y_test.min(), y_test.max()], [y_test.min(), y_test.max()], '--r', label='Ideal: y = x')
x_vals = np.linspace(y_test.min(), y_test.max(), 100)
y_vals = pendiente * x_vals + intercepto
plt.plot(x_vals, y_vals, 'b-', label=formula_texto)
plt.gca().xaxis.set_major_formatter(formatter_millones)
plt.gca().yaxis.set_major_formatter(formatter_millones)

plt.xlabel("Ingreso por persona real (ipcm)")
plt.ylabel("Ingreso por persona predicho")
plt.title("Predicción del ingreso por persona: real vs predicho")
plt.legend()
plt.tight_layout()
plt.savefig("prediccion_vs_real_formula.png", dpi=300)
plt.show()

# Gráfico 2: Importancia de coeficientes (valor absoluto)
# Traducción para nombres
traduccion_coef = {
    "POBREZAI_2": "Pobre no extremo",
    "POBREZAI_3": "No pobre",
    "THOGAV_2": "Familia Nuclear completo",
    "THOGAV_3": "Familia Nuclear incompleto",
    "THOGAV_4": "Familia Extendido",
    "THOGAV_5": "Familia Compuesto",
    "HOMBRES": "Cantidad de hombres",
    "MUJERES": "Cantidad de mujeres",
    "TOTAL": "Total personas hogar",
    "ingrem": "Ingreso familiar total"
}
coef_abs = coeficientes.abs()
coef_abs.index = [traduccion_coef.get(i, i) for i in coef_abs.index]

plt.figure(figsize=(10, 6))
coef_abs.sort_values().plot(kind='barh', color='mediumorchid')
plt.xlabel("Valor absoluto del coeficiente")
plt.title("Importancia relativa de variables (regresión lineal)")
plt.tight_layout()
plt.savefig("importancia_variables.png", dpi=300)
plt.show()

# Cantidad de hombres y mujeres por categoría de pobreza

df_pobreza_filtrado = df_limpio[df_limpio["POBREZAI"].isin([2, 3])]
df_pobreza_sum = df_pobreza_filtrado.groupby("POBREZAI")[["HOMBRES", "MUJERES"]].sum()

# Etiquetas
etiquetas = {
    2: "Pobre no extremo",
    3: "No pobre"
}
df_pobreza_sum.index = df_pobreza_sum.index.map(etiquetas)

ind = np.arange(len(df_pobreza_sum))
width = 0.35

fig, ax = plt.subplots(figsize=(8, 6))
ax.bar(ind - width/2, df_pobreza_sum["HOMBRES"], width, label='Hombres', color='steelblue')
ax.bar(ind + width/2, df_pobreza_sum["MUJERES"], width, label='Mujeres', color='hotpink')

ax.set_xlabel("Categoría Pobreza")
ax.set_ylabel("Cantidad total de personas")
ax.set_title("Cantidad de Hombres y Mujeres por Categoría de Pobreza")
ax.set_xticks(ind)
ax.set_xticklabels(df_pobreza_sum.index)
ax.legend()

plt.tight_layout()
plt.savefig("hombres_mujeres_por_pobreza.png", dpi=300)
plt.show()

6. Conclusiones

  1. Los mayores ingresos promedios se dan en la zona de Asuncion y Central.

  2. Hay brechas significativas en el ingreso promedio por persona segmentando por región, sexo y diferente composicion familiar.

  3. Hay una brecha importante respecto a los varones respecto a las mujeres que se encuentran en pobreza no extrema respecto a los varones.

7. Referencias

  1. Instituto Nacional de Estadística: https://www.ine.gov.py/

  2. Scikit-learn: https://scikit-learn.org/

8. Anexo

El codigo completo se encuentra en el siguiente repositorio: https://github.com/JuanSeSanabria/CursoBigDataSBK