import streamlit as st import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns import shap import mlflow import mlflow.sklearn import mlflow from mlflow.tracking import MlflowClient from sklearn.model_selection import train_test_split from sklearn.linear_model import LinearRegression from sklearn.tree import DecisionTreeRegressor, plot_tree from sklearn.ensemble import RandomForestRegressor from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score from sklearn.metrics import f1_score, accuracy_score, precision_score from sklearn.preprocessing import LabelEncoder from sklearn.tree import DecisionTreeClassifier from sklearn.tree import DecisionTreeRegressor, DecisionTreeClassifier, plot_tree import pickle # Page config st.set_page_config(page_title="Food Delivery Time Prediction", layout="centered", page_icon="๐Ÿ”") # Sidebar navigation st.sidebar.title("๐Ÿ” Food Delivery Dashboard") page = st.sidebar.selectbox("Select Page", [ "Introduction ๐Ÿ“˜", "Visualization ๐Ÿ“Š", "Prediction ๐Ÿ”ฎ", "Explainability ๐Ÿค”", "Model Tracker ๐Ÿ“Š", "Conclusion ๐Ÿ“Œ", "What-If Simulator ๐Ÿ”" ]) # Load dataset @st.cache_data def load_data(): df = pd.read_csv("src/Food_Delivery_Times.csv") return df df = load_data() # Page 1: Introduction if page == "Introduction ๐Ÿ“˜": with st.container(): st.title("๐Ÿšด Food Delivery Time Prediction") st.markdown("## ๐ŸŒŸ Problem Statement") st.markdown(""" Food delivery companies struggle with accurately estimating delivery times. Inaccurate estimates reduce customer satisfaction and can hurt business. This app aims to **predict delivery time** based on factors like distance, traffic, weather, and driver experience using different machine learning models. """) st.image("src/food.jpg") st.markdown("## ๐Ÿ“ Dataset Overview") rows = st.slider("Preview rows", 5, 30, 10) st.dataframe(df.head(rows)) st.markdown("### ๐Ÿ”Ž Missing Values") missing = df.isnull().sum() st.write(missing) if missing.sum() == 0: st.success("โœ… No missing values") else: st.warning("โš ๏ธ Some columns have missing values and will be dropped for modeling.") st.markdown("### ๐Ÿ“Š Summary Statistics") if st.button("Show Summary"): st.dataframe(df.describe()) # Page 2: Visualization elif page == "Visualization ๐Ÿ“Š": with st.container(): st.title("๐Ÿ“Š Data Insights") df_viz = df.dropna() st.markdown("### ๐Ÿš— Delivery Vehicle Type Distribution") vehicle_counts = df_viz["Vehicle_Type"].value_counts() fig1, ax1 = plt.subplots() ax1.pie(vehicle_counts, labels=vehicle_counts.index, autopct='%1.1f%%', startangle=90) ax1.set_title("Distribution of Delivery Vehicle Types") st.pyplot(fig1) st.markdown("### ๐Ÿ›๏ธ Avg Delivery Time by Distance Segment") bins = [0, 5, 10, 15, 20, 25] labels = ["0-5km", "5-10km", "10-15km", "15-20km", "20-25km"] df_viz["Distance_Segment"] = pd.cut(df_viz["Distance_km"], bins=bins, labels=labels) avg_by_segment = df_viz.groupby("Distance_Segment")["Delivery_Time_min"].mean().reset_index() fig2, ax2 = plt.subplots() sns.barplot(x="Distance_Segment", y="Delivery_Time_min", data=avg_by_segment, ax=ax2) ax2.set_xlabel("Distance Segment") ax2.set_ylabel("Average Delivery Time (min)") ax2.set_title("Avg Delivery Time by Distance Segment") st.pyplot(fig2) st.markdown("### ๐Ÿ“Œ How does distance relate to delivery time?") fig, ax = plt.subplots() sns.scatterplot(data=df_viz, x="Distance_km", y="Delivery_Time_min", hue="Traffic_Level", ax=ax) ax.set_title("Delivery Time vs. Distance colored by Traffic Level") st.pyplot(fig) st.markdown("### ๐Ÿ“‰ Correlation Heatmap") df_numeric = df_viz.select_dtypes(include=np.number) fig3, ax3 = plt.subplots() sns.heatmap(df_numeric.corr(), annot=True, fmt=".2f", cmap="coolwarm", ax=ax3) st.pyplot(fig3) # Page 3: Prediction elif page == "Prediction ๐Ÿ”ฎ": with st.container(): mlflow.set_tracking_uri("file:///tmp/mlruns") st.title("๐Ÿ”ฎ Predicting Delivery Time") st.markdown(""" Use different models to predict delivery time and compare their performance. """) # Handle missing values df_model = df.dropna().copy() # Encode categoricals le_weather = LabelEncoder() le_traffic = LabelEncoder() le_time = LabelEncoder() le_vehicle = LabelEncoder() df_model["Weather"] = le_weather.fit_transform(df_model["Weather"]) df_model["Traffic_Level"] = le_traffic.fit_transform(df_model["Traffic_Level"]) df_model["Time_of_Day"] = le_time.fit_transform(df_model["Time_of_Day"]) df_model["Vehicle_Type"] = le_vehicle.fit_transform(df_model["Vehicle_Type"]) features = ["Distance_km", "Weather", "Traffic_Level", "Time_of_Day", "Vehicle_Type", "Preparation_Time_min", "Courier_Experience_yrs"] target = "Delivery_Time_min" X = df_model[features] y = df_model[target] X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) model_choice = st.selectbox("Choose your model", ["Linear Regression", "Decision Tree", "K-Nearest Neighbors"]) with mlflow.start_run(): if model_choice == "Linear Regression": model = LinearRegression() model.fit(X_train, y_train) predictions = model.predict(X_test) st.subheader("๐Ÿ“ˆ Model Performance") st.write(f"**MAE**: {mean_absolute_error(y_test, predictions):.2f}") st.write(f"**MSE**: {mean_squared_error(y_test, predictions):.2f}") st.write(f"**Rยฒ Score**: {r2_score(y_test, predictions):.3f}") fig, ax = plt.subplots() ax.scatter(y_test, predictions, alpha=0.5) ax.plot([y.min(), y.max()], [y.min(), y.max()], 'r--') ax.set_xlabel("Actual Delivery Time") ax.set_ylabel("Predicted Delivery Time") ax.set_title("Actual vs Predicted Delivery Time") st.pyplot(fig) st.subheader("๐Ÿ“Œ Key Insights") st.markdown(""" - **Feature Impact:** Distance, Traffic Level, and Preparation Time were the most influential features in predicting delivery time. - **Model Fit:** The model achieves an Rยฒ score of ~0.77, indicating decent predictive power, but improvements are possible. - **Real-World Use:** Businesses can use this model to estimate delivery ETAs and improve customer satisfaction. More complex models or live traffic inputs could enhance future predictions. """) elif model_choice == "Decision Tree": # Classification setup df_model["FastDelivery"] = (df_model["Delivery_Time_min"] <= 30).astype(int) target = "FastDelivery" X = df_model[features] y = df_model[target] X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) # UI for depth max_depth = st.number_input("Enter the maximum depth of the decision tree", 1, 20, value=5) model = DecisionTreeClassifier(max_depth=max_depth, random_state=42) model.fit(X_train, y_train) preds = model.predict(X_test) # Metrics f1 = f1_score(y_test, preds) acc = accuracy_score(y_test, preds) precision = precision_score(y_test, preds) # Show metrics st.subheader("๐Ÿงฎ Decision Tree Prediction Metrics") col1, col2, col3 = st.columns(3) col1.metric("Decision Tree' f1-Score", f"{f1*100:.1f}%", "vs last run") col2.metric("Accuracy", f"{acc*100:.1f}%", "vs last run") col3.metric("Precision", f"{precision*100:.1f}%", "vs last run") # Visualization st.subheader("๐ŸŒณ Decision Tree Visualization") fig_tree, ax_tree = plt.subplots(figsize=(20, 10)) plot_tree(model, feature_names=features, class_names=["Slow", "Fast"], filled=True, rounded=True, fontsize=10) st.pyplot(fig_tree) elif model_choice == "K-Nearest Neighbors": from sklearn.neighbors import KNeighborsClassifier from sklearn.metrics import accuracy_score import seaborn as sns # Optional: allow user to choose features all_features = ["Distance_km", "Weather", "Traffic_Level", "Time_of_Day", "Vehicle_Type", "Preparation_Time_min", "Courier_Experience_yrs"] selected_features = st.multiselect("Select features for KNN", all_features, default=all_features) if len(selected_features) == 0: st.warning("Please select at least one feature.") else: X = df_model[selected_features] y = (df_model["Delivery_Time_min"] <= 30).astype(int) X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) # Try different k values accuracies = [] k_range = range(1, 21) best_k = 1 best_acc = 0 best_model = None for k in k_range: knn = KNeighborsClassifier(n_neighbors=k) knn.fit(X_train, y_train) preds = knn.predict(X_test) acc = accuracy_score(y_test, preds) accuracies.append(acc) if acc > best_acc: best_k = k best_acc = acc best_model = knn st.markdown(f"โœ… Best value of k: **{best_k}**") st.markdown(f"๐Ÿ“ˆ Best accuracy: **{best_acc:.2%}**") # Plot K vs Accuracy fig, ax = plt.subplots() sns.lineplot(x=list(k_range), y=accuracies, marker="o", ax=ax) ax.set_title("K Number ร— Accuracy") ax.set_xlabel("K") ax.set_ylabel("Accuracy") st.pyplot(fig) # Page 4: Explainability elif page == "Explainability ๐Ÿค”": with st.container(): st.title("๐Ÿค” Model Explainability with SHAP") df_model = df.dropna().copy() df_model["Weather"] = LabelEncoder().fit_transform(df_model["Weather"]) df_model["Traffic_Level"] = LabelEncoder().fit_transform(df_model["Traffic_Level"]) df_model["Time_of_Day"] = LabelEncoder().fit_transform(df_model["Time_of_Day"]) df_model["Vehicle_Type"] = LabelEncoder().fit_transform(df_model["Vehicle_Type"]) features = ["Distance_km", "Weather", "Traffic_Level", "Time_of_Day", "Vehicle_Type", "Preparation_Time_min", "Courier_Experience_yrs"] target = "Delivery_Time_min" X = df_model[features] y = df_model[target] model = RandomForestRegressor(n_estimators=100, max_depth=7, random_state=42) model.fit(X, y) explainer = shap.Explainer(model, X) shap_values = explainer(X) st.subheader("๐ŸŒ Global Feature Importance") fig, ax = plt.subplots() shap.plots.bar(shap_values, max_display=7, show=False) st.pyplot(fig) st.subheader("๐Ÿ“Š SHAP Summary Plot") fig2, ax2 = plt.subplots() shap.summary_plot(shap_values, X, show=False) st.pyplot(fig2) st.subheader("๐Ÿ” Explain Single Prediction") instance = st.slider("Pick a row to explain", 0, len(X)-1, 0) fig3, ax3 = plt.subplots() shap.plots.waterfall(shap_values[instance], show=False) st.pyplot(fig3) elif page == "Model Tracker ๐Ÿ“Š": with st.container(): st.title("๐Ÿ“Š Model Tracker with DagsHub + MLflow") st.markdown("This page shows all logged experiments and highlights your best model based on MAE.") # ๐Ÿ”ง Set MLflow URI (DagsHub) mlflow.set_tracking_uri("https://dagshub.com/zy2869/my-first-repo.mlflow") client = MlflowClient() # ๐Ÿ” Show all experiments so user knows what's available experiments = mlflow.search_experiments() experiment_names = [exp.name for exp in experiments] selected_exp_name = st.selectbox("Choose experiment", experiment_names) selected_exp = client.get_experiment_by_name(selected_exp_name) runs = client.search_runs(experiment_ids=[selected_exp.experiment_id], order_by=["metrics.MAE ASC"]) # ๐Ÿ“Š Create table data = [] for r in runs: data.append({ "Run ID": r.info.run_id, "Model": r.data.tags.get("mlflow.runName", "Unnamed"), "MAE": r.data.metrics.get("MAE", None), "MSE": r.data.metrics.get("MSE", None), "MAPE": r.data.metrics.get("MAPE", None), }) df_runs = pd.DataFrame(data) # ๐Ÿ† Show sorted models st.subheader("Top Performing Models (Sorted by MAE)") if not df_runs.empty: st.dataframe(df_runs.sort_values("MAE", na_position='last').reset_index(drop=True)) else: st.warning("No runs with MAE metric found in this experiment.") elif page == "Conclusion ๐Ÿ“Œ": with st.container(): st.title("๐Ÿ“Œ Conclusion and Insights") st.subheader("๐Ÿ” Delivery Strategy Recommendations Based on Our Analysis") st.markdown(""" **Based on our overall analysis**, we found that delivery time is most strongly influenced by a few key operational features: **distance**, **preparation time**, and **traffic level**. These factors consistently showed high predictive value across models and SHAP explanations. ๐Ÿ“ For instance, our SHAP analysis confirmed that **Distance (km)** had the highest impact on delivery time predictions, while **Preparation Time** also played a major role. When these two were both high, delivery times significantly increased. ๐Ÿ๏ธ Among the different vehicle types, **bikes** were the most frequently used (51%), but they also had more variation in delivery speed depending on other conditions like traffic. ๐Ÿ“ˆ As distance increases, average delivery time predictably risesโ€”a trend confirmed by both bar charts and regression models. """) st.subheader("๐Ÿง  Key Learnings from Model Comparison") st.markdown(""" - **Linear Regression** offered a strong baseline with an Rยฒ of **0.775**. - **Decision Trees** gave better interpretability with strong accuracy (~91.5%) but a lower F1-score. - **K-Nearest Neighbors (KNN)** with selected features reached **96.05% accuracy**. ๐Ÿ” Our model tracker (with MLflow + DagsHub) revealed that **Huber Regressor** performed best in terms of MAE, making it a great option when minimizing large errors. """) st.subheader("๐Ÿšš Real-World Use Case") st.markdown(""" These results suggest that food delivery platforms could: - โœ… Use real-time **distance and traffic** data to adjust estimated delivery windows. - โœ… Improve ETAs by accounting for **preparation time** at the vendor. - โœ… Recommend **vehicle-type optimizations** during peak or off-peak hours. This could lead to improved customer satisfaction, fewer complaints, and better delivery routing decisions. """) st.subheader("๐Ÿ”ง Future Improvements?") st.markdown(""" 1. **Live Traffic API Integration**: Use real-time traffic feeds (e.g., Google Maps API) for more dynamic predictions. 2. **User Behavior Modeling**: Include customer behavior (e.g., reorder rate, tip likelihood) to improve prioritization. 3. **Expand Dataset**: Include orders from multiple cities to improve generalization across delivery environments. """) elif page == "What-If Simulator ๐Ÿ”": with st.container(): st.title("๐Ÿ” What-If Simulator") st.markdown("### Adjust inputs to simulate delivery time!") df_model = df.dropna().copy() df_model["Weather"] = LabelEncoder().fit_transform(df_model["Weather"]) df_model["Traffic_Level"] = LabelEncoder().fit_transform(df_model["Traffic_Level"]) df_model["Time_of_Day"] = LabelEncoder().fit_transform(df_model["Time_of_Day"]) df_model["Vehicle_Type"] = LabelEncoder().fit_transform(df_model["Vehicle_Type"]) features = ["Distance_km", "Weather", "Traffic_Level", "Time_of_Day", "Vehicle_Type", "Preparation_Time_min", "Courier_Experience_yrs"] # Train simple model X = df_model[features] y = df_model["Delivery_Time_min"] model = RandomForestRegressor(n_estimators=100, max_depth=7, random_state=42) model.fit(X, y) # Input widgets st.markdown("#### Input Simulation Variables") col1, col2 = st.columns(2) with col1: distance = st.slider("Distance (km)", 0.5, 25.0, 5.0) prep_time = st.slider("Preparation Time (min)", 5, 40, 15) experience = st.slider("Courier Experience (yrs)", 0, 10, 2) with col2: weather = st.selectbox("Weather", df["Weather"].unique()) traffic = st.selectbox("Traffic Level", df["Traffic_Level"].unique()) time_of_day = st.selectbox("Time of Day", df["Time_of_Day"].unique()) vehicle = st.selectbox("Vehicle Type", df["Vehicle_Type"].unique()) # Encoding user input input_data = pd.DataFrame({ "Distance_km": [distance], "Weather": [LabelEncoder().fit(df["Weather"]).transform([weather])[0]], "Traffic_Level": [LabelEncoder().fit(df["Traffic_Level"]).transform([traffic])[0]], "Time_of_Day": [LabelEncoder().fit(df["Time_of_Day"]).transform([time_of_day])[0]], "Vehicle_Type": [LabelEncoder().fit(df["Vehicle_Type"]).transform([vehicle])[0]], "Preparation_Time_min": [prep_time], "Courier_Experience_yrs": [experience] }) prediction = model.predict(input_data)[0] st.success(f"๐Ÿ“ฆ Estimated Delivery Time: {prediction:.2f} minutes") st.caption("โšก Tip: Try extreme values to simulate peak vs. off-peak hours!")