Dashboard: Shortest Route and Data Mining
Build dashboards to empower effective visualization and drive better business decisions.
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 ...