Spaces:
Sleeping
Sleeping
| ### 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() | |