Using Plotly’s Dash to deliver public sector decision support dashboards
By: Adriano Yoshino
Tags: Plotly, Python, Dash, Data Visualization, Data Science

Building and using decision support systems is a critical competency for any public technologist. While dashboards are everywhere today, they still occupy an important and most visual artifact that appear directly in front of frontline and/or executive decision-makers.

Dashboards are amongst the most used end products of an entire process of acquiring, cleaning, and analyzing data towards answering operational questions and becoming an effective administrator of a public resource.

At ARGO, we have used R Shiny, Carto, and Tableau as our primary analytics visualization platforms. They all had their unique strengths.

R Shiny powers several tools in the Analytics Portal for the California Data Collaborative.

Carto has a commanding presence in the GIS space and allows us to deploy spatial analysis.

Tableau has largely been where we have hosted a major of our Street Maintenance (SQUID) work.

In this post, I will introduce Plotly’s DASH — an open source visualization framework that, combines the best of all the 3 mentioned platforms and packs them into an easily accessible and deployable framework.

For this post, I have repurposed one of ARGO’s more complex decision support systems, built originally in Tableau; the SONYC Siting Analysis for middle school expansion in NYC.

In addition to a step-by-step introduction to DASH, I have also recorded a video training to implement a simple {DASH} web-app. Head on over to the ARGO-Marketplace repo to find examples and working code. You may also open github issues on the repo to ask specific questions about how to use DASH.

Creating Dashboards with Plotly Dash

Recently Plotly announced Dash, an open source framework to create web applications with Python. Equivalent of Shiny in R, Dash combines Flask with Plotly’s python library to create beautiful and responsive applications. We are going to explore this new tool to build a dashboard that show a decision support system to help DYCD/DOE’s award RFPs for the 2015 SONYC expansion.

This topic was explored in a post from April 15, 2017, and the results shown in a dashboard built in Tableau. The goal for this post is basically replicate that dashboard with this new framework and present the step-by-step to accomplish this. The idea is not copy and paste all lines of the code here, but give you some intuition on how to develop each fundamental part of the app. To check the full code, please visit this GitHub repo.

Step 1: The Setup

Before start building the app, it is necessary to install some packages that are going to be used. Here is how to install Dash and other dependencies we will use to build this dashboard:

# Required to build the application

pip install dash

pip install dash_renderer

pip install dash_core_components

pip install dash_html_components

pip install dash_table_experiments

pip install plotly # Required to deploy it in Heroku

pip install gunicorn

Also, as we are going to plot a map, you need to have a Mapbox API Key set up. You can do this by accessing this link.

Step 2: App Initialization

Importing libraries and initializing the app

import dash

from dash.dependencies import Input, Output, State, Event

import dash_core_components as dcc

import dash_html_components as html

import dash_table_experiments as dt

import plotly

import flask

import pandas as pd

import numpy as np app = dash.Dash()

Loading the dataset: the dataset used to this dashboard contains the location information (latitude and longitude) and other features used to calculate the “need score” for each SONYC eligible school. Also, I declared my Mapbox access token to build the map.

# API keys and datasets

mapbox_access_token = "YOUR_KEY" #fill this field with your Mapbox key

map_data = pd.read_csv('SONYC_Dataset.csv')

Step 3: Layout

To help configuring the front-end of the app, I used the Bootstrap grid system used in the Plotly Dash Oil and Gas demo, it will be fundamental to make the app responsive and aligned. To learn more about Bootstrap Grid System, take a loot at this link.

Explaining it in a simple manner, Bootstrap grid will standardize the position of Dash components by dividing the screen into 12 columns. Here is the configuration of the grid for our app:

Yes! One row can contain more than one block. It’s up to you to build the logic of your grid.

Also, declaring a “global layout” dictionary helps making the code succinct and practical, allowing you to style all dash components with few lines

layout = dict(

autosize=True,

height=500,

font=dict(color="#191A1A"),

titlefont=dict(color="#191A1A", size='14'),

margin=dict(

l=35,

r=35,

b=35,

t=45

),

hovermode="closest",

plot_bgcolor='#fffcfc',

paper_bgcolor='#fffcfc',

legend=dict(font=dict(size=10), orientation='h'),

title='Each dot is an NYC Middle School eligible for SONYC funding',

mapbox=dict(

accesstoken=mapbox_access_token,

style="light",

center=dict(

lon=-73.91251,

lat=40.7342

),

zoom=10,

)

)

Step 4: Building the app

To build the app, you have to “declare” the components you are going to use and then feed them with an output. This output can be added directly in this layout step or be fed by the callback function (we’ll see that in the step 5).

The simple structure of a Dash app component is basically:

html.Div(

[

SOME_HTML_OR_DASH_COMPONENT(

id,

content (data),

style (a dictionary with the properties))

], className (name of the Bootstrap Class)

)

And the app is a composition of pre defined components.

For example, our Map + Table + Histogram part can be created by declaring the components in the following way. IMPORTANT: note the className at the end of “[]”, it will indicate the size of the “Bootstrap Grid” or the end of a “row”.

# Map + table + Histogram

html.Div(

[

html.Div(

[

dcc.Graph(id='map-graph',

style={'margin-top': '20'})

], className = "six columns"

),

html.Div(

[

dt.DataTable(

rows=map_data.to_dict('records'),

columns=map_data.columns,

row_selectable=True,

filterable=True,

sortable=True,

selected_row_indices=[],

id='datatable'),

],

style=layout_right,

className="six columns"

),

html.Div(

[

dcc.Graph(id="histogram")

],className="twelve columns")

], className="row"

)

Step 5: Making it work interactively and adding information to the map, table and histogram

Now it’s time to set the callbacks (what makes the app interactive). The logic of a callback function is an input, an output and a function that generate the necessary output:

app.callback(

Output(id, component to be changed),

[Input(id, action)])

def function(input_action):

return result

Following this logic, the function used to update the map when there is a selection on the table is:



Output('map-graph', 'figure'),

[Input('datatable', 'rows'),

Input('datatable', 'selected_row_indices')])

def map_selection(rows, selected_row_indices):

aux = pd.DataFrame(rows)

temp_df = aux.ix[selected_row_indices, :]

if len(selected_row_indices) == 0:

return gen_map(aux)

return gen_map(temp_df) @app .callback(Output('map-graph', 'figure'),[Input('datatable', 'rows'),Input('datatable', 'selected_row_indices')])def map_selection(rows, selected_row_indices):aux = pd.DataFrame(rows)temp_df = aux.ix[selected_row_indices, :]if len(selected_row_indices) == 0:return gen_map(aux)return gen_map(temp_df)

Where gen_map is the function declared to build the map:

def gen_map(map_data):

return {

"data": [

{

"type": "scattermapbox",

"lat": list(map_data['Latitude']),

"lon": list(map_data['Longitude']),

"text": list(map_data['Need Score']),

"mode": "markers",

"name": list(map_data['Location Name']),

"marker": {

"size": 6,

"opacity": 1.0,

"color": color_scale(map_data)

}

}

],

"layout": layout

}

Also, when bars are selected on the histogram, it also selects rows in the table:



Output('datatable', 'selected_row_indices'),

[Input('histogram', 'selectedData')],

[State('datatable', 'selected_row_indices')])

def update_selected_row_indices(selectedData, selected_row_indices):

if selectedData:

selected_row_indices = []

for point in selectedData['points']:

selected_row_indices.append(point['pointNumber'])

return selected_row_indices @app .callback(Output('datatable', 'selected_row_indices'),[Input('histogram', 'selectedData')],[State('datatable', 'selected_row_indices')])def update_selected_row_indices(selectedData, selected_row_indices):if selectedData:selected_row_indices = []for point in selectedData['points']:selected_row_indices.append(point['pointNumber'])return selected_row_indices

After the callbacks the app works following this logic:

Callbacks interaction in the app

Last part, required to run the app:

if __name__ == '__main__':

app.run_server(debug=True)

Step 6: Running the app

To run the app you have to save the file as app.py and run in your terminal:

python app.py

You will see some message like this:

* Running on http://127.0.0.1:8050/ (Press CTRL+C to quit) * Restarting with stat * Debugger is active! * Debugger pin code: 270-342-152

Then, access the link provided to see the dashboard (in our case: http://127.0.0.1:8050/)

You will see a dashboard like this:

Step 7: Deployment

You can deploy your app on a host like Heroku. To do that, you can follow the steps from the Dash User Guide.

You can check out this code in the GitHub, and feel free to leave your comments.

Additional Resources

Felt like you need a step-by-step tutorial to start building dashboards with Dash? Check this series of videos I recorded in April, 2018:

And, here are some links and posts I found useful while learning how to build this Dash app: