IntencionVoto / app.py
CursoIDSA's picture
Update app.py
666daea verified
### IMPORTACIONES
import numpy as np
import pandas as pd
import random
import io
import math
import os
import gradio as gr
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import base64
import sys
from sklearn.model_selection import KFold
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score
from sklearn.preprocessing import StandardScaler, OneHotEncoder, OrdinalEncoder, LabelEncoder
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
import random
import warnings
import joblib
from joblib import dump, load
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
from sklearn.metrics import accuracy_score
import json
# Ignorar warnings
warnings.filterwarnings('ignore')
try:
if sys.platform == "linux" and "multiprocessing" in sys.modules:
multiprocessing.set_start_method('spawn', force=True)
except Exception as e:
pass
IMAGEN_PORTADA_PATH = "ImagenPortada.jpg"
IMAGEN_ENCABEZADO_PATH = "ImagenEncabezado.jpg"
# Para cargar imágenes en String Base64
def cargar_imagen_base64(path):
try:
with open(path, "rb") as f:
encoded_image = base64.b64encode(f.read()).decode("utf-8")
return f"data:image/jpeg;base64,{encoded_image}", ""
except FileNotFoundError:
error_msg = f"ERROR: No se encontró el archivo en la ruta: {path}. Usando fallback."
return "none", error_msg
IMAGEN_PORTADA = cargar_imagen_base64(IMAGEN_PORTADA_PATH)[0]
IMAGEN_ENCABEZADO = cargar_imagen_base64(IMAGEN_ENCABEZADO_PATH)[0]
### SE CARGA EL MODELO RANDOM FOREST CON LOS HIPERPARÁMETROS OPTIMIZADOS
### SE CARGA EL MISMO PREPROCESADOR APLICADO ORIGINALMENTE A LOS DATOS
COLUMNAS_ORDINALES = ['Educacion']
COLUMNAS_NOMINALES = ['Residencia', 'Genero']
COLUMNAS_NUMERICAS = ['Edad', 'Ingreso']
COLUMNA_TARGET = 'Partido' # Necesario para la decodificación de la predicción
# Archivos guardados
MODELO_ARCHIVO = 'modelo_rf_optimizado_deap.pkl'
PREPROCESADOR_ARCHIVO = 'preprocesador_votacion.pkl'
# Cargar los objetos entrenados
try:
modelo_cargado = load(MODELO_ARCHIVO)
preprocesador_cargado = load(PREPROCESADOR_ARCHIVO)
# print(f"Modelo y Preprocesador cargados.")
# 🚨 DEBUG CRUCIAL: Verificar el estado interno
from sklearn.utils.validation import check_is_fitted
try:
check_is_fitted(preprocesador_cargado)
# print("✅ EL PREPROCESADOR ESTÁ CONSIDERADO 'FITTED' INTERNAMENTE.")
except Exception as e:
# Si esta línea se ejecuta, el objeto guardado ESTÁ CORRUPTO o es la causa del error.
print(f"❌ ADVERTENCIA: La verificación de check_is_fitted falló después de la carga: {e}")
# Recuperar el LabelEncoder para decodificar la predicción
# Como el label Encoder no se guardó antes, hay que reentrenarlo con las etiquetas originales,
# entonces se cargan las etiquetas originales para entrenarlo nuevamente.
try:
df_votacion = pd.read_csv('votacion.csv', encoding='utf-8')
le = LabelEncoder()
le.fit(df_votacion[COLUMNA_TARGET]) # Para las etiquetas de los partidos políticos
except Exception as e:
print(f"ERROR al cargar datos o LabelEncoder: {e}. La predicción será un número.")
le = None # Si falla, se devuelve el número
except FileNotFoundError as e:
print(f"ERROR: Archivo no encontrado. Asegúrate de que '{e.filename}' exista.")
# Terminar la ejecución si los archivos esenciales faltan
### FUNCIONES PARA GRADIO
def intencion_voto(edad, genero, ingreso, educacion, residencia):
# DataFrame con los nuevos datos para la predicción
# (Con los mismos nombres de columnas y orden del entrenamiento)
X_nuevo_df = pd.DataFrame({
'Edad': [edad],
'Ingreso': [ingreso],
'Educacion': [educacion],
'Residencia': [residencia],
'Genero': [genero]
})
# Transformar los datos con el objeto preprocesador entrenado
# Se usa .transform(), no .fit_transform()!!
X_nuevo_procesado = preprocesador_cargado.transform(X_nuevo_df)
# Realizar la predicción
# La función .predict() espera datos con la misma forma y transformación.
prediccion_encoded = modelo_cargado.predict(X_nuevo_procesado)
print(prediccion_encoded)
# 4. Decodificar la predicción numérica (0, 1, 2, etc.) a la etiqueta original del partido
if le is not None:
prediccion_partido = le.inverse_transform(prediccion_encoded)
print(prediccion_partido)
return prediccion_partido[0] # Retorna la etiqueta del partido
else:
return f"Predicción numérica (sin decodificar): {prediccion_encoded[0]}"
### DEFINIR LOS ESTILOS PARA LA INTERFAZ GRADIO
# Usamos las variables IMAGEN_PORTADA e IMAGEN_ENCABEZADO que contienen el Base64
css_style = f"""
/* ESTILO PARA LA TARJETA DE PORTADA*/
.votos-background-card {{
background-image: url('{IMAGEN_PORTADA}');
background-size: cover;
background-position: center;
background-blend-mode: multiply;
background-color: rgba(255, 255, 255, 0.2);
border-radius: 15px;
box-shadow: 8px 8px 10px rgba(0, 0, 0, 0.7);
padding: 30px;
min-height: 500px;
}}
/* ESTILO PARA LA TARJETA DE ENCABEZADO */
.votos-header-card {{
background-image: url('{IMAGEN_ENCABEZADO}');
background-size: cover;
border-radius: 15px;
box-shadow: 8px 8px 10px rgba(0, 0, 0, 0.7);
padding: 30px;
height: 50px;
}}
/* ESTILO PARA EL TÍTULO PRINCIPAL */
.title * {{
font-size: 40px !important;
text-shadow: 5px 5px 5px rgba(0, 0, 0, 1);
line-height: 1.3;
color: white !important;
}}
/* ESTILO PARA EL SUBTÍTULO */
.subtitle * {{
font-size: 20px !important;
font-weight: 400;
margin-top: 10px;
line-height: 1.3;
text-shadow: 3px 3px 3px rgba(0, 0, 0, 1);
color: white !important;
}}
/* ESTILO PARA TÍTULO DE PANEL */
.panel_title * {{
font-size: 20px !important;
font-weight: 400;
margin-top: 10px;
line-height: 1.3;
text-shadow: 3px 3px 3px rgba(0, 0, 0, 1);
color: white !important;
}}
/* ESTILO DE RECTÁNGULO NARANJA SEMITRANSPARENTE */
.info-box {{
background-color: rgba(255, 165, 0, 0.6) !important;
border-radius: 15px !important;
/* border: 2px solid rgba(200, 130, 0, 0.8) !important; */
box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.5) !important;
padding: 20px !important;
color: white !important;
}}
"""
### INTERFAZ GRADIO
with gr.Blocks(css=css_style) as application:
gr.Row(elem_classes=["votos-header-card"])
with gr.Tab("Inicio"):
with gr.Column(elem_classes=["votos-background-card"]):
gr.Markdown("ESTIMACIÓN DE <br>LA INTENCIÓN DE VOTO",
elem_classes=["title"])
with gr.Row(elem_classes=["info-box"]):
gr.Markdown("<br><br><br><br>Esta aplicación se basa en el Método Random Forest<br>"
"aplicado a un dataset de votantes de <br>"
"las últimas elecciones de medio término<br>"
"para estimar la intención de voto en los próximos comicios<br>"
"considerando sus características (edad, género, ingreso,<br>"
"nivel educativo y lugar de residencia).<br>"
"Los hiperparámetros del modelo se optimizaron aplicando<br>"
"un algoritmo genético.",
elem_classes=["subtitle"])
with gr.Tab("Datos"):
with gr.Row():
gr.Markdown("CONJUNTO SINTÉTICO DE DATOS SOBRE VOTACIÓN OCTUBRE 2025", elem_classes=["panel_title"])
with gr.Row():
data_table = gr.Dataframe(value=df_votacion,
headers=list(df_votacion.columns),
datatype=["number", "number", "str", "str", "str", "str"],
row_count=len(df_votacion),
col_count=len(df_votacion.columns),
# label="VOTANTES ELECCIONES NACIONALES OCTUBRE 2025",
interactive=False,
wrap=True)
with gr.Tab("Intención de Voto"):
with gr.Row():
gr.Markdown("CARACTERÍSTICAS DEL CIUDADANO", elem_classes=["panel_title"])
with gr.Row():
with gr.Column(scale=1):
input_edad = gr.Slider(minimum=18, maximum=85, step=1, value=18, label="Edad", interactive=True)
input_genero = gr.Radio(choices=["Femenino", "Masculino"], label="Género", value="Femenino", interactive=True)
input_ingresos = gr.Slider(minimum=300000, maximum=5000000, step=25000, value=700000, label="Ingresos", interactive=True)
with gr.Column(scale=1):
input_educacion = gr.Dropdown(choices=["Primario", "Secundario", "Universitario", "Postgrado"],
value="Primario", label="Nivel Educativo", interactive=True)
input_residencia = gr.Dropdown(choices=["Urbana Alta", "Urbana Media", "Urbana Marginal",
"Rural Media", "Rural profunda"], value="Urbana Media", label="Zona de Residencia", interactive=True)
run_button = gr.Button("Ejecutar Simulación", interactive=True)
with gr.Row():
output_voto = gr.Textbox(label="Intención de Voto", interactive=False, lines=1)
run_button.click(
intencion_voto,
inputs=[input_edad, input_genero, input_ingresos, input_educacion, input_residencia],
outputs=[output_voto]
)
if __name__ == "__main__":
application.launch()