GitHub Actions
Deploy to Hugging Face Space
59ebe6e
Raw
History Blame Contribute Delete
8.67 kB
import numpy as np
import pandas as pd
import plotly.express as px
import streamlit as st
TARGET_COLUMN = "Reached.on.Time_Y.N"
TARGET_LABELS = {1: "On Time", 0: "Late"}
def _label_target(series: pd.Series) -> pd.Series:
return series.map(TARGET_LABELS).fillna("Unknown")
def _get_categorical_columns(data: pd.DataFrame) -> list[str]:
return data.select_dtypes(include=["object"]).columns.tolist()
def _get_numeric_columns(data: pd.DataFrame) -> list[str]:
"""Return only business-relevant numeric features (exclude ID & target)."""
candidate_cols = [
"Customer_care_calls",
"Customer_rating",
"Cost_of_the_Product",
"Prior_purchases",
"Discount_offered",
"Weight_in_gms",
]
return [c for c in candidate_cols if c in data.columns]
def _make_numeric_bins(series: pd.Series, n_bins: int = 5) -> pd.Categorical:
"""Create human-friendly ranges like '0–1000' instead of raw numbers."""
min_v = float(series.min())
max_v = float(series.max())
if min_v == max_v:
return pd.cut(series, bins=[min_v - 1, max_v + 1], labels=[f"{min_v:.0f}"])
bins = np.linspace(min_v, max_v, n_bins + 1)
labels = [f"{bins[i]:.0f}{bins[i+1]:.0f}" for i in range(len(bins) - 1)]
return pd.cut(series, bins=bins, labels=labels, include_lowest=True)
def eda_page(data: pd.DataFrame) -> None:
st.header("Exploratory Data Analysis")
total_shipments = len(data)
on_time_rate = data[TARGET_COLUMN].mean() * 100
median_discount = data["Discount_offered"].median()
median_weight = data["Weight_in_gms"].median()
loyal_share = (data["Prior_purchases"] > 3).mean() * 100
c1, c2, c3, c4 = st.columns(4)
c1.metric("Total shipments", f"{total_shipments:,}")
c2.metric("On-time delivery rate", f"{on_time_rate:.1f}%")
c3.metric("Median discount", f"{median_discount:.1f}%")
c4.metric("Loyal customers (> 3 prior purchases)", f"{loyal_share:.1f}%")
st.caption(
"Quick overview: how many shipments you have, how reliable deliveries are, "
"and how generous you are with discounts and loyal customers."
)
tab_target, tab_category, tab_numeric, tab_segments = st.tabs(
["Delivery status", "By category", "By numeric feature", "Business segments"]
)
with tab_target:
st.subheader("Overall delivery status")
target_series = _label_target(data[TARGET_COLUMN])
target_counts = (
target_series.value_counts().rename_axis("Status").reset_index(name="Count")
)
target_counts["Percentage"] = (
target_counts["Count"] / target_counts["Count"].sum() * 100
)
fig = px.pie(
target_counts,
values="Count",
names="Status",
hole=0.35,
)
fig.update_traces(textposition="inside", textinfo="percent+label")
st.plotly_chart(fig, use_container_width=True)
late_row = target_counts.loc[target_counts["Status"] == "Late"]
if not late_row.empty:
late_pct = late_row["Percentage"].iloc[0]
st.write(
f"**Insight:** about **{late_pct:.1f}%** of all shipments arrive late. "
"This is the high-level reliability of your operation."
)
with tab_category:
st.subheader("How delivery status changes by category")
categorical_columns = _get_categorical_columns(data)
if not categorical_columns:
st.info("This dataset does not contain categorical features.")
else:
selected_cat = st.selectbox(
"Choose a categorical feature",
options=categorical_columns,
)
# Instead of 100% bars, show on-time rate per category
summary = (
data.groupby(selected_cat)
.agg(
on_time_percent=(TARGET_COLUMN, lambda x: x.mean() * 100),
total_shipments=(TARGET_COLUMN, "size"),
)
.reset_index()
)
fig_cat = px.bar(
summary,
x=selected_cat,
y="on_time_percent",
text_auto=".1f",
labels={
"on_time_percent": "On-time delivery rate (%)",
"total_shipments": "Number of shipments",
},
)
st.plotly_chart(fig_cat, use_container_width=True)
st.caption(
"Each bar shows **what percentage of shipments are delivered on time** "
f"for each value of **{selected_cat}**. Lower bars = higher risk segments."
)
st.write("Detailed numbers:")
st.dataframe(
summary.rename(
columns={
"on_time_percent": "On-time rate (%)",
"total_shipments": "Total shipments",
}
),
use_container_width=True,
)
with tab_numeric:
st.subheader("Distribution of numeric features")
numeric_columns = _get_numeric_columns(data)
if not numeric_columns:
st.info("This dataset does not contain numeric features (other than ID/target).")
else:
selected_num = st.selectbox(
"Choose a numeric feature",
options=numeric_columns,
)
# Convert numeric values into simple ranges (bins)
binned = _make_numeric_bins(data[selected_num])
temp = data.copy()
temp["range"] = binned
summary_num = (
temp.groupby("range")
.agg(
on_time_percent=(TARGET_COLUMN, lambda x: x.mean() * 100),
total_shipments=(TARGET_COLUMN, "size"),
)
.reset_index()
.dropna()
)
fig_num = px.bar(
summary_num,
x="range",
y="on_time_percent",
text_auto=".1f",
labels={
"range": f"{selected_num} range",
"on_time_percent": "On-time delivery rate (%)",
},
)
st.plotly_chart(fig_num, use_container_width=True)
st.caption(
f"Bars show how **on-time delivery rate** changes across different ranges of "
f"**{selected_num}**. For example, you can see whether very high values are "
"associated with more late shipments."
)
st.write("Detailed numbers:")
st.dataframe(
summary_num.rename(
columns={
"range": f"{selected_num} range",
"on_time_percent": "On-time rate (%)",
"total_shipments": "Total shipments",
}
),
use_container_width=True,
)
with tab_segments:
st.subheader("Business segments: where do we perform well or poorly?")
grouping_column = st.selectbox(
"Group by",
["Mode_of_Shipment", "Warehouse_block", "Product_importance", "Gender"],
)
summary = (
data.groupby(grouping_column)
.agg(
on_time_percent=(TARGET_COLUMN, lambda x: x.mean() * 100),
avg_cost=("Cost_of_the_Product", "mean"),
avg_discount=("Discount_offered", "mean"),
)
.reset_index()
)
fig_segment = px.bar(
summary,
x=grouping_column,
y="on_time_percent",
text_auto=".1f",
color="avg_discount",
color_continuous_scale="Blues",
labels={
"on_time_percent": "On-time delivery rate (%)",
"avg_discount": "Average discount",
},
)
st.plotly_chart(fig_segment, use_container_width=True)
st.dataframe(
summary.rename(
columns={
"on_time_percent": "On-time rate (%)",
"avg_cost": "Avg product cost",
"avg_discount": "Avg discount",
}
),
use_container_width=True,
)
st.caption(
"Use this view to spot **high-risk segments**: groups with low on-time rate, "
"especially if they also receive high discounts or involve high product cost."
)