import streamlit as st import pandas as pd import matplotlib.pyplot as plt import seaborn as sns import requests import plotly.express as px from streamlit_option_menu import option_menu from PIL import Image # ========== CONFIGURATION ========== st.set_page_config(page_title="Getaround Dashboard", layout="wide") st.markdown("

🚗 Getaround Dashboard

", unsafe_allow_html=True) # ========== LOGO ========== try: logo = Image.open("logo.png") st.sidebar.image(logo, width=200) except Exception: st.sidebar.warning("Logo non chargé") # ========== MENU ========== with st.sidebar: selected = option_menu( "Menu principal", ["🏁 EDA delay", "📈 EDA pricing", "⏳ Seuil entre locations", "🔮 API prediction"], icons=["hourglass-split", "bar-chart-line", "clock", "cpu"], menu_icon="cast", default_index=0, ) st.markdown("---") st.markdown("📄 [Espace API HuggingFace](https://huggingface.co/spaces/licorne2lc/get_around_api)") st.markdown("🛠️ [Docs API /predict](https://licorne2lc-get-around-api.hf.space/docs)") # ========== 1. EDA DELAY ========== if selected == "🏁 EDA delay": st.title("📊 Analyse des retards de Getaround") try: dataset = pd.read_excel("get_around_delay_analysis.xlsx") dataset["delay_status"] = dataset["delay_at_checkout_in_minutes"].apply( lambda x: "En avance" if x < 0 else "À l’heure" if x == 0 else "En retard" ) def catégoriser_retard(x): if x <= 0: return "Pas de retard" elif x <= 30: return "< 30 min" elif x <= 60: return "30-60 min" elif x <= 120: return "1h-2h" elif x <= 240: return "2h-4h" else: return "> 4h" retard_order = ["Pas de retard", "< 30 min", "30-60 min", "1h-2h", "2h-4h", "> 4h"] dataset["retard_cat"] = dataset["delay_at_checkout_in_minutes"].apply(catégoriser_retard) dataset["retard_cat"] = pd.Categorical(dataset["retard_cat"], categories=retard_order, ordered=True) fig_checkin = px.pie(dataset, names="checkin_type", title="Répartition des types de check-in", hole=0.4, color_discrete_sequence=px.colors.qualitative.Pastel, width=1000, height=400) st.plotly_chart(fig_checkin) fig_state = px.pie(dataset, names="state", title="Répartition des états de location", hole=0.4, color_discrete_sequence=px.colors.qualitative.Set3, width=1000, height=400) st.plotly_chart(fig_state) fig_delay_status = px.pie(dataset, names="delay_status", title="Retards : avance / à l’heure / en retard", hole=0.4, color_discrete_sequence=px.colors.qualitative.Vivid, width=1000, height=400) st.plotly_chart(fig_delay_status) filtered = dataset[(dataset["delay_at_checkout_in_minutes"] > 0) & (dataset["delay_at_checkout_in_minutes"] <= 1440)] fig_delay_hist = px.histogram(filtered, x="delay_at_checkout_in_minutes", nbins=50, title="Distribution des retards (0 à 1440 minutes)", width=1000, height=400) st.plotly_chart(fig_delay_hist) fig_retard_cat = px.bar(dataset["retard_cat"].value_counts().reindex(retard_order), title="Gravité des retards", labels={"value": "Nombre", "index": "Catégorie"}, width=1000, height=400) st.plotly_chart(fig_retard_cat) dataset_for_join_B = dataset[["rental_id", "delay_at_checkout_in_minutes"]].rename( columns={"rental_id": "rental_id_join", "delay_at_checkout_in_minutes": "delay_at_checkout_in_minutes_join"} ) dataset_join = pd.merge(dataset, dataset_for_join_B, left_on="previous_ended_rental_id", right_on="rental_id_join") dataset_join["real_delay"] = dataset_join["time_delta_with_previous_rental_in_minutes"] - dataset_join["delay_at_checkout_in_minutes_join"] dataset_join_clean = dataset_join.dropna(subset=["delay_at_checkout_in_minutes_join"]) dataset_join_clean["annulation_due_au_retard"] = dataset_join_clean.apply( lambda row: "Oui" if row["state"] == "canceled" and row["real_delay"] < 0 else "Annulée (autre raison)" if row["state"] == "canceled" else "Non", axis=1 ) fig_annul = px.pie(dataset_join_clean, names="annulation_due_au_retard", title="Répartition des annulations dues au retard du locataire précédent", hole=0.4, color_discrete_sequence=px.colors.qualitative.Bold, width=1000, height=400) st.plotly_chart(fig_annul) except FileNotFoundError: st.error("❌ Fichier Excel non trouvé : get_around_delay_analysis.xlsx") # ========== 2. EDA PRICING ========== elif selected == "📈 EDA pricing": st.title("💰 Analyse des prix de location Getaround") try: df_price = pd.read_csv("get_around_pricing_project.csv") st.subheader("Distribution du prix journalier") fig, ax = plt.subplots(figsize=(10, 4)) sns.histplot(df_price["rental_price_per_day"], bins=50, kde=True, ax=ax) st.pyplot(fig) st.subheader("Prix par type de carburant") fig, ax = plt.subplots(figsize=(10, 4)) sns.boxplot(x="fuel", y="rental_price_per_day", data=df_price, ax=ax) st.pyplot(fig) st.subheader("Prix par type de voiture") fig, ax = plt.subplots(figsize=(10, 4)) sns.boxplot(x="car_type", y="rental_price_per_day", data=df_price, ax=ax) st.pyplot(fig) st.subheader("Prix selon la présence de Getaround Connect") fig, ax = plt.subplots(figsize=(10, 4)) sns.boxplot(x="has_getaround_connect", y="rental_price_per_day", data=df_price, ax=ax) st.pyplot(fig) st.subheader("Corrélation entre puissance moteur et prix journalier") fig, ax = plt.subplots(figsize=(10, 4)) sns.scatterplot(x="engine_power", y="rental_price_per_day", data=df_price, hue="car_type", ax=ax) st.pyplot(fig) except FileNotFoundError: st.error("❌ Fichier CSV non trouvé : get_around_pricing_project.csv") # ========== 3. SEUIL ENTRE LOCATIONS ========== elif selected == "⏳ Seuil entre locations": st.title("⏳ Analyse du seuil entre deux locations") # Chargement des données try: dataset = pd.read_excel("get_around_delay_analysis.xlsx") def resolved_rentals(threshold, scope): if scope == "connect": connect_late = dataset[ (dataset["delay_at_checkout_in_minutes"] > 0) & (dataset["checkin_type"] == "connect") ] resolved = connect_late[connect_late["delay_at_checkout_in_minutes"] <= threshold] return round((len(resolved) / len(connect_late)) * 100, 2) else: late = dataset[dataset["delay_at_checkout_in_minutes"] > 0] resolved = late[late["delay_at_checkout_in_minutes"] <= threshold] return round((len(resolved) / len(late)) * 100, 2) # Interface utilisateur st.markdown("### Sélection du seuil") col1, col2 = st.columns(2) with col1: seuil = st.slider("⏱️ Seuil entre deux locations (minutes)", 0, 240, 150, step=30) with col2: scope = st.radio("🚗 Type de véhicules concernés", ["all", "connect"], format_func=lambda x: "Tous les véhicules" if x == "all" else "Connect uniquement") resultat = resolved_rentals(seuil, scope) # Calcul des locations incompatibles (intervalle entre locations trop court) if scope == "connect": total_short = dataset[ (dataset["checkin_type"] == "connect") & (dataset["time_delta_with_previous_rental_in_minutes"] < seuil) ] else: total_short = dataset[ dataset["time_delta_with_previous_rental_in_minutes"] < seuil ] nb_louees_non_resolues = len(total_short) pourcentage_incompatibles = round((nb_louees_non_resolues / len(dataset)) * 100, 2) # Affichage st.success(f"✅ Avec un seuil de **{seuil} minutes**, environ **{resultat}%** des retards sont absorbés.") st.info(f"📉 Environ **{nb_louees_non_resolues} locations** ({pourcentage_incompatibles}%) seraient incompatibles avec ce seuil.") # Préparer les courbes import plotly.graph_objects as go seuils = list(range(0, 241, 30)) all_vals = [resolved_rentals(s, "all") for s in seuils] connect_vals = [resolved_rentals(s, "connect") for s in seuils] # Courbe des % incompatibles if scope == "connect": incompat_vals = [ round(len(dataset[ (dataset["checkin_type"] == "connect") & (dataset["time_delta_with_previous_rental_in_minutes"] < s) ]) / len(dataset) * 100, 2) for s in seuils ] else: incompat_vals = [ round(len(dataset[ dataset["time_delta_with_previous_rental_in_minutes"] < s ]) / len(dataset) * 100, 2) for s in seuils ] # Création du graphique fig = go.Figure() fig.add_trace(go.Scatter( x=seuils, y=all_vals, name="Tous les véhicules - % retards résolus", mode="lines+markers", line=dict(color="blue") )) fig.add_trace(go.Scatter( x=seuils, y=connect_vals, name="Connect uniquement - % retards résolus", mode="lines+markers", line=dict(color="orange") )) fig.add_trace(go.Scatter( x=seuils, y=incompat_vals, name="% locations incompatibles", mode="lines+markers", line=dict(color="purple", dash="dot") )) # Ligne verticale dynamique pour le seuil sélectionné fig.add_trace(go.Scatter( x=[seuil], y=[100], mode="lines", name="Seuil choisi", line=dict(color="red", dash="dash") )) fig.update_layout( title="Impact du seuil : retards résolus vs locations incompatibles", xaxis_title="Seuil (minutes)", yaxis_title="Pourcentage (%)", yaxis_range=[0, 100], template="simple_white" ) st.plotly_chart(fig, use_container_width=True) st.markdown(""" ℹ️ **Conseil produit** : un seuil entre **120 et 150 minutes** permet de résoudre une part significative des retards tout en limitant la perte de réservations successives. """) except FileNotFoundError: st.error("❌ Fichier Excel non trouvé : get_around_delay_analysis.xlsx") # ========== 4. API PREDICTION ========== elif selected == "🔮 API prediction": st.title("🔮 Prédiction du prix de location") st.markdown("Remplis les informations suivantes pour obtenir une estimation du prix") with st.form("prediction_form"): model_key = st.selectbox("Modèle de voiture", ["Peugeot", "Audi", "BMW"]) mileage = st.number_input("Kilométrage", value=50000) engine_power = st.number_input("Puissance moteur", value=100) fuel = st.selectbox("Type de carburant", ["diesel", "petrol", "electric", "hybrid"]) paint_color = st.selectbox("Couleur", ["black", "white", "grey", "blue", "red"]) car_type = st.selectbox("Type de voiture", ["sedan", "convertible", "suv", "coupe"]) private_parking_available = st.checkbox("Parking privé disponible", value=True) has_gps = st.checkbox("GPS", value=True) has_air_conditioning = st.checkbox("Climatisation", value=True) automatic_car = st.checkbox("Boîte automatique", value=True) has_getaround_connect = st.checkbox("Getaround Connect", value=True) has_speed_regulator = st.checkbox("Régulateur de vitesse", value=True) winter_tires = st.checkbox("Pneus hiver", value=True) submitted = st.form_submit_button("📤 Envoyer") if submitted: data = { "model_key": model_key, "mileage": mileage, "engine_power": engine_power, "fuel": fuel, "paint_color": paint_color, "car_type": car_type, "private_parking_available": private_parking_available, "has_gps": has_gps, "has_air_conditioning": has_air_conditioning, "automatic_car": automatic_car, "has_getaround_connect": has_getaround_connect, "has_speed_regulator": has_speed_regulator, "winter_tires": winter_tires, } api_url = "https://licorne2lc-get-around-api.hf.space/predict" try: response = requests.post(api_url, json=data) if response.status_code == 200: price = round(response.json()["prediction"], 2) st.success(f"💰 Prix de location estimé : {price} € / jour") else: st.error(f"❌ Erreur {response.status_code} : {response.text}") except Exception as e: st.error(f"❌ Erreur lors de la requête : {e}")