Spaces:
Runtime error
Runtime error
| import streamlit as st | |
| import pandas as pd | |
| import numpy as np | |
| import pickle | |
| from pathlib import Path | |
| import os | |
| import boto3 | |
| import statsmodels.api as sm | |
| import plotly.express as px | |
| from dotenv import load_dotenv | |
| from io import BytesIO | |
| # Nettoyage du cache au démarrage | |
| st.cache_data.clear() | |
| st.cache_resource.clear() | |
| # Configuration page pleine largeur | |
| st.set_page_config( | |
| page_title="Simulation Geodechets", | |
| page_icon="♻️", | |
| layout="wide" | |
| ) | |
| # Titre de l'app | |
| st.title("Application Geodechets") | |
| st.write("### Simulation de production de déchets par département") | |
| # Chargement des secrets AWS | |
| load_dotenv("secrets.env") | |
| AWS_KEY = os.getenv("AWS_ACCESS_KEY_ID") | |
| AWS_SECRET = os.getenv("AWS_SECRET_ACCESS_KEY") | |
| # Connexion S3 | |
| s3 = boto3.client( | |
| 's3', | |
| aws_access_key_id=AWS_KEY, | |
| aws_secret_access_key=AWS_SECRET, | |
| region_name="eu-west-3" | |
| ) | |
| # Fonction pour charger CSV depuis S3 | |
| def load_data_from_s3(bucket_name, key): | |
| obj = s3.get_object(Bucket=bucket_name, Key=key) | |
| df = pd.read_csv(BytesIO(obj['Body'].read())) | |
| return df | |
| # Fonction pour charger Pickle depuis S3 | |
| def load_pickle_from_s3(bucket_name, key): | |
| obj = s3.get_object(Bucket=bucket_name, Key=key) | |
| return pickle.load(BytesIO(obj['Body'].read())) | |
| # Chargement des données CSV | |
| try: | |
| df = load_data_from_s3("mygeodechet", "df_reduced.csv") | |
| except Exception as e: | |
| st.error(f"Erreur lors du chargement des données : {e}") | |
| df = None | |
| # Chargement des modèles Pickle | |
| model_files = { | |
| "Déblais et gravats": "models/model_ols_Déblais_gravats.pkl", | |
| "Déchets verts": "models/model_ols_Déchets_verts.pkl", | |
| "Encombrants": "models/model_ols_Encombrants.pkl", | |
| "Matériaux recyclables": "models/model_ols_Matériaux_recyclables.pkl", | |
| "Total autres déchets": "models/model_ols_Total_autres_dechets.pkl" | |
| } | |
| models = {} | |
| for label, path in model_files.items(): | |
| try: | |
| models[label] = load_pickle_from_s3("mygeodechet", path) | |
| # st.success(f"✅ Modèle '{label}' chargé depuis S3 !") | |
| except Exception as e: | |
| st.error(f"Erreur lors du chargement du modèle '{label}' : {e}") | |
| models[label] = None | |
| # Sélection du département | |
| if df is not None: | |
| departement = st.selectbox("Choix du département :", sorted(df["Département"].unique())) | |
| df_dep = df[df["Département"] == departement].copy() | |
| st.dataframe(df_dep.head(7), use_container_width=True) | |
| # ------------------------------------------------------------------------------------------------- | |
| # Ajustement des paramètres sur 4 colonnes, avant simultaion, pour l'année 2021 | |
| # selection année 2021 | |
| df_2021 = df_dep[df_dep["année"] == 2021] | |
| # liste des variables à slider | |
| numeric_cols = [ | |
| 'pop_globale', 'densité', | |
| 'tranche_age_0-24', 'tranche_age_25-59', 'tranche_age_60+', | |
| 'csp1_agriculteurs', 'csp2_artisans_commerçant_chef_entreprises', | |
| 'csp3_cadres_professions_intellectuelles', 'csp4_professions_intermédiaires', | |
| 'csp5_employés', 'csp6_ouvriers', 'csp7_retraités', 'csp8_sans_activité', | |
| 'nbre_entreprises', 'nbre_entreprises_agricole', 'nbre_entreprises_industrie', | |
| 'nbre_entreprises_service', 'nb_salaries_secteur_agricole', | |
| 'nb_salaries_secteur_industrie', 'nb_salaries_secteur_service' | |
| ] | |
| # titre | |
| st.write("### Paramètres à ajuster") | |
| col1, col2, col3, col4 = st.columns(4) | |
| cols = [col1, col2, col3, col4] | |
| user_inputs = {} | |
| for i, feature in enumerate(numeric_cols): | |
| min_val = float(df_dep[feature].min())*0.5 # 50% de moins | |
| max_val = float(df_dep[feature].max()) *1.5 # 50% de plus | |
| mean_val = float(df_dep[feature].mean()) | |
| target_col = cols[i % 4] # distribue à travers les 4 colonnes | |
| with target_col: | |
| # slider avec step raisonnable | |
| step = max((max_val - min_val) / 50.0, 1.0) | |
| user_inputs[feature] = st.slider(feature, min_val, max_val, float(mean_val), step=step, key=f"sl_{feature}") | |
| # ------------------------------------------------------------------------------------------------------- | |
| # Partie simulation | |
| st.markdown("---") | |
| # st.markdown("### Lancez la simulation") | |
| if st.button("Lancer la simulation"): | |
| # Préparer X_user | |
| X_user = pd.DataFrame([user_inputs]) | |
| # Nettoyage / conversion : remplace virgules (si jamais), force numérique et fillna | |
| X_user = X_user.apply(lambda col: pd.to_numeric(col.astype(str).str.replace(",", "."), errors="coerce")) | |
| X_user = X_user.fillna(0.0) | |
| results = [] | |
| for label, pipeline in models.items(): | |
| if pipeline is None: | |
| st.error(f"Modèle manquant pour {label}") | |
| results.append({"Type de déchet": label, "Prédiction (tonnes)": "Erreur"}) | |
| continue | |
| try: | |
| preprocessor = pipeline["preprocessor"] | |
| ols_model = pipeline["ols_model"] | |
| # Ajouter toutes les colonnes attendues par le preprocessor avec 0.0 si manquantes | |
| for feat in preprocessor.feature_names_in_: | |
| if feat not in X_user.columns: | |
| X_user[feat] = 0.0 | |
| # Réordonner et forcer float uniquement pour les colonnes du preprocessor | |
| X_sim = X_user[preprocessor.feature_names_in_].astype(float) | |
| # Transformation + constante | |
| X_transformed = preprocessor.transform(X_sim) | |
| X_const = sm.add_constant(X_transformed, has_constant='add') | |
| # Prédiction | |
| pred = ols_model.predict(X_const)[0] | |
| results.append({"Type de déchet": label, "Prédiction (tonnes)": round(float(pred), 2)}) | |
| except Exception as e: | |
| st.error(f"Erreur modèle {label}: {e}") | |
| results.append({"Type de déchet": label, "Prédiction (tonnes)": "Erreur"}) | |
| # ----------------------------------------------------------------------------------------------------------------- | |
| # 2éme Partie | |
| # Affichage résultats en deux colonnes (résultats / graph) | |
| res_col, graph_col = st.columns([1, 2]) | |
| with res_col: | |
| df_results = pd.DataFrame(results) | |
| st.write("#### Résultats Prédictions") | |
| st.dataframe(df_results, use_container_width=True) | |
| with graph_col: | |
| st.write("#### Analyse comparative des déchets : 2021 (réel) vs modèle prédictif") | |
| # Filtrer uniquement les résultats numériques | |
| good = df_results[df_results["Prédiction (tonnes)"].apply(lambda v: isinstance(v, (int, float)))].copy() | |
| if not good.empty: | |
| # Créer un DataFrame avec les valeurs 2021 | |
| df_2021_vals = pd.DataFrame({ | |
| 'Type de déchet': ['Déblais et gravats', 'Déchets verts', 'Encombrants', | |
| 'Matériaux recyclables', 'Total autres déchets'], | |
| '2021': [ | |
| float(df_2021['Déblais_gravats'].values[0]), | |
| float(df_2021['Déchets_verts'].values[0]), | |
| float(df_2021['Encombrants'].values[0]), | |
| float(df_2021['Matériaux_recyclables'].values[0]), | |
| float(df_2021['Total_autres_dechets'].values[0]) | |
| ] | |
| }) | |
| # Fusionner avec les prédictions | |
| df_plot = pd.merge(good, df_2021_vals, on='Type de déchet') # Le merge sert à mettre ces données côte à côte pour le même type de déchet. | |
| df_plot = df_plot.melt(id_vars='Type de déchet', value_vars=['2021', 'Prédiction (tonnes)'], | |
| var_name='Année', value_name='Tonnes') # attend un format “long” pour faire des barres côte à côte par catégorie. | |
| # Graphique barres côte à côte | |
| fig = px.bar(df_plot, x='Type de déchet', y='Tonnes', color='Année', barmode='group', height=300) | |
| st.plotly_chart(fig, use_container_width=True) | |
| else: | |
| st.info("Aucun résultat numérique à afficher") |