Spaces:
Sleeping
Sleeping
| #-------------------------------------------------------- Imports nécessaires --------------------------------------------------- | |
| import pandas as pd | |
| import plotly.express as px | |
| import matplotlib.pyplot as plt | |
| import plotly.io as pio | |
| import sklearn | |
| import warnings | |
| import sksurv.datasets | |
| import numpy as np | |
| import joblib | |
| import pickle | |
| import streamlit as st | |
| import os | |
| import io | |
| import urllib.request | |
| import json | |
| import matplotlib | |
| import plotly.graph_objects as go | |
| import xgboost as xgb | |
| import warnings | |
| import folium | |
| import boto3 | |
| import psycopg2 | |
| import datetime | |
| # import mlflow | |
| from typing import Optional | |
| from lifelines import CoxPHFitter | |
| from itertools import product | |
| from tqdm import tqdm | |
| from xgboost import XGBRegressor, XGBClassifier, DMatrix, train as xgb_train | |
| from xgbse import XGBSEKaplanNeighbors | |
| from xgbse.converters import convert_to_structured | |
| from sklearn.preprocessing import StandardScaler | |
| from sklearn.impute import SimpleImputer | |
| from sklearn.pipeline import Pipeline, make_pipeline | |
| from sklearn.model_selection import train_test_split,GridSearchCV, KFold, ParameterGrid | |
| from sksurv.ensemble import GradientBoostingSurvivalAnalysis | |
| from sklearn.exceptions import UndefinedMetricWarning | |
| from sklearn.cluster import DBSCAN | |
| from sklearn import set_config | |
| from sksurv.datasets import load_breast_cancer | |
| from sksurv.metrics import concordance_index_censored | |
| from sksurv.linear_model import CoxnetSurvivalAnalysis, CoxPHSurvivalAnalysis | |
| from sksurv.util import Surv | |
| from sksurv.preprocessing import OneHotEncoder | |
| from branca.element import Template, MacroElement | |
| from folium import DivIcon | |
| from streamlit_folium import st_folium | |
| from sqlalchemy import create_engine, text | |
| warnings.filterwarnings("ignore", category=UndefinedMetricWarning) | |
| set_config(display="text") | |
| #_________________________________________________# Configuration de la page_______________________________________________________ | |
| st.set_page_config(page_title="Projet Incendies", layout="wide") | |
| #_________________________________________________# Sidebar de navigation_________________________________________________ | |
| st.sidebar.title("Navigation") | |
| page = st.sidebar.radio("Aller à", [ | |
| "Accueil", | |
| "Notre Projet", | |
| "Exploration des données", | |
| "Résultats des modèles", | |
| ]) | |
| #________________________________________________________# Footer#_____________________________________________________________ | |
| def show_footer(): | |
| st.markdown("---") | |
| st.markdown("Projet réalisé dans le cadre de la LEAD. © 2025") | |
| #_________________________________________________# Chargement DATASET (modèle)#_______________________________________________ | |
| # mlflow.set_tracking_uri(os.getenv('https://gdleds-mlflow-fire2.hf.space')) | |
| os.environ['AWS_ACCESS_KEY_ID'] = os.getenv('AWS_ACCESS_KEY_ID') | |
| os.environ['AWS_SECRET_ACCESS_KEY'] = os.getenv('AWS_SECRET_ACCESS_KEY') | |
| os.environ['MLFLOW_DEFAULT_ARTIFACT_ROOT'] = os.getenv('MLFLOW_DEFAULT_ARTIFACT_ROOT') | |
| os.environ['S3_BUCKET'] = os.getenv('S3_BUCKET') | |
| s3 = boto3.client('s3') | |
| db_user = os.getenv("DB_USER") | |
| db_password = os.getenv("DB_PASS") | |
| db_host = os.getenv("DB_HOST") | |
| db_name = os.getenv("DB_NAME") | |
| engine = create_engine(f"postgresql+psycopg2://{db_user}:{db_password}@{db_host}/{db_name}") | |
| def load_model_data(): | |
| yesterday = (datetime.date.today() - datetime.timedelta(days=1)) | |
| # query = """SELECT * FROM data_prediction WHERE date BETWEEN '2024-01-01' AND '{yesterday}'""" | |
| # query = f"""SELECT * FROM data_prediction WHERE date = '{yesterday}'""" | |
| query = """SELECT * FROM data_prediction WHERE date >= CURRENT_DATE - INTERVAL '180 days'""" | |
| # s3.download_file(os.getenv('S3_Bucket'),'compile/predictions_feu_2025.csv', 'prediction_feu_2025.csv') | |
| # url = "https://projet-incendie.s3.eu-west-3.amazonaws.com/dataset_modele_decompte.csv" | |
| try: | |
| df = pd.read_sql(query, engine) | |
| df=pd.DataFrame(df) | |
| for col in df.columns: | |
| if "date" in col.lower(): | |
| df[col] = pd.to_datetime(df[col], errors="coerce", dayfirst=True) | |
| return df | |
| except Exception as e: | |
| st.error(f"❌ Erreur lors du chargement des données : {e}") | |
| return pd.DataFrame() | |
| #_________________________________________________# Chargement des données d'incendies et de coordonnées#_______________________________________ | |
| def load_data(): | |
| s3.download_file(os.getenv('S3_BUCKET'),'dataset/Incendies_2006_2024 (1).csv', 'incendies_2006_2024.csv') | |
| # url_incendies = 'https://projet-incendie.s3.eu-west-3.amazonaws.com/Incendies_2006_2024.csv' | |
| return pd.read_csv('incendies_2006_2024.csv', sep=';', low_memory=False) | |
| def load_coords(): | |
| s3.download_file(os.getenv('S3_BUCKET'), 'meteo-corse/coordonnees_villes (2).csv', 'coordonnees_villes.csv') | |
| # url_coords = 'https://projet-incendie.s3.eu-west-3.amazonaws.com/coordonnees_villes.csv' | |
| return pd.read_csv('coordonnees_villes.csv', sep=',', encoding='utf-8') | |
| def load_df_merge(): | |
| s3.download_file(os.getenv('S3_BUCKET'), 'dataset/historique_incendies_avec_coordonnees.csv', 'historique_incendies_avec_coordonnees.csv') | |
| # url = 'https://projet-incendie.s3.eu-west-3.amazonaws.com/historique_incendies_avec_coordonnees.csv' | |
| return pd.read_csv('historique_incendies_avec_coordonnees.csv', sep=';', encoding='utf-8') | |
| #------------------------------------------------------- ----------------Notre produit#_________________________________________________ | |
| warnings.filterwarnings("ignore", category=UndefinedMetricWarning) | |
| set_config(display="text") | |
| # ──────────────────────────────────────────────── | |
| # 1) FONCTION DE CHARGEMENT DU CSV BRUT | |
| # ──────────────────────────────────────────────── | |
| def load_raw_data() -> pd.DataFrame: | |
| # url = ( | |
| # "https://projet-incendie.s3.eu-west-3.amazonaws.com/" | |
| # "dataset_modele_decompte.csv" | |
| # ) | |
| s3.download_file(os.getenv('S3_BUCKET'), 'compile/predictions_feu_2025.csv', 'predictions_feu_2025.csv') | |
| return pd.read_csv('predictions_feu_2025.csv', sep=";") | |
| def get_latest_model_key(): | |
| try: | |
| response = s3.list_objects_v2( | |
| Bucket=os.getenv('S3_BUCKET'), | |
| Prefix='mlflow/models/' | |
| ) | |
| if "Contents" not in response: | |
| raise ValueError("Aucun modèle trouvé dans le bucket S3.") | |
| # Filtrer uniquement les .joblib | |
| models = [ | |
| obj for obj in response["Contents"] | |
| if obj["Key"].endswith(".joblib") | |
| ] | |
| if not models: | |
| raise ValueError("Aucun fichier .joblib trouvé.") | |
| # Trier par LastModified (date d'upload dans S3) | |
| models.sort(key=lambda x: x["LastModified"], reverse=True) | |
| latest_key = models[0]["Key"] | |
| print(f"Dernier modèle détecté : {latest_key}") | |
| return latest_key | |
| except Exception as e: | |
| raise RuntimeError(f"Erreur récupération modèle S3 : {e}") | |
| def load_model_from_s3(key: Optional[str] = None): | |
| bucket = os.getenv('S3_BUCKET') | |
| if key is None: | |
| key = get_latest_model_key() | |
| response = s3.get_object(Bucket=bucket, Key=key) | |
| buffer = io.BytesIO(response["Body"].read()) | |
| model = joblib.load(buffer) | |
| return model | |
| def load_baseline_S3(): | |
| bucket = os.getenv('S3_BUCKET') | |
| BASELINE_KEY = "mlflow/models/baseline_c87008d290ec435eb93cc302e41ce934.pkl" | |
| response = s3.get_object(Bucket=bucket, Key=BASELINE_KEY) | |
| buffer = io.BytesIO(response["Body"].read()) | |
| baseline = pickle.load(buffer) | |
| return baseline["baseline_survival"], baseline["baseline_hazard"] | |
| # ──────────────────────────────────────────────── | |
| # 2) FONCTION D’ENTRAÎNEMENT + PRÉDICTIONS | |
| # ──────────────────────────────────────────────── | |
| # @st.cache_data(show_spinner="⚙️ Entraînement du modèle…", ttl=None) | |
| def predict(df_raw: pd.DataFrame, model, baseline_survival) -> pd.DataFrame: | |
| """Retourne df_map prêt pour la carte avec les colonnes | |
| proba_7j, proba_30j, …, proba_180j.""" | |
| # a) Nettoyage | |
| df = df_raw.copy() | |
| df = df.rename(columns={"feu_prévu": "event", "décompte": "duration"}) | |
| df["event"] = df["event"].astype(bool) | |
| df["duration"] = df["duration"].fillna(0) | |
| # b) Features | |
| features = [ | |
| "rr","um","tn","tx","jours_sans_pluie","jours_tx_sup_30", | |
| "etpgrille_7j","compteur_jours_vers_prochain_feu", "moyenne_temperature_mois","moyenne_precipitations_mois","moyenne_vitesse_vent_mois","compteur_feu_log" | |
| ] | |
| features = [f.lower() for f in features] | |
| features = [f for f in features if f in df.columns] | |
| log_hr = model.predict(df[features]) | |
| HR = np.exp(log_hr) | |
| horizons = {7: "proba_7j", 30: "proba_30j", 60: "proba_60j", | |
| 90: "proba_90j", 180: "proba_180j"} | |
| S0 = baseline_survival.squeeze() | |
| def S0_at(t): | |
| return np.interp(t, S0.index.values, S0.values) | |
| for t, col in horizons.items(): | |
| S_t = (S0_at(t)) ** HR # survival | |
| df[col] = 1 - S_t # prob event | |
| df_map = df[["lat", "lon", "nom"] + list(horizons.values())].copy() | |
| # df_map = df[["latitude_feu", "longitude_feu", "ville"] + list(horizons.values())].copy() | |
| return df_map | |
| # ──────────────────────────────────────────────── | |
| # 3) AFFICHAGE SUR LA PAGE « Accueil » | |
| # ──────────────────────────────────────────────── | |
| if page == "Accueil": | |
| st.title("Carte du risque d’incendie en Corse") | |
| df_raw = load_model_data() | |
| latest_model_key = get_latest_model_key() | |
| model = load_model_from_s3(latest_model_key) | |
| baseline_survival, baseline_hazard = load_baseline_S3() | |
| df_map = predict(df_raw, model, baseline_survival) | |
| horizons_lbl = { | |
| "7 jours": "proba_7j", | |
| "30 jours": "proba_30j", | |
| "60 jours": "proba_60j", | |
| "90 jours": "proba_90j", | |
| "180 jours": "proba_180j", | |
| } | |
| choix = st.radio( | |
| "Choisis l’horizon temporel :", | |
| list(horizons_lbl.keys()), | |
| horizontal=True, | |
| index=0, | |
| ) | |
| col_proba = horizons_lbl[choix] | |
| # Palette dynamique | |
| vmax = float(df_map[col_proba].max()) | |
| fig = px.scatter_map( | |
| df_map, | |
| lat="lat", | |
| lon="lon", | |
| hover_name="nom", | |
| hover_data={col_proba: ":.2%"}, | |
| color=col_proba, | |
| color_continuous_scale="YlOrRd", # jaune → orange → rouge | |
| range_color=(0.0, 1), | |
| zoom=7, | |
| height=650, | |
| ) | |
| fig.update_layout( | |
| map_style="open-street-map", | |
| margin=dict(l=0, r=0, t=0, b=0), | |
| coloraxis_colorbar=dict(title="Probabilité", tickformat=".0%"), | |
| ) | |
| st.subheader(f"Risque d’incendie – horizon **{choix}**") | |
| st.plotly_chart(fig, use_container_width=True) | |
| # ---------------------------------------------------------------Carte des casernes de pompiers#______________________________________________ | |
| if page == "Accueil": | |
| s3.download_file(os.getenv('S3_BUCKET'), 'dataset/casernes_corses.csv', 'casernes_corses.csv') | |
| df_casernes = pd.read_csv('casernes_corses.csv', sep=',', encoding='utf8') | |
| # Nettoyage des coordonnées | |
| df_casernes['latitude'] = df_casernes['latitude'].astype(str).str.replace(',', '.').astype(float) | |
| df_casernes['longitude'] = df_casernes['longitude'].astype(str).str.replace(',', '.').astype(float) | |
| df_casernes = df_casernes.dropna(subset=['latitude', 'longitude']) | |
| # Catégorisation des casernes | |
| df_casernes['categorie'] = np.select( | |
| [ | |
| df_casernes['nom'].str.contains('centre', case=False, na=False), | |
| df_casernes['nom'].str.contains('base', case=False, na=False), | |
| df_casernes['nom'].str.contains('SSLIA', case=False, na=False), | |
| df_casernes['nom'].str.contains('citerne', case=False, na=False), | |
| df_casernes['nom'].str.contains('borne', case=False, na=False), | |
| ], | |
| [ | |
| "Centre d'incendie et de secours", | |
| 'Base forestière', | |
| 'SSLIA (aérodromes)', | |
| 'Citerne', | |
| 'Borne incendie' | |
| ], | |
| default='Autre' | |
| ) | |
| # Dictionnaire d'emojis | |
| emoji_legende = { | |
| "Centre d'incendie et de secours": "🚒", | |
| "Base forestière": "🌲", | |
| "SSLIA (aérodromes)": "✈️", | |
| "Citerne": "💦" | |
| } | |
| # Carte centrée sur la Corse | |
| m = folium.Map(location=[42.0396, 9.0129], zoom_start=8) | |
| for _, row in df_casernes.iterrows(): | |
| emoji = emoji_legende.get(row['categorie'], '❓') | |
| folium.Marker( | |
| location=[row['latitude'], row['longitude']], | |
| popup=f"{emoji} {row['nom']}", | |
| icon=DivIcon(html=f"""<div style="font-size:24px">{emoji}</div>""") | |
| ).add_to(m) | |
| # Légende HTML | |
| legend_html = """ | |
| {% macro html(this, kwargs) %} | |
| <div style=" | |
| position: fixed; | |
| bottom: 50px; left: 50px; width: 280px; | |
| background-color: white; | |
| border: 2px solid grey; | |
| z-index: 9999; | |
| font-size: 14px; | |
| color: black; | |
| padding: 10px; | |
| border-radius: 10px; | |
| box-shadow: 2px 2px 6px rgba(0,0,0,0.3); | |
| "> | |
| <b>📘 Légende</b><br> | |
| 🚒 Centre d'incendie et de secours<br> | |
| 🌲 Base forestière<br> | |
| ✈️ SSLIA (aérodromes)<br> | |
| 💦 Citerne<br> | |
| </div> | |
| {% endmacro %} | |
| """ | |
| legend = MacroElement() | |
| legend._template = Template(legend_html) | |
| m.get_root().add_child(legend) | |
| st.subheader("🗺️ Carte des casernes et équipements de lutte contre les incendies") | |
| st_folium(m, width=1000, height=800) | |
| #----------------------------------------------------------------------Page Notre Projet--------------------------------------------------- | |
| if page == "Notre Projet": | |
| st.title("🔥 Projet Analyse des Incendies 🔥") | |
| st.subheader(" 📊 Contexte") | |
| st.subheader("🌲La forêt française en chiffres") | |
| col1, col2 = st.columns([2, 1]) | |
| with col1: | |
| st.markdown(""" | |
| La France est le 4ᵉ pays européen en superficie forestière, avec **17,5 millions d’hectares** en métropole (32 % du territoire) et **8 millions** en Guyane. | |
| Au total, les forêts couvrent environ **41 %** du territoire national. | |
| - **75 %** des forêts sont privées (3,5 millions de propriétaires). | |
| - **16 %** publiques (collectivités). | |
| - **9 %** domaniales (État). | |
| La forêt française est un réservoir de biodiversité : | |
| - **190 espèces d’arbres** (67 % feuillus, 33 % conifères). | |
| - **73 espèces de mammifères**, **120 d’oiseaux**. | |
| - Environ **30 000 espèces** de champignons et autant d’insectes. | |
| - **72 %** de la flore française se trouve en forêt. | |
| Les forêts françaises absorbent environ **9 %** des émissions nationales de gaz à effet de serre, jouant un rôle crucial dans la lutte contre le changement climatique. | |
| Le Code forestier encadre leur gestion durable pour protéger la biodiversité, l’air, l’eau et prévenir les risques naturels. | |
| """) | |
| if page == "Notre Projet": | |
| st.header("🔥 Corse : Bilan Campagne Feux de Forêts 2024") | |
| # Tabs par grande section | |
| tab1, tab2, tab3, tab4, tab5, tab6 = st.tabs([ | |
| "📌 Contexte", "🛠️ Prévention", "🚒 Moyens", "📊 Statistiques", | |
| "🔍 Causes", "🔎 Enquêtes" | |
| ]) | |
| with tab1: | |
| with st.expander("📌 Contexte général"): | |
| st.markdown(""" | |
| - **80 %** de la Corse est couverte de forêts/maquis → **fort risque incendie** | |
| - **2023-2024** : la plus chaude et la plus sèche jamais enregistrée | |
| - **714 mm** de pluie sur l’année (**78 %** de la normale) | |
| - **Façade orientale** : seulement **30 %** des précipitations normales | |
| """) | |
| with tab2: | |
| with st.expander("🛠️ Prévention & Investissements"): | |
| st.markdown(""" | |
| - **1,9 million €** investis en 2023-2024 par l’État (jusqu’à 80 % de financement) | |
| - Travaux financés : | |
| - Pistes DFCI/DECI (Sorio di Tenda, Oletta, Île-Rousse…) | |
| - Citernes souples & points d’eau | |
| - Drones, caméras thermiques, logiciels SIG | |
| - Véhicules pour réserves communales | |
| """) | |
| with tab3: | |
| with st.expander("🚒 Moyens déployés"): | |
| st.markdown(""" | |
| - Jusqu’à **500 personnels mobilisables** | |
| - **168 sapeurs-pompiers SIS2B**, **261 UIISC5**, forestiers-sapeurs, gendarmerie, ONF… | |
| - Moyens aériens : | |
| - **1 hélico**, **2 canadairs** à Ajaccio | |
| - **12 canadairs** + **8 Dashs** nationaux en renfort | |
| """) | |
| with tab4: | |
| with st.expander("📊 Statistiques Feux Été 2024"): | |
| st.markdown(""" | |
| - **107 feux** recensés (~9/semaine) | |
| - **130 ha** brûlés dont : | |
| - 83 % des feux <1 ha : **5,42 ha** | |
| - 4 gros feux >10 ha : **72,84 ha** | |
| - Linguizetta (**22,19 ha**), Oletta (**18,9 ha**), Pioggiola (**18,75 ha**), Tallone (**13 ha**) | |
| - Depuis janvier 2024 : **285 feux** pour **587 ha** | |
| - Feu majeur à Barbaggio : **195 ha** (33 % du total annuel) | |
| """) | |
| with tab5: | |
| with st.expander("🔍 Causes des feux (38 cas identifiés)"): | |
| st.markdown(""" | |
| - **11** : foudre | |
| - **8** : écobuages | |
| - **6** : malveillance | |
| - **5** : accidents | |
| - **4** : mégots de cigarette | |
| """) | |
| with st.expander("⚠️ Prévention = priorité absolue"): | |
| st.markdown(""" | |
| - **90 %** des feux ont une origine humaine | |
| - Causes principales : **imprudences** (mégots, BBQ, travaux, écobuages…) | |
| """) | |
| with tab6: | |
| with st.expander("🔎 Enquêtes & Surveillance"): | |
| st.markdown(""" | |
| - **20 incendies** étudiés par la Cellule Technique d’Investigation (CTIFF) | |
| - Équipes mobilisées : **7 forestiers**, **15 pompiers**, **21 forces de l’ordre** | |
| - **Fermeture de massif** enclenchée 1 seule fois : forêt de Pinia | |
| """) | |
| #---------------------------------------------------Equipe du projet--------------------------------------------------- | |
| # st.subheader("👨💻 Équipe du projet") | |
| # col3= st.columns(1) | |
| # col1, col2, col3 = st.columns(3) | |
| # with col1: | |
| # st.image("images/Faycal_Belambri.jpg", width=150) | |
| # st.markdown("**Fayçal Belambri**\n\nData Scientist\n\nSpécialiste App Streamlit et visualisation") | |
| # with col2: | |
| # st.image("images/Joel_Termondjian.jpg", width=150) | |
| # st.markdown("**Joël Termondjian**\n\nData Scientist\n\nResponsable des données\n\nPreprocessing\n\nData Enagineering") | |
| # with col3: | |
| # st.image("images/Marc_Barthes.jpg", width=150) | |
| # st.markdown("**Marc Barthes**\n\nData Scientist\n\nML Engineer\n\nExpert en modèles de prédiction") | |
| #---------------------------------------------------Notre Objectif -------------------------------------------------------- | |
| st.subheader("🎯 Notre Objectif") | |
| st.markdown(""" | |
| Dans un contexte de **changement climatique** et de **risques accrus d’incendies de forêt**, notre équipe a développé un projet innovant visant à **analyser et prédire les zones à risque d’incendie** en France, avec un focus particulier sur la **Corse**. | |
| """) | |
| #---------------------------------------------------Obectifs du projet--------------------------------------------------- | |
| col1, col2 = st.columns([1, 1]) | |
| with col1: | |
| st.subheader("🔍 Exploration des données") | |
| st.markdown(""" | |
| - ✅ **Évolution du nombre d’incendies**, répartition par mois et par causes. | |
| - ✅ **Cartographie interactive** des incendies sur tout le territoire. | |
| - ✅ **Analyse des clusters** grâce à DBSCAN pour identifier les zones les plus à risque. | |
| """) | |
| with col2: | |
| st.subheader("📈 Modèles prédictifs") | |
| st.markdown(""" | |
| - ✅ **Comparaison des modèles** : Random Forest, XGBoost, analyse de survie. | |
| - ✅ **Prédiction des zones à risque** avec visualisation sur carte. | |
| - ✅ Fourniture d'un **outil décisionnel** pour les autorités et les services de gestion des risques. | |
| """) | |
| st.subheader("📘 Définition de l'analyse de survie (Survival Analysis") | |
| col1, col2, col3, col4 = st.columns(4) | |
| with col1: | |
| st.markdown("### 🧠 Qu’est-ce que l’analyse de survie ?") | |
| st.markdown(""" | |
| L’**analyse de survie** (ou **Survival Analysis**) est une méthode statistique utilisée pour **modéliser le temps avant qu’un événement se produise**, comme : | |
| - 🔥 un incendie, | |
| - 🏥 un décès, | |
| - 📉 une résiliation d’abonnement, | |
| - 🧯 une panne. | |
| """) | |
| with col2: | |
| st.markdown("### 📌 Objectif :") | |
| st.markdown(""" | |
| > Estimer la **probabilité qu’un événement ne se soit pas encore produit** à un instant donné. | |
| """) | |
| with col3: | |
| st.markdown("### 🔑 Concepts fondamentaux : ") | |
| st.markdown(""" | |
| - ⏳ **Temps de survie (`T`)** : temps écoulé jusqu’à l’événement. | |
| - 🎯 **Événement** : le phénomène qu’on cherche à prédire (feu, panne, décès...). | |
| - ❓ **Censure** : l’événement **n’a pas encore eu lieu** durant la période d’observation. | |
| - 📉 **Fonction de survie `S(t)`** : probabilité de "survivre" après le temps `t`. | |
| - ⚠️ **Fonction de risque `h(t)`** : probabilité que l’événement se produise **immédiatement après `t`**, sachant qu’il ne s’est pas encore produit. | |
| """) | |
| with col4: | |
| st.markdown ("### 🧪 Exemples d’applications :") | |
| st.markdown(""" | |
| | Domaine | Exemple | | |
| |--------|---------| | |
| | 🔥 Incendies | Quand un feu va-t-il se déclarer ? | | |
| | 🏥 Santé | Combien de temps un patient survivra après traitement ? | | |
| | 📉 Marketing | Quand un client risque-t-il de partir ? | | |
| | 🧑💼 RH | Quand un salarié quittera-t-il l’entreprise ? | | |
| """) | |
| show_footer() | |
| #---------------------------------------------------# Page EDA ----------------------------------------------------------------- | |
| if page == "Exploration des données": | |
| st.title("🗺️ Visualisation des incendies entre 2006 et 2024") | |
| df = load_data() | |
| coords = load_coords() | |
| df_merge = load_df_merge() | |
| st.subheader("Aperçu des coordonnées des villes") | |
| fig = px.scatter_map( | |
| coords, | |
| lat="latitude", | |
| lon="longitude", | |
| hover_name="ville", | |
| height=800, | |
| zoom=5, | |
| map_style="carto-positron", | |
| title="Carte interactive des communes (coordonnées)" | |
| ) | |
| st.plotly_chart(fig, use_container_width=True) | |
| #---------------------------------------------------# DBSCAN Clustering--------------------------------------------------- | |
| st.subheader("🔥 Détection des clusters d'incendies avec DBSCAN") | |
| commune_counts = df_merge.groupby(['Nom de la commune', 'latitude', 'longitude']).size().reset_index(name='frequence') | |
| df_expanded = commune_counts.loc[commune_counts.index.repeat(commune_counts['frequence'])].reset_index(drop=True) | |
| X = df_expanded[['latitude', 'longitude']] | |
| coords_rad = np.radians(X) | |
| kms_per_radian = 6371.0088 | |
| eps_km = 5 | |
| eps = eps_km / kms_per_radian | |
| db = DBSCAN(eps=eps, min_samples=20, metric='haversine').fit(coords_rad) | |
| df_expanded['cluster'] = db.labels_ | |
| clustered_data = df_expanded[df_expanded['cluster'] != -1] | |
| fig = px.scatter_map( | |
| clustered_data, | |
| lat="latitude", | |
| lon="longitude", | |
| color="cluster", | |
| hover_name="Nom de la commune", | |
| zoom=5, | |
| height=900, | |
| title="🔥 Clusters d'incendies en France (2006-2024) détectés par DBSCAN", | |
| map_style="carto-positron" | |
| ) | |
| st.plotly_chart(fig, use_container_width=True) | |
| #---------------------------------------------------Histogramme mensuel#--------------------------------------------------- | |
| st.title("Comparaison mensuelle des incendies par année") | |
| df_temp = df_merge.copy() | |
| df_temp['Date'] = pd.to_datetime(df_temp['Date'], errors='coerce') | |
| df_temp = df_temp.dropna(subset=['Date']) | |
| df_temp['mois'] = df_temp['Date'].dt.month | |
| df_temp['année'] = df_temp['Date'].dt.year | |
| mois_abbr = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', | |
| 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] | |
| df_temp['mois_nom'] = df_temp['mois'].apply(lambda x: mois_abbr[x - 1]) | |
| df_temp['mois_nom'] = pd.Categorical(df_temp['mois_nom'], categories=mois_abbr, ordered=True) | |
| df_grouped = df_temp.groupby(['mois_nom', 'année']).size().reset_index(name='nombre_feux') | |
| fig = px.bar( | |
| df_grouped, | |
| x='mois_nom', | |
| y='nombre_feux', | |
| color='année', | |
| barmode='group', | |
| title='Comparaison mensuelle des incendies par année', | |
| height=600, | |
| width=1000 | |
| ) | |
| st.plotly_chart(fig, use_container_width=True) | |
| show_footer() | |
| #--------------------------------------------------- Analyse des causes--------------------------------------------------- | |
| causes = df_merge['Nature'].value_counts() | |
| st.subheader("Répartition des causes d'incendies") | |
| fig, ax = plt.subplots(figsize=(8, 6)) | |
| ax.pie( | |
| causes.values, | |
| labels=causes.index, | |
| autopct='%1.1f%%', | |
| startangle=140, | |
| shadow=True, | |
| explode=[0.05]*len(causes) | |
| ) | |
| ax.set_title("Répartition des causes d'incendies") | |
| ax.axis('equal') | |
| st.pyplot(fig) | |
| #---------------------------------------------------- Nombre total d’incendies par année----------------------------------------- | |
| if page == "Exploration des données": | |
| st.title("Analyse des incendies par année 🔥") | |
| # Copie du DataFrame | |
| df_temp = df_merge.copy() | |
| # Conversion de la colonne Date | |
| df_temp['Date'] = pd.to_datetime(df_temp['Date']) | |
| df_temp['année'] = df_temp['Date'].dt.year | |
| # Regroupement par année uniquement | |
| df_grouped = df_temp.groupby('année').size().reset_index(name='nombre_feux') | |
| # Création du graphique en barres | |
| fig = px.bar( | |
| df_grouped, | |
| x='année', | |
| y='nombre_feux', | |
| title='Nombre total d’incendies par année', | |
| height=600, | |
| width=1200, | |
| text='nombre_feux' | |
| ) | |
| fig.update_xaxes( | |
| tickmode='linear', | |
| dtick=1 # une année à chaque tick | |
| ) | |
| fig.update_layout( | |
| xaxis_title='Année', | |
| yaxis_title='Nombre de feux', | |
| xaxis_tickangle=0 | |
| ) | |
| st.plotly_chart(fig) | |
| #---------------------------------------------------- Page Exploration des données ----------------------------------------- | |
| if page == "Exploration des données": | |
| #---------------------------------------------------- Les 10 départements avec le plus d’incendies ----------------------------------------- | |
| # Copie du DataFrame | |
| df_temp = df_merge.copy() | |
| # Regroupement par département | |
| df_grouped = df_temp.groupby('Département').size().reset_index(name='nombre_feux') | |
| # Classement décroissant et sélection du top 10 | |
| df_top10 = df_grouped.sort_values(by='nombre_feux', ascending=False).head(10) | |
| # Graphique en barres | |
| fig = px.bar( | |
| df_top10, | |
| x='Département', | |
| y='nombre_feux', | |
| title='Les 10 départements avec le plus d’incendies', | |
| height=600, | |
| width=1000, | |
| text='nombre_feux' | |
| ) | |
| # Fond clair | |
| fig.update_layout( | |
| template='plotly_white', | |
| xaxis_title='Département', | |
| yaxis_title='Nombre de feux', | |
| xaxis_tickangle=-45, | |
| ) | |
| # Texte au-dessus des barres | |
| fig.update_traces(textposition='outside') | |
| # Affichage dans l'app | |
| st.plotly_chart(fig) | |
| #---------------------------------------------------- Les 10 départements les plus touchés ----------------------------------------- | |
| if page == "Exploration des données": | |
| # 🔎 Vérification rapide du DataFrame | |
| if "Département" not in df_merge.columns: | |
| st.error("❌ La colonne 'Département' est absente du DataFrame.") | |
| elif df_merge.empty: | |
| st.warning("⚠️ Le DataFrame est vide.") | |
| else: | |
| # ✅ Copie et nettoyage du DataFrame | |
| df_temp = df_merge.copy() | |
| df_temp = df_temp[df_temp['Département'].notna()] # Supprime les lignes sans département | |
| # 📊 Regroupement par département | |
| df_grouped = df_temp.groupby('Département').size().reset_index(name='nombre_feux') | |
| # 🔢 Total général | |
| total_feux = df_grouped['nombre_feux'].sum() | |
| # 🔝 Top 10 des départements | |
| df_top10 = df_grouped.sort_values(by='nombre_feux', ascending=False).head(10) | |
| # 📈 Calcul des proportions | |
| df_top10['proportion_totale'] = df_top10['nombre_feux'] / total_feux | |
| # 🥧 Création du graphique circulaire | |
| fig_pie = px.pie( | |
| df_top10, | |
| names='Département', | |
| values='nombre_feux', | |
| title='Les 10 départements les plus touchés (proportion sur le total global)', | |
| ) | |
| fig_pie.update_traces(textinfo='label+percent') | |
| # 📌 Affichage dans l'app | |
| st.plotly_chart(fig_pie) | |
| #---------------------------------------------------- Carte des feux par département (2006-2024) --------------------------------------------------------- | |
| if page == "Exploration des données": | |
| st.subheader("Carte des feux par département (2006-2024)") | |
| # Copie du dataset | |
| df_temp = df_merge.copy() | |
| # Codes départements formatés | |
| df_temp['Département'] = df_temp['Département'].astype(str).str.zfill(2) | |
| df_grouped = df_temp.groupby('Département').size().reset_index(name='nombre_feux') | |
| # Chargement GeoJSON | |
| url_geojson = 'https://raw.githubusercontent.com/gregoiredavid/france-geojson/master/departements-version-simplifiee.geojson' | |
| with urllib.request.urlopen(url_geojson) as response: | |
| departements_geojson = json.load(response) | |
| # Carte choroplèthe | |
| fig = px.choropleth( | |
| df_grouped, | |
| geojson=departements_geojson, | |
| locations='Département', | |
| featureidkey='properties.code', | |
| color='nombre_feux', | |
| color_continuous_scale='OrRd', | |
| title='Total des feux par département (2006-2024)', | |
| labels={'nombre_feux': 'Feux'}, | |
| ) | |
| # Style géographique | |
| fig.update_geos( | |
| visible=False, | |
| lataxis_range=[41, 52], | |
| lonaxis_range=[-5.5, 10], | |
| showcountries=False, | |
| showcoastlines=False, | |
| showland=True, | |
| landcolor='white', | |
| fitbounds="locations" | |
| ) | |
| # Mise en page | |
| fig.update_layout( | |
| template='plotly_white', | |
| width=1000, | |
| height=700, | |
| margin=dict(l=0, r=20, t=40, b=0), | |
| coloraxis_colorbar=dict( | |
| title="Feux", | |
| thickness=15, | |
| len=0.4, | |
| y=0.5 | |
| ) | |
| ) | |
| # Affichage Streamlit | |
| st.plotly_chart(fig) | |
| # ---------------------------------------------------- Page Résultats des modèles ----------------------------------------- | |
| elif page == "Résultats des modèles": | |
| st.title("📈 Résultats des modèles prédictifs") | |
| st.subheader("### Comparaison des modèles de Survival Analysis") | |
| st.markdown(""" | |
| | Modèle | Concordance Index | | |
| |-----------------------------------|-------------------| | |
| | Predict survival fonction (MVP) | 0.641 | | |
| | XGBOOST survival cox | 0.752 | | |
| """) | |
| st.markdown("👉 Le modèle **XGBOOST survival cox** obtient la meilleure performance globale.") | |
| st.subheader("📈 Suivi des resultats quotidien du modèle") | |
| query = """SELECT * FROM metrics""" | |
| df = pd.read_sql(query, engine) | |
| df = pd.DataFrame(df) | |
| st.dataframe(df.tail(15)) | |
| show_footer() |