Spaces:
Sleeping
Sleeping
| import streamlit as st | |
| import pandas as pd | |
| import numpy as np | |
| import pickle | |
| import os | |
| import sys | |
| from data_processing import load_sample_data, preprocess_inputs | |
| from model_utils import load_model, predict_price | |
| from streamlit_folium import folium_static | |
| from visualizations import ( | |
| create_price_distribution_chart, | |
| create_feature_impact_chart, | |
| create_price_vs_area_chart, | |
| create_correlation_heatmap, | |
| create_interactive_map, | |
| create_price_by_zipcode_chart, | |
| create_comparison_dashboard | |
| ) | |
| from ai_chatbot import display_chat_interface | |
| # Set page config | |
| st.set_page_config( | |
| page_title="House Price Predictor", | |
| page_icon="🏠", | |
| layout="wide" | |
| ) | |
| # Intégration directe du CSS | |
| st.markdown(""" | |
| <style> | |
| /* Custom Streamlit CSS */ | |
| /* Main background and text colors */ | |
| .stApp { | |
| background-color: #171933; | |
| color: #D4DCFF; | |
| } | |
| .stMarkdown { | |
| color: #D4DCFF; | |
| } | |
| .stSidebar { | |
| background-color: #22244D; | |
| color: #D4DCFF; | |
| } | |
| .stSidebar .st-emotion-cache-l1ktzw { | |
| color: #D4DCFF; | |
| } | |
| /* Headers styling */ | |
| h1, h2, h3 { | |
| font-family: 'Roboto', sans-serif; | |
| } | |
| h1 { | |
| font-size: 2.5rem; | |
| color: #D4DCFF; | |
| text-align: center; | |
| margin-bottom: 1rem; | |
| font-weight: 700; | |
| padding-bottom: 0.3em; | |
| } | |
| h2 { | |
| font-weight: 600; | |
| color: #D4DCFF; | |
| } | |
| h3 { | |
| font-weight: 500; | |
| } | |
| .e194bff02 { | |
| color: #D4DCFF; | |
| } | |
| /* Form styling */ | |
| .stForm { | |
| background-color: #22244D; | |
| padding: 20px; | |
| border-radius: 10px; | |
| box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | |
| } | |
| .stForm input, .stForm .st-dr, .stForm .e5tuigk2 { | |
| background-color: #171933; | |
| } | |
| .stForm .st-ds, | |
| .stForm #number_input_1, | |
| .stForm #number_input_2, | |
| .stForm #number_input_3, | |
| .stForm #number_input_4, | |
| .stForm #number_input_5, | |
| .stForm #number_input_6, | |
| .stForm #number_input_7, | |
| .stForm #number_input_8, | |
| .stForm #number_input_9, | |
| .stForm #number_input_10, | |
| .stForm #number_input_11, | |
| .stForm #number_input_12{ | |
| color: #D4DCFF; | |
| } | |
| .st-cw .st-ce .st-bn .st-cx .st-cy .st-cz .st-d0 { | |
| background-color: #22244D; | |
| } | |
| .em9zgd08 { | |
| background-color: #5396E7; | |
| } | |
| /* Button styling */ | |
| .stButton > button { | |
| background-color: #3498db; | |
| color: white; | |
| font-weight: 600; | |
| border-radius: 5px; | |
| padding: 0.5em 1em; | |
| transition: all 0.3s ease; | |
| } | |
| .stButton > button:hover { | |
| background-color: #2980b9; | |
| box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); | |
| } | |
| /* Input widgets styling - Version cohérente */ | |
| .stNumberInput > div > div > input, | |
| .stSelectbox > div > div > input, | |
| .stTextInput > div > div > input, | |
| .stTextArea > div > div > textarea, | |
| .stMultiselect > div > div > div, | |
| .stSlider > div > div > div > div, | |
| .stCheckbox > div > div > div, | |
| .stRadio > div > div > div { | |
| background-color: #171933 !important; | |
| border: 1px solid #3498db !important; | |
| border-radius: 5px !important; | |
| color: #D4DCFF !important; | |
| } | |
| /* Focus states pour les inputs */ | |
| .stNumberInput > div > div > input:focus, | |
| .stSelectbox > div > div > input:focus, | |
| .stTextInput > div > div > input:focus, | |
| .stTextArea > div > div > textarea:focus { | |
| border-color: #3498db !important; | |
| box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.2) !important; | |
| } | |
| /* Styling pour les dropdowns et selectbox */ | |
| .stSelectbox > div > div > div[data-baseweb="select"] { | |
| background-color: #171933 !important; | |
| border: 1px solid #3498db !important; | |
| } | |
| /* Styling pour les multiselect */ | |
| .stMultiselect > div > div > div[data-baseweb="select"] { | |
| background-color: #171933 !important; | |
| border: 1px solid #3498db !important; | |
| } | |
| /* Styling pour les sliders */ | |
| .stSlider > div > div > div > div[data-baseweb="slider"] { | |
| background-color: #171933 !important; | |
| } | |
| /* Styling pour les checkboxes et radios */ | |
| .stCheckbox > div > div > div[data-baseweb="checkbox"], | |
| .stRadio > div > div > div[data-baseweb="radio"] { | |
| background-color: #171933 !important; | |
| border: 1px solid #3498db !important; | |
| } | |
| /* Hover effects pour tous les inputs */ | |
| .stNumberInput:hover > div > div > input, | |
| .stSelectbox:hover > div > div > input, | |
| .stTextInput:hover > div > div > input, | |
| .stTextArea:hover > div > div > textarea, | |
| .stMultiselect:hover > div > div > div, | |
| .stSlider:hover > div > div > div > div, | |
| .stCheckbox:hover > div > div > div, | |
| .stRadio:hover > div > div > div { | |
| border-color: #2980b9 !important; | |
| box-shadow: 0 2px 4px rgba(52, 152, 219, 0.1) !important; | |
| } | |
| /* Sidebar styling */ | |
| .css-1d391kg { | |
| background-color: #ffffff; | |
| } | |
| .css-1d391kg .stMarkdown { | |
| color: #D4DCFF; | |
| } | |
| /* Success message styling */ | |
| .element-container .stAlert.st-ae { | |
| border-radius: 8px; | |
| } | |
| .element-container .stSuccess { | |
| color: #155724; | |
| border-color: #c3e6cb; | |
| } | |
| /* Error message styling */ | |
| .element-container .stError { | |
| color: #721c24; | |
| border-color: #f5c6cb; | |
| } | |
| /* Multiselect dropdown styling */ | |
| .stMultiSelect > div { | |
| border-radius: 8px; | |
| box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); | |
| } | |
| /* Add some animation to the chart */ | |
| [data-testid="stDataFrame"] { | |
| transition: transform 0.3s ease; | |
| } | |
| [data-testid="stDataFrame"]:hover { | |
| transform: scale(1.01); | |
| } | |
| /* Card-like sections */ | |
| .stExpander { | |
| border-radius: 8px; | |
| box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); | |
| background-color: #22244D; | |
| } | |
| .e194bff00 { | |
| color: #D4DCFF; | |
| } | |
| /* Section dividers */ | |
| hr { | |
| border: 0; | |
| height: 1px; | |
| background-image: linear-gradient(to right, rgba(0, 0, 0, 0), rgba(52, 152, 219, 0.75), rgba(0, 0, 0, 0)); | |
| margin: 2em 0; | |
| } | |
| /* Hover effects for selectbox */ | |
| .stSelectbox:hover { | |
| border-color: #3498db; | |
| } | |
| /* Property Details Section Styling */ | |
| [data-testid="stHeader"]:has(h1, h2, h3)::before { | |
| content: "🏠"; | |
| margin-right: 10px; | |
| } | |
| /* Custom styling for specific components */ | |
| h1 { | |
| font-size: 2.5rem; | |
| color: #D4DCFF; | |
| text-align: center; | |
| margin-bottom: 1rem; | |
| } | |
| .sub-header { | |
| font-size: 1.2rem; | |
| color: #D4DCFF; | |
| text-align: center; | |
| margin-bottom: 2rem; | |
| font-style: italic; | |
| } | |
| .feature-section { | |
| color: #D4DCFF; | |
| padding: 20px; | |
| border-radius: 10px; | |
| margin-bottom: 20px; | |
| } | |
| .prediction-result { | |
| font-size: 1.5rem; | |
| font-weight: bold; | |
| text-align: center; | |
| padding: 20px; | |
| border-radius: 10px; | |
| margin: 20px 0; | |
| background: linear-gradient(to right, #3498db, #2c3e50); | |
| color: white; | |
| } | |
| .confidence-interval { | |
| font-size: 1.1rem; | |
| text-align: center; | |
| color: #7f8c8d; | |
| margin-top: -10px; | |
| margin-bottom: 20px; | |
| } | |
| .footer { | |
| text-align: center; | |
| color: #95a5a6; | |
| padding: 20px; | |
| font-size: 0.8rem; | |
| } | |
| .sidebar-header { | |
| background-color: #171933; | |
| color: #D4DCFF; | |
| border-radius: 5px; | |
| text-align: center; | |
| font-weight: bold; | |
| } | |
| .sidebar-box { | |
| border-radius: 8px; | |
| margin-bottom: 20px; | |
| } | |
| .sidebar-subheader { | |
| background-color: #171933; | |
| color: #2c3e50; | |
| margin-top: 20px; | |
| font-weight: 600; | |
| text-align: center; | |
| border-radius: 5px; | |
| margin-bottom: 20px; | |
| } | |
| .stat-item { | |
| background-color: #171933; | |
| padding: 8px; | |
| border-radius: 5px; | |
| margin-bottom: 5px; | |
| border-left: 3px solid #02b35a; | |
| } | |
| .property-form { | |
| background-color: #22244D; | |
| } | |
| .feature-label { | |
| font-weight: 600; | |
| color: #D4DCFF; | |
| margin-bottom: 5px; | |
| } | |
| .submit-btn { | |
| background-color: #3498db; | |
| color: white; | |
| font-weight: bold; | |
| padding: 10px 20px; | |
| border-radius: 5px; | |
| border: none; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| display: block; | |
| width: 100%; | |
| text-align: center; | |
| margin-top: 20px; | |
| } | |
| .submit-btn:hover { | |
| background-color: #2980b9; | |
| box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); | |
| } | |
| /* Labels styling pour une meilleure cohérence */ | |
| .stNumberInput > label, | |
| .stSelectbox > label, | |
| .stTextInput > label, | |
| .stTextArea > label, | |
| .stMultiselect > label, | |
| .stSlider > label, | |
| .stCheckbox > label, | |
| .stRadio > label { | |
| color: #D4DCFF !important; | |
| font-weight: 600 !important; | |
| margin-bottom: 8px !important; | |
| } | |
| /* Conteneurs de widgets pour une meilleure organisation */ | |
| .stNumberInput, | |
| .stSelectbox, | |
| .stTextInput, | |
| .stTextArea, | |
| .stMultiselect, | |
| .stSlider, | |
| .stCheckbox, | |
| .stRadio { | |
| margin-bottom: 15px !important; | |
| } | |
| /* Styling pour les placeholders */ | |
| .stNumberInput > div > div > input::placeholder, | |
| .stTextInput > div > div > input::placeholder, | |
| .stTextArea > div > div > textarea::placeholder { | |
| color: #95a5a6 !important; | |
| opacity: 0.7 !important; | |
| } | |
| /* Amélioration du styling des boutons de formulaire */ | |
| .stFormSubmitButton > button { | |
| background-color: #3498db !important; | |
| color: white !important; | |
| font-weight: 600 !important; | |
| border-radius: 8px !important; | |
| padding: 12px 24px !important; | |
| border: none !important; | |
| transition: all 0.3s ease !important; | |
| box-shadow: 0 2px 4px rgba(52, 152, 219, 0.2) !important; | |
| } | |
| .stFormSubmitButton > button:hover { | |
| background-color: #2980b9 !important; | |
| box-shadow: 0 4px 8px rgba(52, 152, 219, 0.3) !important; | |
| transform: translateY(-1px) !important; | |
| } | |
| /* Styling pour les messages d'erreur et de succès */ | |
| .stAlert { | |
| border-radius: 8px !important; | |
| border: none !important; | |
| box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1) !important; | |
| } | |
| /* Amélioration du styling des onglets */ | |
| .stTabs > div > div > div > div > div { | |
| background-color: #22244D !important; | |
| border-radius: 8px !important; | |
| } | |
| .stTabs > div > div > div > div > div > button { | |
| color: #D4DCFF !important; | |
| background-color: transparent !important; | |
| border: none !important; | |
| border-radius: 6px !important; | |
| transition: all 0.3s ease !important; | |
| } | |
| .stTabs > div > div > div > div > div > button[aria-selected="true"] { | |
| background-color: #3498db !important; | |
| color: white !important; | |
| box-shadow: 0 2px 4px rgba(52, 152, 219, 0.2) !important; | |
| } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| # Title and description with custom styling | |
| st.markdown('<h1>🏠 Prédiction de Prix Immobilier</h1>', unsafe_allow_html=True) | |
| st.markdown('<p class="sub-header">Cette application vous permet de prédire le prix des propriétés en utilisant un modèle XGBoost pré-entraîné.</p>', unsafe_allow_html=True) | |
| # Load sample data for statistics | |
| sample_data = load_sample_data() | |
| # Charger le modèle directement au démarrage de l'application | |
| if 'model' not in st.session_state: | |
| try: | |
| st.session_state.model = load_model() | |
| st.success("✅ Modèle chargé avec succès !") | |
| except Exception as e: | |
| st.error(f"Erreur lors du chargement du modèle : {str(e)}") | |
| st.session_state.model = None | |
| # Sidebar for data statistics and settings | |
| with st.sidebar: | |
| # Settings section | |
| st.markdown('<h2 class="sidebar-header">⚙️ Paramètres</h2>', unsafe_allow_html=True) | |
| st.markdown('<div style="color: #D4DCFF;" class="sidebar-box">', unsafe_allow_html=True) | |
| # Add confidence level settings | |
| confidence_level = st.slider( | |
| "Niveau de confiance (%)", | |
| min_value=50, | |
| max_value=99, | |
| value=95, | |
| step=5, | |
| help="Niveau de confiance pour l'intervalle de prédiction" | |
| ) | |
| # Add error margin slider | |
| error_margin_percent = st.slider( | |
| "Marge d'erreur estimée (%)", | |
| min_value=5, | |
| max_value=30, | |
| value=15, | |
| step=5, | |
| help="Pourcentage d'erreur estimée pour le modèle" | |
| ) | |
| st.markdown('</div>', unsafe_allow_html=True) | |
| # Dataset Statistics | |
| if sample_data is not None: | |
| # Preview | |
| st.markdown('<div class="sidebar-box">', unsafe_allow_html=True) | |
| st.markdown('<h3 style="margin-bottom: 15px;" class="sidebar-subheader">📊 Aperçu du Dataset</h3>', unsafe_allow_html=True) | |
| st.dataframe(sample_data.head(3), use_container_width=True) | |
| st.markdown('</div>', unsafe_allow_html=True) | |
| # Features stats | |
| st.markdown('<div class="sidebar-box">', unsafe_allow_html=True) | |
| st.markdown('<h3 class="sidebar-subheader">Caractéristiques Numériques</h3>', unsafe_allow_html=True) | |
| # Price stats (highlighted) | |
| if 'price' in sample_data.columns: | |
| price_stats = sample_data['price'].describe() | |
| st.markdown(""" | |
| <div style="background-color: #171933; padding: 10px; border-radius: 5px; margin-bottom: 15px; margin-top: 15px; border-left: 3px solid #3498db;"> | |
| <h4 style="margin: 0; color: #D4DCFF;">Prix (Target Variable)</h4> | |
| <p style="margin: 5px 0;">Min: ${:,.2f} | Max: ${:,.2f}</p> | |
| <p style="margin: 5px 0;">Mean: ${:,.2f} | Median: ${:,.2f}</p> | |
| </div> | |
| """.format( | |
| price_stats['min'], | |
| price_stats['max'], | |
| price_stats['mean'], | |
| price_stats['50%'] | |
| ), unsafe_allow_html=True) | |
| # Other features | |
| num_cols = sample_data.select_dtypes(include=['int64', 'float64']).columns | |
| for col in num_cols: | |
| if col != 'id' and col != 'price': # Exclude ID and target variable | |
| stats = sample_data[col].describe() | |
| st.markdown(f""" | |
| <div class="stat-item"> | |
| <strong>{col}</strong><br/> | |
| Min: {stats['min']:.2f} | Max: {stats['max']:.2f}<br/> | |
| Mean: {stats['mean']:.2f} | |
| </div> | |
| """, unsafe_allow_html=True) | |
| st.markdown('</div>', unsafe_allow_html=True) | |
| # Main content | |
| st.markdown('<h2 style="color: #D4DCFF; padding-bottom: 5px;">Détails de la Propriété</h2>', unsafe_allow_html=True) | |
| # Define all available features | |
| all_features = { | |
| 'bedrooms': "Bedrooms", | |
| 'bathrooms': "Bathrooms", | |
| 'sqft_living': "Living Area (sqft)", | |
| 'sqft_lot': "Lot Size (sqft)", | |
| 'floors': "Floors", | |
| 'waterfront': "Waterfront", | |
| 'view': "View", | |
| 'condition': "Condition", | |
| 'grade': "Grade", | |
| 'yr_built': "Year Built", | |
| 'yr_renovated': "Year Renovated", | |
| 'zipcode': "Zipcode", | |
| 'lat': "Latitude", | |
| 'long': "Longitude", | |
| 'sqft_above': "Square Feet Above Ground", | |
| 'sqft_basement': "Square Feet Basement" | |
| } | |
| # Multi-select to choose fields to omit - styling with custom container | |
| st.markdown('<div class="feature-section">', unsafe_allow_html=True) | |
| st.subheader("Sélectionnez les champs à omettre pour la prédiction") | |
| omitted_fields = st.multiselect( | |
| "", | |
| options=list(all_features.values()) | |
| ) | |
| st.markdown('</div>', unsafe_allow_html=True) | |
| # Create lookup from display name to field name | |
| display_to_field = {v: k for k, v in all_features.items()} | |
| # Get the field names that are omitted | |
| omitted_field_names = [display_to_field[field] for field in omitted_fields] | |
| # Create form for user inputs | |
| with st.form("property_form"): | |
| # Wrap the form in a styled container | |
| st.markdown('<div class="property-form">', unsafe_allow_html=True) | |
| # Create columns for a cleaner form layout | |
| col1, col2, col3 = st.columns(3) | |
| # Initialize input variables with None | |
| bedrooms = bathrooms = sqft_living = sqft_lot = floors = None | |
| waterfront = view = condition = grade = yr_built = None | |
| yr_renovated = zipcode = lat = long = sqft_above = sqft_basement = None | |
| # Column 1 features | |
| with col1: | |
| if "Bedrooms" not in omitted_fields: | |
| st.markdown('<div class="feature-label">Chambres</div>', unsafe_allow_html=True) | |
| bedrooms = st.number_input("Chambres", min_value=0, max_value=10, value=3, step=1, label_visibility="collapsed") | |
| if "Bathrooms" not in omitted_fields: | |
| st.markdown('<div class="feature-label">Salles de bain</div>', unsafe_allow_html=True) | |
| bathrooms = st.number_input("Salles de bain", min_value=0.0, max_value=10.0, value=2.0, step=0.25, label_visibility="collapsed") | |
| if "Living Area (sqft)" not in omitted_fields: | |
| st.markdown('<div class="feature-label">Surface habitable (pieds carrés)</div>', unsafe_allow_html=True) | |
| sqft_living = st.number_input("Surface habitable", min_value=200, max_value=10000, value=1500, step=100, label_visibility="collapsed") | |
| if "Lot Size (sqft)" not in omitted_fields: | |
| st.markdown('<div class="feature-label">Taille du terrain (pieds carrés)</div>', unsafe_allow_html=True) | |
| sqft_lot = st.number_input("Taille du terrain", min_value=200, max_value=100000, value=5000, step=500, label_visibility="collapsed") | |
| if "Floors" not in omitted_fields: | |
| st.markdown('<div class="feature-label">Étages</div>', unsafe_allow_html=True) | |
| floors = st.number_input("Étages", min_value=1.0, max_value=4.0, value=1.0, step=0.5, label_visibility="collapsed") | |
| # Column 2 features | |
| with col2: | |
| if "Waterfront" not in omitted_fields: | |
| st.markdown('<div class="feature-label">Vue sur l\'eau</div>', unsafe_allow_html=True) | |
| waterfront = st.selectbox("Vue sur l'eau", options=[0, 1], format_func=lambda x: 'Oui' if x == 1 else 'Non', label_visibility="collapsed") | |
| if "View" not in omitted_fields: | |
| st.markdown('<div class="feature-label">Vue</div>', unsafe_allow_html=True) | |
| view = st.selectbox("Vue", options=[0, 1, 2, 3, 4], | |
| format_func=lambda x: {0: "Aucune", 1: "Passable", 2: "Moyenne", 3: "Bonne", 4: "Excellente"}[x], | |
| label_visibility="collapsed") | |
| if "Condition" not in omitted_fields: | |
| st.markdown('<div class="feature-label">État</div>', unsafe_allow_html=True) | |
| condition = st.selectbox("État", options=[1, 2, 3, 4, 5], | |
| format_func=lambda x: {1: "Mauvais", 2: "Passable", 3: "Moyen", 4: "Bon", 5: "Excellent"}[x], | |
| label_visibility="collapsed") | |
| if "Grade" not in omitted_fields: | |
| st.markdown('<div class="feature-label">Qualité de construction</div>', unsafe_allow_html=True) | |
| grade = st.selectbox("Qualité", options=list(range(1, 14)), | |
| format_func=lambda x: { | |
| 1: "Très mauvaise", 2: "Mauvaise", 3: "Mauvaise", 4: "Moyenne inférieure", | |
| 5: "Moyenne", 6: "Moyenne", 7: "Bonne", 8: "Bonne", | |
| 9: "Meilleure", 10: "Meilleure", 11: "Excellente", | |
| 12: "Excellente", 13: "Luxe" | |
| }[x], | |
| label_visibility="collapsed") | |
| if "Square Feet Above Ground" not in omitted_fields: | |
| st.markdown('<div class="feature-label">Surface au-dessus du sol (pieds carrés)</div>', unsafe_allow_html=True) | |
| sqft_above = st.number_input("Surface au-dessus du sol", min_value=200, max_value=9000, value=1000, step=100, label_visibility="collapsed") | |
| # Column 3 features | |
| with col3: | |
| if "Square Feet Basement" not in omitted_fields: | |
| st.markdown('<div class="feature-label">Surface du sous-sol (pieds carrés)</div>', unsafe_allow_html=True) | |
| sqft_basement = st.number_input("Surface du sous-sol", min_value=0, max_value=5000, value=0, step=100, label_visibility="collapsed") | |
| if "Year Built" not in omitted_fields: | |
| st.markdown('<div class="feature-label">Année de construction</div>', unsafe_allow_html=True) | |
| yr_built = st.number_input("Année de construction", min_value=1900, max_value=2023, value=1980, step=1, label_visibility="collapsed") | |
| if "Year Renovated" not in omitted_fields: | |
| st.markdown('<div class="feature-label">Année de rénovation (0 = jamais rénové)</div>', unsafe_allow_html=True) | |
| yr_renovated = st.number_input("Année de rénovation", min_value=0, max_value=2023, value=0, step=1, label_visibility="collapsed") | |
| if "Zipcode" not in omitted_fields: | |
| st.markdown('<div class="feature-label">Code postal</div>', unsafe_allow_html=True) | |
| zipcode = st.number_input("Code postal", min_value=10000, max_value=99999, value=98000, step=1, label_visibility="collapsed") | |
| # Geographic Location (optional) | |
| if "Latitude" not in omitted_fields or "Longitude" not in omitted_fields: | |
| st.markdown("<hr>", unsafe_allow_html=True) | |
| st.markdown('<h3 style="color: #D4DCFF; text-align: center; margin-bottom: 15px;">Localisation Géographique</h3>', unsafe_allow_html=True) | |
| geo_col1, geo_col2 = st.columns(2) | |
| with geo_col1: | |
| if "Latitude" not in omitted_fields: | |
| st.markdown('<div class="feature-label">Latitude</div>', unsafe_allow_html=True) | |
| lat = st.number_input("Latitude", min_value=45.0, max_value=50.0, value=47.5, step=0.001, format="%.6f", label_visibility="collapsed") | |
| with geo_col2: | |
| if "Longitude" not in omitted_fields: | |
| st.markdown('<div class="feature-label">Longitude</div>', unsafe_allow_html=True) | |
| long = st.number_input("Longitude", min_value=-125.0, max_value=-115.0, value=-122.0, step=0.001, format="%.6f", label_visibility="collapsed") | |
| # Close the form div | |
| st.markdown('</div>', unsafe_allow_html=True) | |
| # Submit button | |
| submit_button = st.form_submit_button( | |
| label="Prédire le Prix", | |
| use_container_width=True, | |
| type="primary" | |
| ) | |
| # Create input dictionary from form values | |
| input_dict = { | |
| 'bedrooms': bedrooms, | |
| 'bathrooms': bathrooms, | |
| 'sqft_living': sqft_living, | |
| 'sqft_lot': sqft_lot, | |
| 'floors': floors, | |
| 'waterfront': waterfront, | |
| 'view': view, | |
| 'condition': condition, | |
| 'grade': grade, | |
| 'sqft_above': sqft_above, | |
| 'sqft_basement': sqft_basement, | |
| 'yr_built': yr_built, | |
| 'yr_renovated': yr_renovated, | |
| 'zipcode': zipcode, | |
| 'lat': lat, | |
| 'long': long, | |
| 'sqft_living15': sqft_living, # Using the same value as sqft_living as an approximation | |
| 'sqft_lot15': sqft_lot # Using the same value as sqft_lot as an approximation | |
| } | |
| # Handle form submission | |
| if submit_button: | |
| # Check if model is loaded | |
| if st.session_state.model is None: | |
| st.error("⚠️ Le modèle n'a pas pu être chargé. Veuillez vérifier que le fichier 'model.pkl' existe.") | |
| else: | |
| try: | |
| # Preprocess inputs | |
| processed_input = preprocess_inputs(input_dict) | |
| # Make prediction | |
| confidence_level_decimal = confidence_level / 100 | |
| error_margin_decimal = error_margin_percent / 100 | |
| prediction_result = predict_price( | |
| st.session_state.model, | |
| processed_input, | |
| with_confidence_interval=True, | |
| confidence_level=confidence_level_decimal, | |
| error_margin=error_margin_decimal | |
| ) | |
| # Suppression de l'affichage du prix estimé ici (évite le double affichage) | |
| # On conserve uniquement la sauvegarde dans la session | |
| property_data_for_chat = {k: v for k, v in input_dict.items() if v is not None} | |
| st.session_state.last_prediction_data = { | |
| 'property_data': property_data_for_chat, | |
| 'prediction_result': prediction_result, | |
| 'market_data': sample_data, | |
| 'confidence_level': confidence_level, | |
| 'error_margin_percent': error_margin_percent | |
| } | |
| except Exception as e: | |
| st.error(f"Erreur lors de la prédiction: {str(e)}") | |
| st.info("Veuillez vérifier que le modèle chargé est compatible avec les caractéristiques que vous avez saisies.") | |
| # Affichage persistant des visualisations et du chatbot | |
| if 'last_prediction_data' in st.session_state and st.session_state.last_prediction_data: | |
| prediction_data = st.session_state.last_prediction_data | |
| prediction_result = prediction_data['prediction_result'] | |
| property_data = prediction_data['property_data'] | |
| sample_data = prediction_data['market_data'] | |
| confidence_level = prediction_data.get('confidence_level', 95) | |
| error_margin_percent = prediction_data.get('error_margin_percent', 15) | |
| if isinstance(prediction_result, tuple) and len(prediction_result) == 3: | |
| predicted_price, lower_bound, upper_bound = prediction_result | |
| else: | |
| predicted_price = prediction_result | |
| lower_bound = upper_bound = None | |
| # Affichage du résultat de la prédiction | |
| st.markdown(f""" | |
| <div class="prediction-result"> | |
| 💰 Prix Estimé: ${predicted_price:,.2f} | |
| </div> | |
| """, unsafe_allow_html=True) | |
| if lower_bound is not None and upper_bound is not None: | |
| st.info(f""" | |
| Cette prédiction est basée sur les caractéristiques que vous avez fournies. | |
| L'intervalle de confiance indique que le prix réel a {confidence_level}% de chances | |
| de se situer entre les valeurs indiquées, en supposant une marge d'erreur de {error_margin_percent}%. | |
| """) | |
| st.subheader("📈 Détails de l'intervalle de confiance") | |
| st.write(f"Niveau de confiance: {confidence_level}%") | |
| st.write(f"Marge d'erreur estimée: {error_margin_percent}%") | |
| st.write(f"Intervalle: ${lower_bound:,.2f} - ${upper_bound:,.2f}") | |
| st.progress((predicted_price - lower_bound) / (upper_bound - lower_bound)) | |
| # Visualisations interactives | |
| st.markdown("<hr>", unsafe_allow_html=True) | |
| st.markdown('<h2 style="color: #D4DCFF; text-align: center; margin: 20px 0;">📊 Visualisations Interactives</h2>', unsafe_allow_html=True) | |
| tab1, tab2, tab3, tab4, tab5 = st.tabs([ | |
| "📈 Distribution des Prix", | |
| "🏠 Prix vs Caractéristiques", | |
| "🗺️ Carte Interactive", | |
| "📊 Analyse Comparative", | |
| "🔍 Corrélations" | |
| ]) | |
| with tab1: | |
| st.markdown('<h3 style="color: #D4DCFF;margin-bottom: 10px;padding-left: 20px;">Distribution des Prix du Marché</h3>', unsafe_allow_html=True) | |
| price_dist_chart = create_price_distribution_chart(sample_data, predicted_price) | |
| if price_dist_chart: | |
| st.plotly_chart(price_dist_chart, use_container_width=True) | |
| price_area_chart = create_price_vs_area_chart(sample_data, predicted_price, property_data.get('sqft_living')) | |
| if price_area_chart: | |
| st.plotly_chart(price_area_chart, use_container_width=True) | |
| with tab2: | |
| st.markdown('<h3 style="color: #D4DCFF;margin-bottom: 10px;padding-left: 20px;">Impact des Caractéristiques sur le Prix</h3>', unsafe_allow_html=True) | |
| feature_options = { | |
| 'bedrooms': 'Nombre de chambres', | |
| 'bathrooms': 'Nombre de salles de bain', | |
| 'sqft_living': 'Surface habitable', | |
| 'floors': 'Nombre d\'étages', | |
| 'grade': 'Qualité de construction', | |
| 'condition': 'État de la propriété' | |
| } | |
| selected_feature = st.selectbox( | |
| "Choisissez une caractéristique à analyser :", | |
| options=list(feature_options.keys()), | |
| format_func=lambda x: feature_options[x] | |
| ) | |
| feature_chart = create_feature_impact_chart(sample_data, selected_feature, predicted_price) | |
| if feature_chart: | |
| st.plotly_chart(feature_chart, use_container_width=True) | |
| with tab3: | |
| st.markdown('<h3 style="color: #D4DCFF;margin-bottom: 10px;padding-left: 20px;">Carte Interactive des Propriétés</h3>', unsafe_allow_html=True) | |
| lat = property_data.get('lat') | |
| long = property_data.get('long') | |
| if lat is not None and long is not None: | |
| interactive_map = create_interactive_map(sample_data, lat, long, predicted_price) | |
| if interactive_map: | |
| folium_static(interactive_map, width=800, height=500) | |
| else: | |
| st.info("📍 Activez les coordonnées géographiques pour voir la carte interactive") | |
| interactive_map = create_interactive_map(sample_data) | |
| if interactive_map: | |
| folium_static(interactive_map, width=800, height=500) | |
| with tab4: | |
| st.markdown('<h3 style="color: #D4DCFF;margin-bottom: 10px;padding-left: 20px;">Analyse Comparative avec le Marché</h3>', unsafe_allow_html=True) | |
| comparison_chart, comparison_stats = create_comparison_dashboard(sample_data, predicted_price, property_data) | |
| if comparison_chart: | |
| st.plotly_chart(comparison_chart, use_container_width=True) | |
| if comparison_stats: | |
| st.markdown('<h4 style="color: #D4DCFF; margin-top: 20px;margin-bottom: 10px;padding-left: 20px;">Statistiques Comparatives</h4>', unsafe_allow_html=True) | |
| col1, col2, col3 = st.columns(3) | |
| with col1: | |
| st.metric("Prix Moyen", comparison_stats['Prix moyen du marché']) | |
| with col2: | |
| st.metric("Prix Médian", comparison_stats['Prix médian du marché']) | |
| with col3: | |
| st.metric("Votre Prédiction", comparison_stats['Votre prédiction']) | |
| col4, col5 = st.columns(2) | |
| with col4: | |
| st.metric("Différence", comparison_stats['Différence avec la moyenne']) | |
| with col5: | |
| st.metric("Pourcentage", comparison_stats['Pourcentage vs moyenne']) | |
| zipcode_chart = create_price_by_zipcode_chart(sample_data, predicted_price, property_data.get('zipcode')) | |
| if zipcode_chart: | |
| st.plotly_chart(zipcode_chart, use_container_width=True) | |
| with tab5: | |
| st.markdown('<h3 style="color: #D4DCFF;margin-bottom: 10px;padding-left: 20px;">Matrice de Corrélation</h3>', unsafe_allow_html=True) | |
| correlation_chart = create_correlation_heatmap(sample_data) | |
| if correlation_chart: | |
| st.plotly_chart(correlation_chart, use_container_width=True) | |
| st.markdown(""" | |
| <div style="background-color: #22244D; padding: 15px; border-radius: 8px; margin-top: 15px;"> | |
| <h4 style="color: #D4DCFF; margin-bottom: 10px;">📋 Interprétation des Corrélations</h4> | |
| <ul style="color: #D4DCFF;"> | |
| <li><strong>Rouge foncé</strong> : Corrélation positive forte (proche de 1)</li> | |
| <li><strong>Bleu foncé</strong> : Corrélation négative forte (proche de -1)</li> | |
| <li><strong>Blanc</strong> : Pas de corrélation (proche de 0)</li> | |
| </ul> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # Chatbot | |
| st.markdown("<hr>", unsafe_allow_html=True) | |
| st.markdown('<h2 style="color: #D4DCFF; text-align: center; margin: 20px 0;">🤖 Assistant IA Immobilier</h2>', unsafe_allow_html=True) | |
| display_chat_interface( | |
| property_data=property_data, | |
| prediction_result=prediction_result, | |
| market_data=sample_data | |
| ) | |
| # Add a footer | |
| st.markdown(""" | |
| <div class="footer"> | |
| Développé avec BONDA DENICLO Emilio | © 2025 | |
| </div> | |
| """, unsafe_allow_html=True) |