Spaces:
Build error
Build error
| import streamlit as st | |
| import pandas as pd | |
| import numpy as np | |
| import pickle | |
| import os | |
| from data_processing import load_sample_data, preprocess_inputs | |
| from model_utils import load_model, predict_price | |
| # Set page config | |
| st.set_page_config( | |
| page_title="House Price Predictor", | |
| page_icon="🏠", | |
| layout="wide" | |
| ) | |
| # Load custom CSS | |
| def load_css(css_file): | |
| try: | |
| # Essayer avec UTF-8 explicitement | |
| with open(css_file, 'r', encoding='utf-8') as f: | |
| css = f.read() | |
| return css | |
| except UnicodeDecodeError: | |
| # Fallback sur une autre encodage si UTF-8 échoue | |
| try: | |
| with open(css_file, 'r', encoding='latin-1') as f: | |
| css = f.read() | |
| return css | |
| except Exception as e: | |
| return None | |
| try: | |
| css = load_css('style.css') | |
| if css: | |
| st.markdown(f'<style>{css}</style>', unsafe_allow_html=True) | |
| else: | |
| # Si le fichier CSS ne peut pas être chargé, ajouter un style minimal inline | |
| st.markdown(""" | |
| <style> | |
| h1 { | |
| font-size: 2rem; | |
| color: #7f8c8d; | |
| text-align: center; | |
| margin-bottom: 1rem; | |
| } | |
| .sub-header { | |
| font-size: 1.2rem; | |
| color: #7f8c8d; | |
| text-align: center; | |
| margin-bottom: 2rem; | |
| } | |
| .feature-section { | |
| background-color: white; | |
| padding: 20px; | |
| border-radius: 10px; | |
| box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | |
| 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; | |
| } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| except Exception as e: | |
| st.write(f"Erreur lors du chargement du CSS (mode dégradé activé): {e}") | |
| # Custom styling for specific components | |
| st.markdown(""" | |
| <style> | |
| 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; | |
| box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | |
| 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; | |
| } | |
| </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() | |
| # Sidebar for model upload and data statistics | |
| with st.sidebar: | |
| st.markdown(""" | |
| <style> | |
| .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; | |
| } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| # Model upload section | |
| st.markdown('<h2 class="sidebar-header">Télécharger le Modèle pour faire des prédictions</h2>', unsafe_allow_html=True) | |
| st.markdown('<div class="sidebar-box">', unsafe_allow_html=True) | |
| uploaded_model = st.file_uploader("", type=["pkl", "joblib", "sav"]) | |
| if uploaded_model is not None: | |
| st.success("✅ Modèle téléchargé avec succès!") | |
| # Confidence interval settings | |
| 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) | |
| # Initialize model | |
| model = None | |
| if uploaded_model is not None: | |
| try: | |
| model = load_model(uploaded_model) | |
| # Success message is now shown in the sidebar | |
| except Exception as e: | |
| st.error(f"Erreur lors du chargement du modèle : {e}") | |
| st.info("Veuillez vérifier que le modèle est un fichier XGBoost valide au format .pkl") | |
| # 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"): | |
| # Apply CSS to the form | |
| st.markdown(""" | |
| <style> | |
| .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); | |
| } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| # 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 lot (pieds carrés)</div>', unsafe_allow_html=True) | |
| sqft_lot = st.number_input("Taille du lot", min_value=500, max_value=200000, 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 "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") | |
| # Column 3 features | |
| with col3: | |
| if "Year Renovated" not in omitted_fields: | |
| st.markdown('<div class="feature-label">Année de rénovation (0 si aucune)</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=98000, max_value=99000, value=98000, step=1, label_visibility="collapsed") | |
| 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=47.0, max_value=48.0, value=47.5, step=0.01, format="%.4f", label_visibility="collapsed") | |
| 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=-123.0, max_value=-121.0, value=-122.0, step=0.01, format="%.4f", 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=10000, value=1000, step=100, label_visibility="collapsed") | |
| 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") | |
| # Close the container div | |
| st.markdown('</div>', unsafe_allow_html=True) | |
| # Submit button with custom styling | |
| col1, col2, col3 = st.columns([1, 2, 1]) | |
| with col2: | |
| submitted = st.form_submit_button("Prédire le Prix", use_container_width=True) | |
| # Add additional styling to the button | |
| st.markdown(""" | |
| <style> | |
| div.stButton > button { | |
| background-color: #3498db; | |
| color: white; | |
| font-weight: bold; | |
| border-radius: 5px; | |
| border: none; | |
| padding: 0.5em 1em; | |
| font-size: 1.2em; | |
| } | |
| div.stButton > button:hover { | |
| background-color: #2980b9; | |
| box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); | |
| } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| # Process the form submission | |
| if submitted: | |
| if model is None: | |
| st.error("Veuillez télécharger un modèle d'abord.") | |
| else: | |
| try: | |
| # Get the confidence level and error margin from the sidebar | |
| conf_level = confidence_level / 100.0 | |
| error_margin = error_margin_percent / 100.0 | |
| # Prepare input data | |
| input_data = { | |
| '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 | |
| } | |
| # Track omitted features | |
| omitted_features = {k: None for k, v in input_data.items() if v is None} | |
| # Preprocess inputs | |
| processed_input = preprocess_inputs(input_data) | |
| # Make prediction with confidence interval | |
| predicted_price, interval_lower, interval_upper = predict_price( | |
| model, | |
| processed_input, | |
| with_confidence_interval=True, | |
| confidence_level=conf_level, | |
| error_margin=error_margin | |
| ) | |
| # Display prediction with custom styling | |
| st.markdown(f'<div class="prediction-result">Prix Prédit: ${predicted_price:,.2f}</div>', unsafe_allow_html=True) | |
| # Display confidence interval | |
| st.markdown(f""" | |
| <div class="confidence-interval"> | |
| Intervalle de confiance ({confidence_level}%): ${interval_lower:,.2f} - ${interval_upper:,.2f} | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # Show input summary with styled container | |
| with st.expander("📋 Résumé des détails de la propriété"): | |
| st.markdown('<div class="feature-section">', unsafe_allow_html=True) | |
| # Show which features were used as provided by user | |
| st.subheader("🔹 Caractéristiques fournies par l'utilisateur") | |
| provided_features = {k: v for k, v in input_data.items() if v is not None} | |
| if provided_features: | |
| st.json(provided_features) | |
| else: | |
| st.info("Aucune caractéristique n'a été fournie par l'utilisateur.") | |
| # Show which features were omitted and filled with default values | |
| if omitted_features: | |
| st.subheader("🔸 Caractéristiques omises (remplies avec des valeurs par défaut)") | |
| st.write("Les caractéristiques suivantes ont été omises et remplies avec des valeurs par défaut du jeu de données:") | |
| # Create columns for better display | |
| omitted_cols = st.columns(3) | |
| for i, feature in enumerate(omitted_features): | |
| col_idx = i % 3 | |
| with omitted_cols[col_idx]: | |
| st.markdown(f"**{feature}**: {processed_input[feature].values[0]:.2f}") | |
| # Show the final processed input used for prediction | |
| st.subheader("📊 Données d'entrée finales utilisées") | |
| st.dataframe(processed_input, use_container_width=True) | |
| # Show confidence interval details | |
| 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: ${interval_lower:,.2f} - ${interval_upper:,.2f}") | |
| st.markdown('</div>', unsafe_allow_html=True) | |
| except Exception as e: | |
| st.error(f"Une erreur s'est produite lors de la prédiction: {e}") | |
| st.error("Détails: " + str(e)) # More detailed error message | |
| # Add a footer | |
| st.markdown('<div class="footer">Développé avec BONDA DENICLO Emilio | © 2025</div>', unsafe_allow_html=True) |