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).
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
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
Consolidación de datos de 3 años
Análisis exploratorio
Modelado predictivo
Evaluación de modelos
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]
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]
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.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.
| fit_intercept | True | |
| copy_X | True | |
| tol | 1e-06 | |
| n_jobs | None | |
| positive | False |
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
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()
Los mayores ingresos promedios se dan en la zona de Asuncion y Central.
Hay brechas significativas en el ingreso promedio por persona segmentando por región, sexo y diferente composicion familiar.
Hay una brecha importante respecto a los varones respecto a las mujeres que se encuentran en pobreza no extrema respecto a los varones.
Instituto Nacional de Estadística: https://www.ine.gov.py/
Scikit-learn: https://scikit-learn.org/
El codigo completo se encuentra en el siguiente repositorio: https://github.com/JuanSeSanabria/CursoBigDataSBK