Dashboard: Shortest Route and Data Mining
Build dashboards to empower effective visualization and drive better business decisions.
We'll cover the following...
We created a visualization to show the shortest route on a map. Now, let’s add our data mining insights and make the dashboard even more meaningful.
Data preparation
Before we create the dashboard, let’s take a look at the source data.
import pandas as pddf=pd.read_csv('StoreClusterCwDashi.csv')# Set the option to display all columnspd.set_option('display.max_columns', None)print(df)
We’ve aggregated the RFM clustering results based on the store for four calendar weeks. For the dashboard to work properly, we need to handle each metric as its own row. By organizing the metrics as individual rows, we allow for better handling of the data in the dashboard, enabling dynamic filtering and selection of metrics. This structured data layout facilitates more flexible and intuitive interactions with the dashboard.
import pandas as pddf = pd.read_csv('StoreClusterCwDashi.csv')# Group the data by specified columns and calculate the mean for each groupcrossDf = df.groupby(['CW', 'Store', 'Segment', 'StoreName']).mean()# Stack the grouped data to create a Pandas Series 'sf'sf = crossDf.stack()# Convert the Pandas Series 'sf' back into a DataFrame called 'crossDf'crossDf = pd.DataFrame(sf)# Reset the index of the DataFrame and move index levels to columnscrossDf = crossDf.reset_index()# Rename specific columns in the DataFramecrossDf.rename(columns={'level_4': 'Indicator Name', 0: 'Value', 'CW': 'CW', 'StoreName': 'Store Name'}, inplace=True)print(crossDf)
Geographically clustered TSP dashboard
On a two-dimensional map, we can represent two key figures using size and color. However, we give the user the possibility to choose the key figure to be displayed for size using the crossfilter-xaxis-column drop-down. The metric for color can be chosen in the crossfilter-yaxis-column drop-down. Let’s first take a look at the finished dashboard and then discuss the implementation in detail.
from dash import Dash, html, dcc, Input, Output
import pandas as pd
import plotly.express as px
import numpy as np
# Read data from CSV file and perform data transformations
df = pd.read_csv('StoreClusterCwDashi.csv')
# Group the data by specified columns and calculate the mean for each group
crossDf = df.groupby(['CW', 'Store', 'Segment', 'StoreName']).mean()
# Stack the grouped data to create a Pandas Series 'sf'
sf = crossDf.stack()
# Convert the Pandas Series 'sf' back into a DataFrame called 'crossDf'
crossDf = pd.DataFrame(sf)
# Reset the index of the DataFrame and move index levels to columns
crossDf = crossDf.reset_index()
# Rename specific columns in the DataFrame
crossDf.rename(columns={'level_4': 'Indicator Name', 0: 'Value', 'CW': 'CW', 'StoreName': 'Store Name'}, inplace=True)
# Initialize the Dash app
app = Dash(__name__, title='Educative TSP', update_title='Educative is updating..')
image_path = 'assets/dashboard-educative-logo.png'
# Define the layout of the app
app.layout = html.Div([
# Empty header with logo
html.Div([
html.H1('', style={'display': 'inline-block', 'height': '50px'}),
html.Img(src=image_path, style={'height': '50px'})
]),
# Scatter plot component
html.Div([
dcc.Graph(
id='crossfilter-indicator-scatter', config={
"displaylogo": False,
'modeBarButtonsToRemove': ['pan2d']},
hoverData={'points': [{'customdata': 0}]}
),
], style={'width': '49%', 'display': 'inline-block', 'padding': '0 20'}),
# Dropdowns and controls for user interaction
html.P("Please use the sliders below to select the KPI."),
html.Div([
html.Div([
# Dropdown for x-axis column selection
dcc.Dropdown(
crossDf['Indicator Name'].unique(),
'NetSales',
id='crossfilter-xaxis-column',
),
# Radioitems for x-axis type
dcc.RadioItems(
[],
'Linear',
id='crossfilter-xaxis-type',
labelStyle={'display': 'inline-block', 'marginTop': '5px'}
)
]),
], style={'width': '49%', 'display': 'inline-block'}),
# More dropdowns and controls
html.Div([
dcc.Dropdown(
crossDf['Indicator Name'].unique(),
'Recency',
id='crossfilter-yaxis-column'
),
dcc.Dropdown(
crossDf['Indicator Name'].unique(),
'Frequency',
id='crossfilter-zaxis-column'
),
dcc.Dropdown(
crossDf['Indicator Name'].unique(),
'OverallScore',
id='crossfilter-qaxis-column'
),
dcc.Dropdown(id='continent-dropdown',
options=[{'label': i, 'value': i} for i in np.append([0], crossDf['Store Name'].unique())],
multi=True, value=0
),
dcc.Dropdown(id='ClusterCat-dropdown',
options=[{'label': i, 'value': i} for i in np.append(['All'], crossDf['Segment'].unique())],
multi=True, value='All'
),
dcc.RadioItems(
[],
'Linear',
id='crossfilter-yaxis-type',
labelStyle={'display': 'inline-block', 'marginTop': '5px'}
)
], style={'width': '49%', 'float': 'right', 'display': 'inline-block'}),
# Slider for CW selection
html.Div(dcc.Slider(
crossDf['CW'].min(),
crossDf['CW'].max(),
step=None,
id='crossfilter-CW--slider',
value=crossDf['CW'].max(),
marks={str(int): str(int) for int in crossDf['CW'].unique()}
), style={'width': '49%', 'padding': '0px 20px 20px 20px'}),
])
# Use callback function to update the scatter plot
@app.callback(
Output('crossfilter-indicator-scatter', 'figure'),
Input('crossfilter-xaxis-column', 'value'), # 1st input
Input('crossfilter-yaxis-column', 'value'), # 2nd input
Input('crossfilter-zaxis-column', 'value'), # 3rd input
Input('crossfilter-xaxis-type', 'value'), # 4th input
Input('crossfilter-yaxis-type', 'value'), # 5th input
Input('continent-dropdown', 'value'), # SpHA
Input('crossfilter-CW--slider', 'value'), # 6th input
Input('crossfilter-qaxis-column', 'value'),
Input('ClusterCat-dropdown', 'value'))
def update_graph(xaxis_column_name, yaxis_column_name, zaxis_column_name,
xaxis_type, yaxis_type, Cust_value,
CW_value, qaxis_column_name, CusterCat_value):
# Filtering based on the slider and dropdown selection
if Cust_value == 0:
crossDff = crossDf.loc[crossDf['CW'] == CW_value]
else:
crossDff = crossDf.loc[(crossDf['CW'] == CW_value) & (crossDf['Store Name'].isin(Cust_value))]
if CusterCat_value == 'All':
crossDff = crossDff.loc[crossDff['CW'] == CW_value]
else:
crossDff = crossDff.loc[(crossDff['CW'] == CW_value) & (crossDff['Segment'].isin(CusterCat_value))]
# Create a scatter_geo plot
fig = px.scatter_geo(
lat=crossDff[crossDff['Indicator Name'] == 'lat']['Value'],
lon=crossDff[crossDff['Indicator Name'] == 'lon']['Value'],
hover_name=crossDff.loc[crossDf['Indicator Name'] == yaxis_column_name]['Store Name'],
projection="natural earth",
size=crossDff[crossDff['Indicator Name'] == xaxis_column_name]['Value'],
color=crossDff[crossDff['Indicator Name'] == yaxis_column_name]['Value']
)
fig.update_traces(customdata=crossDff[crossDff['Indicator Name'] == yaxis_column_name]['Store Name'])
fig.update_xaxes(title=xaxis_column_name, type='linear' if xaxis_type == 'Linear' else 'log')
fig.update_yaxes(title=yaxis_column_name, type='linear' if yaxis_type == 'Linear' else 'log')
fig.update_layout(margin={'l': 40, 'b': 40, 't': 10, 'r': 0}, hovermode='closest', template="plotly_white")
return fig
# Run the app
if __name__ == '__main__':
app.run(debug=True, host = '0.0.0.0',port=8100)
Note: Please click the provided URL to see the visualization in a large tab.
In an interactive dashboard, many things are connected code-wise. Let’s discuss the code based on three essential ...