...

/

Dashboard: Shortest Route and Data Mining

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.

Press + to interact
import pandas as pd
df=pd.read_csv('StoreClusterCwDashi.csv')
# Set the option to display all columns
pd.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.

Press + to interact
import pandas as pd
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)
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)  
Dashboard with one graph, drop-downs, and radio items

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 ...