KillD00zer's picture
Update app.py
01fbe33 verified
raw
history blame contribute delete
17.4 kB
# %% [markdown]
# # Inventory Management Dashboard and Analysis
# %% [markdown]
# ## <mark>1. Read & describe the data
# %%
# PeakFit_analysis csv data reading
import pandas as pd
import numpy as np
import altair as alt
import panel as pn
import plotly.graph_objects as go
import plotly.express as px
pn.extension('plotly')
ACCENT = "teal"
# %%
df = pd.read_excel('PeakFit Essentials.xlsx')
df.sort_values(by="Date").head(5)
# %% [markdown]
# ## <mark>2. Clean the data
# %%
#drop unusfull columns
df = df.drop(columns=['Expired Items','Returned Items','Items Out for Sales','Items Out for Quality Control','Items Out for Events'])
# %%
df["Date"] = pd.to_datetime(df["Date"], format="%Y-%m-%d")
# %%
#replace expected & actual with stocked quantity
#make column for Inventory Discrepancy
df['Inventory Discrepancy'] = df['Actual Quantity'] - df['Expected Quantity']
df = df.drop(columns=['Expected Quantity', 'Actual Quantity' ])
print(df['Inventory Discrepancy'].sum()) #total Inventory Discrepancy in 10 monthes [ -N : missing , +N more than expeted ]
df.head()
# %%
df['Month'] = df['Date'].dt.month_name()
# %%
print(df['Inventory Discrepancy'].sum())
# %% [markdown]
# ### the new data discription
# %%
df.info()
# %%
df.describe(include='all')
# %% [markdown]
# Date started 2024-01-01 ended 2024-09-30 with 2000 record .
#
# time 3 shifts per day (morning, afternoon, evening).
#
# item ID are only 6 items (Dumbbells, Yoga Mat, Resistance Bands, Protein Powder, Foam Roller, Kettlebells).
#
# category are only 3 items , but 4 in category sheet (Strength Training Equipment, fitness accessories, strength training equipment). there is an issue (miss leading inputs)
#
# responsible staff are only 6 (['Robert Tabone', 'Simon Fenech', 'Jean-Pierre Ellul','Andrew Cauchi', 'Aaron Vella', 'Franklin Attard']).
#
# and no nulls in the data
#
#
# %%
# fixing category
# assinge ITM001 , ITM003 , ITM006 to ctg001 as they all strength training Items
df.loc[df['Item ID'].isin(['ITM001', 'ITM003', 'ITM006']), 'Category'] = 'CTG001'
# assinge ITM004 as CTG004 ( protein poweder is supplements )
df.loc[df["Item ID"].isin(["ITM004"]), "Category"]= "CTG003"
#assinge ITM005 as CTG005 (foam rollers are recovery tools)
df.loc[df["Item ID"].isin(["ITM005"]), "Category"]="CTG004"
df['Category'].unique()
# %% [markdown]
# ## <mark>3. Data proccessing
# %% [markdown]
# #### calender heatmap (Heat_fig) ####
# %%
# Convert DateTime to datetime format
df['DateTime'] = pd.to_datetime(df['Date'])
# Extract Month and Day
df['Month'] = df['DateTime'].dt.strftime('%B') # Month name
df['Day'] = df['DateTime'].dt.day # Day of month
# Aggregate data by Day and Month (sum of Inventory Discrepancy)
df_agg = df.groupby(['Day', 'Month'])['Inventory Discrepancy'].sum().reset_index()
# Create pivot table for heatmap
heatmap_data = df_agg.pivot(index='Day', columns='Month', values='Inventory Discrepancy')
# Fill NaN values with 0 (days with no Inventory Discrepancy)
heatmap_data = heatmap_data.fillna(0)
# Define month order for proper sorting
month_order = ["January", "February", "March", "April", "May", "June",
"July", "August", "September", "October"]
# Filter to only include months that are in our data
available_months = [month for month in month_order if month in heatmap_data.columns]
heatmap_data = heatmap_data.reindex(columns=available_months)
df_agg.head()
# %%
# Create the heatmap
Heat_fig = px.imshow(
heatmap_data.values,
labels=dict(x="Month", y="Day", color="Inventory Discrepancy"),
x=heatmap_data.columns,
y=heatmap_data.index,
color_continuous_scale="RdBu", # Changed to Reds as Inventory Discrepancy are positive now
text_auto=True # Display values in the cells
)
Heat_fig.update_layout(
title="Inventory Discrepancy/day Heatmap",
xaxis_title="Month",
yaxis_title="Day of Month",
yaxis=dict(
range=[1, 31], # Explicitly reverse the order (assuming max day is 31)
autorange=False # Disable automatic range adjustment
), # Keep day labels in correct order
width=700, # Set width
height=600, # Set height
plot_bgcolor="black", # Set plot background to black
paper_bgcolor="white", # Set entire figure background to black
font=dict(color="black") # Set text color to white for contrast
)
# To display the figure
Heat_fig
# %% [markdown]
# #made a calnder as heatmap, and found :
#
# at (25/07) 31 unexpected item found
#
# at (30/05) & 03/08 highest missing record at -23 item
# %% [markdown]
# #### by weekday (Bar_fig)
# %%
# Extract weekday name
df["Weekday"] = df["Date"].dt.day_name()
# Group by weekday and sum Inventory Discrepancy
df_grouped = df.groupby("Weekday")["Inventory Discrepancy"].sum().reset_index()
# Define weekday order
weekday_order = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
# Create a category type with our custom order
df_grouped["Weekday"] = pd.Categorical(df_grouped["Weekday"], categories=weekday_order, ordered=True)
# Sort by our ordered category
df_grouped = df_grouped.sort_values("Weekday")
# # Create color sequence to match your original
# colors = ["skyblue", "orange", "green", "red", "purple", "pink", "brown"]
df.head()
# %%
# Create the bar chart
Bar_fig = px.bar(
df_grouped,
x="Weekday",
y="Inventory Discrepancy",
title="Inventory Discrepancy by Day of the Week",
color="Inventory Discrepancy", # Color by value
color_continuous_scale=['red', 'blue'], # Red for negative, blue for positive
labels={"Weekday": "Day of the Week", "Inventory Discrepancy": "Total Inventory Discrepancy"}
)
# Update layout for dark background
Bar_fig.update_layout(
plot_bgcolor="black", # Set plot background to black
paper_bgcolor="black", # Set entire figure background to black
font=dict(color="white") # Set text color to white for contrast
)
# Add grid lines
Bar_fig.update_yaxes(showgrid=True, gridwidth=1, gridcolor='rgba(255,255,255,0.2)')
component0 = pn.panel(Bar_fig)
# Display the figure
Bar_fig
# %% [markdown]
# allover the week tuesdays were for losing items . about 94 item were missed
# %% [markdown]
# ### staff sum of recorded stocks allover the year (Bar2_fig)
# %%
#Inventory Discrepancy per staff
df_miss_perstaff=df.groupby("Responsible Staff").agg({
'Inventory Discrepancy': 'sum'
})
df_miss_perstaff.head(10)
# %% [markdown]
# franklin & jean were responsible for most missing recordes ( 39 , 11)
# %% [markdown]
# ### shifts and responsible staff (Line_fig)
# %%
#staff per shift
df_staff_shifts = df.groupby(["Responsible Staff", "Time"]).size().unstack(fill_value=0)
df_staff_shifts["Total Shifts"] = df_staff_shifts.sum(axis=1)
df_staff_shifts.head(10)
# %%
# Data
staff = ["Aaron Vella", "Andrew Cauchi", "Franklin Attard", "Jean-Pierre Ellul", "Robert Tabone", "Simon Fenech"]
shifts = ["Morning", "Afternoon", "Evening"]
morning = [104, 96, 106, 122, 123, 128]
afternoon = [103, 110, 115, 106, 106, 104]
evening = [107, 106, 98, 140, 124, 102]
# Define custom colors for each staff member
colors = ['blue', 'red', 'green', 'purple', 'orange', 'brown']
# Create figure with specified name
Line_fig = go.Figure()
# Add traces for each staff member with specific colors
for i, staff_name in enumerate(staff):
y_values = [morning[i], afternoon[i], evening[i]]
Line_fig.add_trace(go.Scatter(
x=shifts,
y=y_values,
mode='lines+markers',
name=staff_name,
line=dict(color=colors[i], width=2),
marker=dict(color=colors[i], size=8),
hovertemplate='<b>%{x} Shift</b><br>' +
f'{staff_name}: ' + '%{y}<extra></extra>'
))
# Update layout
Line_fig.update_layout(
title="Shift Distribution Across Staff Members",
xaxis_title="Shifts",
yaxis_title="Shift Distribution",
legend_title="Staff Members",
height=600,
width=800,
hovermode="closest"
)
# Add grid lines for better readability
Line_fig.update_xaxes(showgrid=True, gridwidth=1, gridcolor='rgba(0,0,0,0.1)')
Line_fig.update_yaxes(showgrid=True, gridwidth=1, gridcolor='rgba(0,0,0,0.1)')
# Show the figure
Line_fig
# %% [markdown]
# ### final stocks of Items
# %%
df_stocks = df.groupby(["Item ID", "Category"])["Inventory Discrepancy"].agg(sum)
pd.DataFrame(df_stocks).head(10)
# %% [markdown]
# ITM002 & ITM005 are the missed itmes : (Yoga Mat: 71 missing , foam rollers: 42 missing)
# %% [markdown]
# ## the interactive bar chart (fig)
# %%
#make a df to customize
activ_df = pd.DataFrame(df[["Time", "Month","Responsible Staff","Category","Inventory Discrepancy"]])
activ_df.head()
# %%
# Create a vertical radio button group
list_dropdown = pn.widgets.RadioButtonGroup(
name="Select Data on X axis",
options=["Responsible Staff", "Date", "Month", "Time", "Category", "Item ID" , "Weekday"],
description="select data type to calculate stock for!",
button_style='outline'
)
# Wrap in a Panel component (optional, but can help with layout)
component1 = pn.panel(list_dropdown)
list_dropdown
# %%
@pn.depends(list_dropdown)
def inter_bar(list_dropdown):
# Aggregate data for plotting
grouped_df = df.groupby(list_dropdown)['Inventory Discrepancy'].sum().reset_index()
# Ensure 'Month' column is ordered correctly if the dropdown is "Month"
if list_dropdown == "Month":
grouped_df["Month"] = pd.Categorical(grouped_df["Month"], categories=month_order, ordered=True)
grouped_df = grouped_df.sort_values("Month")
# Set title dynamically
tit = "Missing/overstock by item ID" if list_dropdown == "item ID" else f'Inventory Discrepancy by {list_dropdown}'
# Create the figure using Plotly Express
fig = px.bar(
grouped_df,
x=list_dropdown,
y="Inventory Discrepancy",
title=tit,
color="Inventory Discrepancy", # Color by category
color_continuous_scale=['red', ACCENT], # Adjust color scale
)
# Improve layout
fig.update_layout(
xaxis_title=list_dropdown,
yaxis_title="Total Inventory Discrepancy",
plot_bgcolor="#f8f8f8",
paper_bgcolor="#f8f8f8",
font=dict(color="black"),
xaxis=dict(gridcolor='lightgray', showgrid=True),
yaxis=dict(gridcolor='black'),
width=1200,
height=500,
)
return fig
component2 = pn.panel(inter_bar) # Assign to Panel
inter_bar("Month") # Show ordered months
# %% [markdown]
# ## creating KPIs cards
# %% [markdown]
# total actual stock= 94489
#
# total expected stock= 94447
#
# total overstock= 42 (.044% of the stock)
#
# ITM002(Yoga Mat) lost 71 pieces , ITM005(Foam Roller) lost 42 piece, totlal= 113
#
# the over stock items (ITM001: 16 , ITM003: 35 , ITM004: 35 , ITM006: 69), total= 155
# %%
ACCENT = "teal"
styles = {
"box-shadow": "rgba(50, 50, 93, 0.25) 0px 6px 12px -2px, rgba(0, 0, 0, 0.3) 0px 3px 7px -3px",
"border-radius": "10px",
"padding": "10px",
"background": "#DCEFF0",
'box-shadow': "3px 3px 6px rgba(0,0,0,0.3)",
"width": "385px",
"height": "220px"
} # a dict to use later in the cards
# %%
# KPIs cards
cards= pn.FlexBox(
#'Highest miss/day
pn.indicators.Number(name='The worst-performing day <br> (03.Aug & 30.May)📉',
value=23,
format='<span style="font-size:1.5em;">{value}</span>'
'<span style="font-size:0.7em;"> items</span>',
colors=[(1, 'black')],
styles=styles,
),
pn.indicators.Number(
name='Franklin Attard <br> the biggest Loser',
value=39,
# Embed HTML to style the units separately:
format='<span style="font-size:1.5em;">{value}</span>'
'<span style="font-size:0.7em;"> items</span>',
colors=[(0, ACCENT)]
,styles=styles
),
pn.indicators.Number(
name='Morning shift had the <br> hieghest missing rate',
value=84,
# Embed HTML to style the units separately:
format='<span style="font-size:1.5em;">{value}</span>'
'<span style="font-size:0.7em;"> items</span>',
colors=[(0, ACCENT)]
,styles=styles
)
)
# %%
cards
# %% [markdown]
# ### making cards of stock discrepancy
# %%
# Data for missing and overstocked items
missing_items = {
"Yoga Mat": 71,
"Foam Roller": 42
}
overstock_items = {
"Dumbbells": 16,
"Resistance Band": 35,
"Jump Rope": 35,
"Kettlebell": 69
}
# Convert to DataFrame
df_missing = pd.DataFrame(missing_items.items(), columns=["Item", "Quantity Lost"])
df_overstock = pd.DataFrame(overstock_items.items(), columns=["Item", "Quantity Overstocked"])
# %%
# Create bar plots
fig_missing = px.bar(
df_missing,
x="Item",
y="Quantity Lost",
title="Missing Items(max:71, sum:113)",
color="Quantity Lost",
color_continuous_scale=[(0, "lightcoral"), (0.5, "red"), (1, "darkred")]
)
fig_overstock = px.bar(
df_overstock,
x="Item",
y="Quantity Overstocked",
title="Overstocked Items(max:69, sum:155)",
color="Quantity Overstocked",
color_continuous_scale="blues"
)
# Improve layout
fig_missing.update_layout(paper_bgcolor="#DCEFF0", plot_bgcolor="#DCEFF0", width=295, height=295, coloraxis_showscale=False,title_font=dict(size=16))
fig_overstock.update_layout(paper_bgcolor="#DCEFF0", plot_bgcolor="#DCEFF0", width=295, height=295,coloraxis_showscale=False,title_font=dict(size=14) )
component3=pn.panel(fig_missing)
component4=pn.panel(fig_overstock)
# %%
# Convert to DataFrame
df_missing = pd.DataFrame(missing_items.items(), columns=["Item", "Quantity Lost"])
df_overstock = pd.DataFrame(overstock_items.items(), columns=["Item", "Quantity Overstocked"])
# Create bar plots
fig_missing = px.bar(
df_missing,
x="Item",
y="Quantity Lost",
title="Missing Items(max:71, sum:113)",
color="Quantity Lost",
color_continuous_scale=[(0, "lightcoral"), (0.5, "red"), (1, "darkred")]
)
missing_items_indicator = pn.indicators.Number(
name="Missing Items", # Title
value=113, # Number displayed
format="{:.0f}", # No decimal places
colors=[(50, "red")], # Customize color if needed
font_size="20pt" # Adjust font size
)
fig_overstock = px.bar(
df_overstock,
x="Item",
y="Quantity Overstocked",
title="Overstocked Items(max:69, sum:155)",
color="Quantity Overstocked",
color_continuous_scale="blues"
)
# Improve layout
fig_missing.update_layout(
paper_bgcolor="#DCEFF0",
plot_bgcolor="#DCEFF0",
width=600,
height=295,
coloraxis_showscale=False,
title_font=dict(size=28, color="#8B0000") # Set title color to red
)
fig_overstock.update_layout(
paper_bgcolor="#DCEFF0",
plot_bgcolor="#DCEFF0",
width=600,
height=295,
coloraxis_showscale=False,
title_font=dict(size=28, color="teal") # Set title color to blue
)
component3=pn.panel(fig_missing)
component4=pn.panel(fig_overstock)
comp5=pn.panel(missing_items_indicator)
# %% [markdown]
# ## Dashboard layout
# %%
styles2 = {
"box-shadow": "rgba(50, 50, 93, 0.25) 0px 6px 12px -2px, rgba(0, 0, 0, 0.3) 0px 3px 7px -3px",
"border-radius": "10px",
"padding": "10px",
"background": "#DADADA",
'box-shadow': "3px 3px 6px rgba(0,0,0,0.3)",
"width": "280px",
"height": "80px"
}
sidebar_card1 = pn.indicators.Number(
name=("Total records: 2000"),
styles=styles2
)
sidebar_card2 = pn.indicators.Number(
name=("Start date: 01-01-2024 To 30-09-2024"),
styles=styles2
)
insight1 = pn.indicators.Number(
name="More managment on Morinig shift is essential ",
styles={"background": "#6AB187", "border-radius": "10px", "padding": "10px", "width": "300" , "height":"120px"}
)
insight2 = pn.indicators.Number(
name="light items must be moved in more watched ereas ",
styles={"background": "#6AB187", "border-radius": "10px", "padding": "10px", "width": "300" , "height":"120px"}
)
insight3 = pn.indicators.Number(
name="need better rocording system to overcome the overstocking issue",
styles={"background": "#6AB187", "border-radius": "10px", "padding": "10px", "width": "300" , "height":"120px"}
)
# %%
import panel as pn
# Create the template
template = pn.template.EditableTemplate(
title="PeakFit Inventory Analysis Interactive Dashboard",
theme="default",
header_background="#1C4E80", # Header background color
sidebar=[
pn.pane.Markdown("Inventory Infos & Insights"), # Sidebar title
pn.pane.Image(
"images.png",
width=290, # Adjust width as needed
align="center"
),
sidebar_card1,
sidebar_card2,
insight1,
insight2,
insight3
],
sidebar_width=320, # Adjust sidebar width
)
# Add main content
template.main.append(
pn.Column(
# Row 1: Charts
pn.Row(component3, component4, sizing_mode="stretch_width"),
# Row 2: KPI Cards
pn.Row(cards, sizing_mode="stretch_width"),
# Row 3: Stacked Components
pn.Column(
pn.Row(component2, sizing_mode="stretch_width"),
pn.Row(pn.Spacer(width=200), component1, sizing_mode="stretch_width"),
),
)
)
template.servable()