|
import streamlit as st |
|
from src.data import StoreDataLoader |
|
from src.model import Model_Load |
|
import matplotlib.pyplot as plt |
|
import seaborn as sns |
|
import plotly.graph_objects as go |
|
from sklearn.metrics import mean_absolute_error,mean_squared_error |
|
import numpy as np |
|
import pandas as pd |
|
from src.prediction import test_prediction,val_prediction,create_week_date_featues |
|
import plotly.express as px |
|
|
|
|
|
hide_streamlit_style = """ |
|
<style> |
|
#MainMenu {visibility: hidden;} |
|
footer {visibility: hidden;} |
|
</style> |
|
""" |
|
st.markdown(hide_streamlit_style, unsafe_allow_html=True) |
|
|
|
|
|
model_obj=Model_Load() |
|
|
|
@st.cache_data |
|
def convert_df(df): |
|
return df.to_csv(index=False).encode('utf-8') |
|
|
|
|
|
st.markdown(""" |
|
<div style='text-align: center; margin-top:-70px; margin-bottom: -50px;margin-left: -50px;'> |
|
<h2 style='font-size: 20px; font-family: Courier New, monospace; |
|
letter-spacing: 2px; text-decoration: none;'> |
|
<img src="https://acis.affineanalytics.co.in/assets/images/logo_small.png" alt="logo" width="70" height="30"> |
|
<span style='background: linear-gradient(45deg, #ed4965, #c05aaf); |
|
-webkit-background-clip: text; |
|
-webkit-text-fill-color: transparent; |
|
text-shadow: none;'> |
|
Product Demand Forecasting Dashboard |
|
</span> |
|
<span style='font-size: 40%;'> |
|
<sup style='position: relative; top: 5px; color: #ed4965;'>by Affine</sup> |
|
</span> |
|
</h2> |
|
</div> |
|
""", unsafe_allow_html=True) |
|
|
|
|
|
with st.sidebar: |
|
st.markdown("""<div style='text-align: left; margin-top:-200px;margin-left:-40px;'> |
|
<img src="https://affine.ai/wp-content/uploads/2023/05/Affine-Logo.svg" alt="logo" width="300" height="60"> |
|
</div>""", unsafe_allow_html=True) |
|
option=st.selectbox("Select Model",['TFT','Prophet']) |
|
|
|
|
|
if option=='TFT': |
|
|
|
|
|
path='data/train.csv' |
|
obj=StoreDataLoader(path) |
|
train_dataset,test_dataset,training,validation,earliest_time=obj.tft_data() |
|
print(f"TRAINING ::START DATE ::{train_dataset['date'].min()} :: END DATE ::{train_dataset['date'].max()}") |
|
print(f"TESTING ::START DATE ::{test_dataset['date'].min()} :: END DATE ::{test_dataset['date'].max()}") |
|
list_store=train_dataset['store'].unique() |
|
list_items=train_dataset['item'].unique() |
|
|
|
try: |
|
|
|
model=model_obj.store_model_load(option) |
|
with st.sidebar: |
|
|
|
|
|
store=st.selectbox("Select Store ID",list_store) |
|
|
|
item=st.selectbox("Select Product ID",list_items) |
|
|
|
|
|
testing_results=test_prediction(model,train_dataset=train_dataset,test_dataset=test_dataset |
|
,earliest_time=earliest_time,store_id=store,item_id=item) |
|
|
|
rmse=np.around(np.sqrt(mean_squared_error(testing_results['Lead_1'],testing_results['prediction'])),2) |
|
mae=np.around(mean_absolute_error(testing_results['Lead_1'],testing_results['prediction']),2) |
|
print(f"TEST DATA = Item ID : {item} :: MAE : {mae} :: RMSE : {rmse}") |
|
|
|
final_data=pd.concat([train_dataset,test_dataset]) |
|
consumer_data=final_data.loc[(final_data['store']==store) & (final_data['item']==item)] |
|
consumer_data.fillna(0,inplace=True) |
|
date_list=[] |
|
demand_prediction=[] |
|
for i in range(30): |
|
|
|
encoder_data = consumer_data[lambda x: x.days_from_start > x.days_from_start.max() - 150] |
|
last_data = consumer_data[lambda x: x.days_from_start == x.days_from_start.max()] |
|
|
|
|
|
date_list.append(encoder_data.tail(1).iloc[-1,:]['date']) |
|
|
|
test_prediction = model.predict(encoder_data, |
|
mode="prediction", |
|
trainer_kwargs=dict(accelerator="cpu"), |
|
return_x=True) |
|
|
|
decoder_data = pd.concat( |
|
[last_data.assign(date=lambda x: x.date + pd.offsets.DateOffset(i)) for i in range(1, 2)], |
|
ignore_index=True, |
|
) |
|
|
|
decoder_data["hours_from_start"] = (decoder_data["date"] - earliest_time).dt.seconds / 60 / 60 + (decoder_data["date"] - earliest_time).dt.days * 24 |
|
decoder_data['hours_from_start'] = decoder_data['hours_from_start'].astype('int') |
|
decoder_data["hours_from_start"] += encoder_data["hours_from_start"].max() + 1 - decoder_data["hours_from_start"].min() |
|
|
|
decoder_data["days_from_start"] = (decoder_data["date"] - earliest_time).apply(lambda x:x.days) |
|
|
|
decoder_data=create_week_date_featues(decoder_data,'date') |
|
|
|
decoder_data['sales']=float(test_prediction.output[0][-1]) |
|
|
|
demand_prediction.append(float(test_prediction.output[0][-1])) |
|
|
|
decoder_data['time_idx']=int(test_prediction.x['decoder_time_idx'][0][-1]) |
|
|
|
consumer_data=pd.concat([consumer_data,decoder_data]) |
|
|
|
consumer_data['lag_1']=consumer_data['sales'].shift(1) |
|
consumer_data['lag_5']=consumer_data['sales'].shift(5) |
|
|
|
consumer_data=consumer_data.reset_index(drop=True) |
|
|
|
d2=pd.DataFrame({"date":date_list,"prediction":demand_prediction})[['date','prediction']] |
|
|
|
d2['store']=store |
|
d2['item']=item |
|
|
|
with st.sidebar: |
|
st.markdown(f""" |
|
<style> |
|
/* Sidebar header style */ |
|
.sidebar-header {{ |
|
padding: 1px; |
|
background-color: #9966FF; |
|
text-align: center; |
|
font-size: 13px; |
|
font-weight: bold; |
|
color: #FFF ; |
|
}} |
|
</style> |
|
|
|
<div class="sidebar-header"> |
|
Models Evalution |
|
</div> |
|
""",unsafe_allow_html=True) |
|
st.dataframe(pd.DataFrame({"KPI":['RMSE','MAE'],"TFT":[7.73,6.17],"Prophet":[7.32,6.01]}).set_index('KPI'),width=300) |
|
|
|
|
|
|
|
|
|
|
|
|
|
st.markdown(f""" |
|
<style> |
|
/* Sidebar header style */ |
|
.sidebar-header {{ |
|
padding: 3px; |
|
background-color:linear-gradient(45deg, #ed4965, #c05aaf); |
|
text-align: center; |
|
font-size: 13px; |
|
font-weight: bold; |
|
color: #FFF ; |
|
}} |
|
</style> |
|
|
|
<div class="sidebar-header"> |
|
KPI :: {item} |
|
</div> |
|
""",unsafe_allow_html=True) |
|
st.dataframe(pd.DataFrame({"KPI":['RMSE','MAE'],"TFT":[rmse,mae]}).set_index('KPI'),width=300) |
|
|
|
|
|
|
|
tab1,tab2=st.tabs(['📈Forecast Plot','🗃Forecast Table']) |
|
|
|
tab1.markdown(""" |
|
<div style='text-align: left; margin-top:-10px;margin-bottom:-10px;'> |
|
<h2 style='font-size: 30px; font-family: Palatino, serif; |
|
letter-spacing: 2px; text-decoration: none;'> |
|
📈 |
|
<span style='background: linear-gradient(45deg, #ed4965, #c05aaf); |
|
-webkit-background-clip: text; |
|
-webkit-text-fill-color: transparent; |
|
text-shadow: none;'> |
|
Forecast Plot |
|
</span> |
|
<span style='font-size: 40%;'> |
|
<sup style='position: relative; top: 5px; color: #ed4965;'></sup> |
|
</span> |
|
</h2> |
|
</div> |
|
""", unsafe_allow_html=True) |
|
|
|
testing_results['prediction']=testing_results['prediction'].apply(lambda x:round(x)) |
|
testing_results['date']=testing_results['date'].dt.date |
|
d2['prediction']=d2['prediction'].apply(lambda x:round(x)) |
|
d2['date']=d2['date'].dt.date |
|
|
|
|
|
fig = go.Figure([ |
|
|
|
|
|
go.Scatter(x=testing_results['date'], y=testing_results['Lead_1'],name='Observed',line=dict(color='rgba(218, 112, 214, 0.5)')), |
|
go.Scatter(x=testing_results['date'],y=testing_results['prediction'],name='Historical Forecast',line=dict(color='#9400D3', dash='dash')), |
|
go.Scatter(x=d2['date'],y=d2['prediction'],name='Future Forecast',line=dict(color='Dark Orange', dash='dot'))]) |
|
fig.update_layout( |
|
xaxis_title='Date', |
|
yaxis_title='Order Demand', |
|
margin=dict(l=0, r=0, t=50, b=0), |
|
xaxis=dict(title_font=dict(size=20)), |
|
yaxis=dict(title_font=dict(size=20))) |
|
fig.update_layout(width=700,height=400) |
|
tab1.plotly_chart(fig) |
|
|
|
tab2.markdown(""" |
|
<div style='text-align: left; margin-top:-10px;'> |
|
<h2 style='font-size: 30px; font-family: Palatino, serif; |
|
letter-spacing: 2px; text-decoration: none;'> |
|
📃 |
|
<span style='background: linear-gradient(45deg, #ed4965, #c05aaf); |
|
-webkit-background-clip: text; |
|
-webkit-text-fill-color: transparent; |
|
text-shadow: none;'> |
|
Forecast Table |
|
</span> |
|
<span style='font-size: 40%;'> |
|
<sup style='position: relative; top: 5px; color: #ed4965;'></sup> |
|
</span> |
|
</h2> |
|
</div> |
|
""", unsafe_allow_html=True) |
|
final_r=pd.concat([d2[['date','store','item','prediction']],testing_results[['date','store','item','prediction']]]).sort_values('date').drop_duplicates().reset_index(drop=True) |
|
csv = convert_df(final_r) |
|
tab2.dataframe(final_r,width=500) |
|
tab2.download_button( |
|
"Download", |
|
csv, |
|
"file.csv", |
|
"text/csv", |
|
key='download-csv' |
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
except: |
|
st.sidebar.error('Model Not Loaded successfully!',icon="🚨") |
|
|
|
|
|
|
|
|
|
elif option=='Prophet': |
|
print("prophet") |
|
|
|
|
|
path='data/train.csv' |
|
obj=StoreDataLoader(path) |
|
fb_train_data,fb_test_data,item_dummay,store_dummay=obj.fb_data() |
|
|
|
|
|
|
|
print(f"TRAINING ::START DATE ::{fb_train_data['ds'].min()} :: END DATE ::{fb_train_data['ds'].max()}") |
|
print(f"TESTING ::START DATE ::{fb_test_data['ds'].min()} :: END DATE ::{fb_test_data['ds'].max()}") |
|
train_new=fb_train_data.drop('y',axis=1) |
|
test_new=fb_test_data.drop('y',axis=1) |
|
|
|
try: |
|
fb_model=model_obj.store_model_load(option) |
|
|
|
|
|
|
|
list_items=item_dummay.columns |
|
list_store=store_dummay.columns |
|
with st.sidebar: |
|
store=st.selectbox("Select Store",list_store) |
|
item=st.selectbox("Select Product",list_items) |
|
|
|
test_prediction=fb_model.predict(test_new.loc[test_new[item]==1]) |
|
train_prediction=fb_model.predict(train_new.loc[train_new[item]==1]) |
|
|
|
y_true_test=fb_test_data.loc[fb_test_data[item]==1] |
|
y_true_train=fb_train_data.loc[fb_train_data[item]==1] |
|
|
|
y_train_pred=train_prediction[['ds','yhat']].iloc[-60:,:] |
|
y_train_true=y_true_train[['ds','y']].iloc[-60:,:] |
|
|
|
y_test_pred=test_prediction[['ds','yhat']] |
|
y_test_true=y_true_test[['ds','y']] |
|
|
|
rmse=np.sqrt(mean_squared_error(y_test_true['y'],y_test_pred['yhat'])) |
|
mae=mean_absolute_error(y_test_true['y'],y_test_pred['yhat']) |
|
|
|
fb_final=pd.concat([fb_train_data,fb_test_data]) |
|
|
|
fb_consumer=fb_final.loc[(fb_final[store]==1) & (fb_final[item]==1)] |
|
|
|
|
|
date_list=[] |
|
prediction_list=[] |
|
|
|
|
|
for i in range(30): |
|
|
|
next_prediction=fb_consumer.tail(1).drop('y',axis=1) |
|
|
|
prediction=fb_model.predict(next_prediction) |
|
|
|
|
|
date_list.append(prediction['ds'][0]) |
|
prediction_list.append(prediction['yhat'][0]) |
|
|
|
|
|
|
|
last_data = fb_consumer[lambda x: x.ds == x.ds.max()] |
|
|
|
decoder_data = pd.concat( |
|
[last_data.assign(ds=lambda x: x.ds + pd.offsets.DateOffset(i)) for i in range(1, 2)], |
|
ignore_index=True, |
|
) |
|
|
|
decoder_data=create_week_date_featues(decoder_data,'ds') |
|
|
|
decoder_data['sales']=prediction['yhat'][0] |
|
|
|
fb_consumer=pd.concat([fb_consumer,decoder_data]) |
|
|
|
fb_consumer['lag_1']=fb_consumer['sales'].shift(1) |
|
fb_consumer['lag_5']=fb_consumer['sales'].shift(5) |
|
fb_consumer=fb_consumer.reset_index(drop=True) |
|
future_prediction=pd.DataFrame({"ds":date_list,"yhat":prediction_list}) |
|
future_prediction['store']=store |
|
future_prediction['item']=item |
|
|
|
with st.sidebar: |
|
st.markdown(f""" |
|
<style> |
|
/* Sidebar header style */ |
|
.sidebar-header {{ |
|
padding: 1px; |
|
background-color: #9966FF; |
|
text-align: center; |
|
font-size: 13px; |
|
font-weight: bold; |
|
color: #FFF ; |
|
}} |
|
</style> |
|
|
|
<div class="sidebar-header"> |
|
Models Evalution |
|
</div> |
|
""",unsafe_allow_html=True) |
|
st.dataframe(pd.DataFrame({"KPI":['RMSE','MAE'],"TFT":[7.73,6.17],"Prophet":[7.32,6.01]}).set_index('KPI'),width=300) |
|
st.markdown(f""" |
|
<style> |
|
/* Sidebar header style */ |
|
.sidebar-header {{ |
|
padding: 3px; |
|
background-color:linear-gradient(45deg, #ed4965, #c05aaf); |
|
text-align: center; |
|
font-size: 13px; |
|
font-weight: bold; |
|
color: #FFF ; |
|
}} |
|
</style> |
|
|
|
<div class="sidebar-header"> |
|
KPI :: {item} |
|
</div> |
|
""",unsafe_allow_html=True) |
|
|
|
st.dataframe(pd.DataFrame({"KPI":['RMSE','MAE'],"Prophet":[rmse,mae]}).set_index('KPI'),width=300) |
|
|
|
|
|
tab1,tab2=st.tabs(['📈Forecast Plot','🗃Forecast Table']) |
|
|
|
tab1.markdown(""" |
|
<div style='text-align: left; margin-top:-10px;margin-bottom:-10px;'> |
|
<h2 style='font-size: 30px; font-family: Palatino, serif; |
|
letter-spacing: 2px; text-decoration: none;'> |
|
📈 |
|
<span style='background: linear-gradient(45deg, #ed4965, #c05aaf); |
|
-webkit-background-clip: text; |
|
-webkit-text-fill-color: transparent; |
|
text-shadow: none;'> |
|
Forecast Plot |
|
</span> |
|
<span style='font-size: 40%;'> |
|
<sup style='position: relative; top: 5px; color: #ed4965;'></sup> |
|
</span> |
|
</h2> |
|
</div> |
|
""", unsafe_allow_html=True) |
|
|
|
|
|
y_train_true['y']=y_train_true['y'].astype('int') |
|
y_train_pred['yhat']=y_train_pred['yhat'].astype('int') |
|
y_test_true['y']=y_test_true['y'].astype('int') |
|
y_test_pred['yhat']=y_test_pred['yhat'].astype('int') |
|
future_prediction['yhat']=future_prediction['yhat'].astype('int') |
|
y_train_true['ds']=y_train_true['ds'].dt.date |
|
y_train_pred['ds']=y_train_pred['ds'].dt.date |
|
y_test_true['ds']=y_test_true['ds'].dt.date |
|
y_test_pred['ds']=y_test_pred['ds'].dt.date |
|
future_prediction['ds']=future_prediction['ds'].dt.date |
|
|
|
|
|
fig = go.Figure([ |
|
|
|
|
|
go.Scatter(x=y_test_true['ds'], y=y_test_true['y'],name='Observed',line=dict(color='rgba(218, 112, 214, 0.5)')), |
|
go.Scatter(x=y_test_pred['ds'],y=y_test_pred['yhat'],name='Historical Forecast',line=dict(color='#9400D3', dash='dash')), |
|
go.Scatter(x=future_prediction['ds'],y=future_prediction['yhat'],name='Future Forecast',line=dict(color='Dark Orange', dash='dot'))]) |
|
fig.update_layout( |
|
xaxis_title='Date', |
|
yaxis_title='Order Demand', |
|
margin=dict(l=0, r=0, t=50, b=0), |
|
xaxis=dict(title_font=dict(size=20)), |
|
yaxis=dict(title_font=dict(size=20))) |
|
fig.update_layout(width=700,height=400) |
|
tab1.plotly_chart(fig) |
|
|
|
results=y_test_pred.reset_index() |
|
results['store']='store_1' |
|
results['item']=item |
|
tab2.markdown(""" |
|
<div style='text-align: left; margin-top:-10px;'> |
|
<h2 style='font-size: 30px; font-family: Palatino, serif; |
|
letter-spacing: 2px; text-decoration: none;'> |
|
📃 |
|
<span style='background: linear-gradient(45deg, #ed4965, #c05aaf); |
|
-webkit-background-clip: text; |
|
-webkit-text-fill-color: transparent; |
|
text-shadow: none;'> |
|
Forecast Table |
|
</span> |
|
<span style='font-size: 40%;'> |
|
<sup style='position: relative; top: 5px; color: #ed4965;'></sup> |
|
</span> |
|
</h2> |
|
</div> |
|
""", unsafe_allow_html=True) |
|
final_r=pd.concat([future_prediction[['ds','store','item','yhat']],results[['ds','store','item','yhat']]]).sort_values('ds').drop_duplicates().reset_index(drop=True) |
|
csv = convert_df(final_r) |
|
tab2.dataframe(final_r,width=500) |
|
tab2.download_button( |
|
"Download", |
|
csv, |
|
"file.csv", |
|
"text/csv", |
|
key='download-csv' |
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
except: |
|
st.sidebar.error('Model Not Loaded successfully!',icon="🚨") |
|
|
|
|
|
|
|
|