Build a simple interactive analytics web app using the 2022–2023 NBA Player Stats dataset and Dash framework for Python
What About Dash?
Throughout my Ph.D., I’ve grown into a great enthusiast of the Dash framework for Python. In the world of data analysis and visualization, Dash is becoming increasingly popular for building interactive web-based dashboards. With its simplicity and flexibility, Dash allows you to easily create and customize data-driven visualizations without requiring extensive web development experience.
In this article, we‘ll explore how to build a simple report in Dash by setting up the necessary environment, importing data, and creating a basic layout. Additionally, we’ll cover how to create interactive elements like dropdowns and charts, all within the Dash framework.
Setting Up the Environment
While not mandatory, it is best to create a virtual environment to isolate the dependencies of your Dash project. This allows us to have a tidy environment and avoid a conflicting package mess. We can create a new environment by doing the following:
# create a new environment for our Dash project
python -m venv dash_env
# and activate it
myenvScriptsactivate
We can then install the Dash library by simply using pip:
pip install dash
Importing the Dataset
I chose to use the 2022–2023 NBA Player Stats dataset, which contains 2022–2023 regular season NBA player stats, presenting attributes such as points, assists, rebounds, etc.
Here is a quick breakdown for each of the 30 columns of the dataset:
To import the dataset into our Dash app, we initially need to ensure that the dataset file (in CSV) is accessible to our project directory. Then we can do the following:
import pandas as pd
# read the dataset into a pandas DataFrame
df = pd.read_csv('nba_dataset.csv', encoding='latin-1')
After importing the dataset, we can gain a basic understanding of its structure and contents by:
# display the first few rows of the DataFrame
print(df.head())
# get information about the DataFrame
print(df.info())
# generate summary statistics of the dataset
print(df.describe())
Building our Dash App
In general, Dash apps include two main components: layout and callbacks. The layout component defines the visual and structural parts of the app, whereas the callbacks component describes the app’s interactivity.
Before diving into the layout creation, we need to import the required libraries, including Dash components and the necessary data visualization libraries (here, we’ll be using Plotly):
import dash
import dash_core_components as dcc
import dash_html_components as html
import plotly.express as px
import plotly.graph_objects as go
Then, we can initiate an instance of a Dash app simply by doing the following:
app = dash.Dash(__name__)
Now we are ready to define the layout and callback components of our Dash app.
Creating a layout
My goal is to create a layout that looks like the following image:
More specifically, this layout is going consist of three main containers:
- The blue container includes a dropdown menu allowing us to select a player.
- The green container includes the player’s shooting charts for field, two-point, and three-point field goals.
- The red container includes a playmaker analysis bubble chart that displays points, assists, and rebounds.
Firstly, we need to define the dropdown menu selections by using the following code:
# dropdown menu options
player_options = [{'label': name, 'value': name} for name in df['Player'].unique()]
Then, we need to define the dropdown menu in the layout like this:
# dropdown menu
html.Div([
html.H1('NBA Player Stats 2022 - 2023'),
html.Div([
dcc.Dropdown(
id='player-dropdown',
options=[{'label': player, 'value': player} for player in df['Player']],
value=df['Player'].iloc[0],
placeholder='Select a player'
)
], style={'display': 'block', 'height': '30%', 'justify-content': 'center', 'color': 'gray'}),
html.Div([], style={'height': '70%'})
], style={'display': 'inline-block', 'height': '100%', 'width': '15%', 'background-color': '#17408B', 'color': 'white', 'padding' : '2%', 'position': 'relative', }),
Then, we can also create the shooting charts for the selected player. These will be three pie charts displaying the score of successful vs attempted goals for field, two-point, and three-point field goals.
# player's shopting pie charts
html.Div([
html.Div(id='player-stats-container', children=[]),
html.H1('Players Shooting'),
html.Div([
dcc.Graph(id='goals-pie-chart', style={'display': 'inline-block', 'box-sizing': 'border-box', }),
dcc.Graph(id='2Pgoals-pie-chart', style={'display': 'inline-block', 'box-sizing': 'border-box', }),
dcc.Graph(id='3Pgoals-pie-chart', style={'display': 'inline-block', 'box-sizing': 'border-box', })
], style={'display': 'inline-block' })
], style={'display': 'block', 'height': '40%', 'width': '100%', 'box-sizing': 'border-box', 'color': 'gray', 'margin-left': '2%'})
Finally, we can create a playmaker analysis bubble chart. This will be a scatter plot displaying assists in the x-axis, points in the y-axis, and rebounds as the size of the marker.
# playmaker analysis
html.Div([
html.Div(id='total-stats-container',children=[]),
html.H1('Playmaker Analysis'),
html.Div([dcc.Graph(id='bubble-chart'), ], style={'display': 'block', 'box-sizing': 'border-box'})
], style={'display': 'block', 'height': '60%', 'width': '100%', 'box-sizing': 'border-box', 'color': 'gray'
, 'margin-left': '2%'})
Playmaker analysis allows us to visualize a player’s playmaking ability (assists) in relation to their scoring output (points). In that way, we can identify players who excel in playmaking and scoring or specialize in one area over the other. Incorporating rebounds as the size of the marker adds another dimension to the analysis. This allows us to quickly identify players who not only contribute in terms of points and assists but also have a significant impact on rebounding.
By bundling everything together within a parent container and assigning it to app.layout, we can create the layout component of our report with the following code:
app.layout = html.Div([
html.Div([ # drop down menu
html.H1('NBA Player Stats 2022 - 2023'),
html.Div([
dcc.Dropdown(
id='player-dropdown',
options=[{'label': player, 'value': player} for player in df['Player']],
value=df['Player'].iloc[0],
placeholder='Select a player'
)
], style={'display': 'block', 'height': '30%', 'justify-content': 'center', 'color': 'gray'}),
html.Div([], style={'height': '70%'})
], style={'display': 'inline-block', 'height': '100%', 'width': '15%', 'background-color': '#17408B', 'color': 'white', 'padding' : '2%', 'position': 'relative', }),
html.Div([
html.Div([ # players' shooting
html.Div(id='player-stats-container', children=[]),
html.H1('Players Shooting'),
html.Div([
dcc.Graph(id='goals-pie-chart', style={'display': 'inline-block', 'box-sizing': 'border-box', }),
dcc.Graph(id='2Pgoals-pie-chart', style={'display': 'inline-block', 'box-sizing': 'border-box', }),
dcc.Graph(id='3Pgoals-pie-chart', style={'display': 'inline-block', 'box-sizing': 'border-box', })
], style={'display': 'inline-block' })
], style={'display': 'block', 'height': '40%', 'width': '100%', 'box-sizing': 'border-box', 'color': 'gray', 'margin-left': '2%'}),
html.Div([ # playmaker analysis
html.Div(id='total-stats-container',children=[]),
html.H1('Playmaker Analysis'),
html.Div([dcc.Graph(id='bubble-chart'),
], style={'display': 'block', 'box-sizing': 'border-box'})
], style={'display': 'block', 'height': '60%', 'width': '100%', 'box-sizing': 'border-box', 'color': 'gray'
, 'margin-left': '2%'}),
], style={'display': 'inline-block', 'height': '100%', 'width': '85%', 'background-color': 'white', 'box-sizing': 'border-box', })
], style={'display':'flex', 'height': '100vh', 'width': '100vw', 'position': 'fixed', 'margin': '-8px','justify-content': 'center', 'boxSizing': 'border-box' })
Setting up callbacks
The layout component we created describes the look and feel of our report. Nevertheless, to ensure the proper functionality of the app, we also need to define the callbacks component, to specify how the layout elements interact with each other. For instance, when we select a player from the dropdown, we want the data of the pie charts to be updated for the selected player.
This interaction should be defined in the callbacks section. By specifying the necessary callbacks, we establish the logic that connects the user’s actions with the corresponding updates and changes in the displayed data.
Therefore, we can define the callback that updates the Field Goals pie chart according to the player we choose in the dropdown as the following:
# define the callback function for the pie chart
@app.callback(
dash.dependencies.Output('goals-pie-chart', 'figure'),
dash.dependencies.Input('player-dropdown', 'value')
)
def update_pie_chart(selected_player):
player_data = df[df['Player'] == selected_player].iloc[0]
goals_made = player_data['FG']
goals_attempted = player_data['FGA']
# calculate the percentage of Goals Made vs Goals Attempted
percent_made = (goals_made / goals_attempted) * 100
percent_attempted = 100 - percent_made
# create the labels for the pie chart
labels = ['Goals Made', 'Goals Attempted']
# create the values for the pie chart
values = [percent_made, percent_attempted]
# create the pie chart trace
pie_chart = go.Pie(labels=labels, values=values)
# create the (Plotly) layout for the pie chart
layout = go.Layout(title=f'Field Goals rate')
# create the figure and add the pie chart trace to it
fig = go.Figure(data=[pie_chart], layout=layout)
colors = ['#C9082A', ' #E8E8E8'] # red for Goals Made, gray for Goals Attempted
fig.update_traces(marker=dict(colors=colors))
return fig
We can set the callbacks to update the pie charts of the two- and three-point field goals.
Finally, we can set the callback for the bubble chart as per the following code, which will highlight the player selected in the dropdown menu with a red bubble.
# define the callback function for the bubble chart
@app.callback(
dash.dependencies.Output('bubble-chart', 'figure'),
[dash.dependencies.Input('player-dropdown', 'value')]
)
def update_bubble_chart(selected_player):
bubble_chart = go.Scatter(
x=df['AST'],
y=df['PTS'],
mode='markers',
marker=dict(
size=df['TRB'],
sizemode='diameter',
sizeref=0.3,
color=df['Player'].apply(lambda x: 'red' if x == selected_player else 'grey')
),
hovertext=df.apply(lambda row: f"AST: {str(row['AST'])} , PTS: {str(row['PTS'])}, {row['Player']}", axis=1)
)
# create the (Plotly) layout for the pie chart
layout = go.Layout( height=700, margin=dict(t=0), xaxis = dict(title='Assists (AST)'), yaxis=dict(title='Points (PTS)') )
fig = go.Figure( data = [bubble_chart], layout = layout)
return fig
Testing the app
After defining the layout and callbacks components, our app is ready, and we can launch it by writing the following code:
if __name__ == '__main__':
app.run_server(debug=True)
Then, we can run the entire app file.
If everything is done correctly, we’ll get something like this:
✨And voilà✨
The Dash app runs on a localhost server and is accessible via a web browser in the displayed URL. It is worth mentioning that the Dash framework provides a very convenient debugging method called hot reload, indicated in ‘debug mode: on.’
This means the Dash app will automatically reload every time we make and save changes in the Dash app file. Finally, when deploying a Dash app in a production environment, several cloud-based platform service options exist, like Heroku. Also, an on-premise server can be used for deploying a Dash app.
On my Mind
In data analytics and visualization, custom reporting tools like Dash are becoming increasingly popular for their flexibility and ease of use. Compared to self-service tools like Power BI or Tableau, which offer a wide range of pre-built visualization options, custom reporting tools like Dash provide complete control over the design and functionality of your reports. This allows for creating truly tailor-made reports and visualizations, that entirely fit the user’s needs and requirements.
On top of that, since Dash is built on Python, it allows for seamless integration of other popular Python libraries and data analysis tools, such as scikit-learn or TensorFlow. This integration is valuable because it brings machine learning models (or any other Python-built functionality) closer to the end user with no need for staging model results in intermediate apps or databases.
Moreover, Dash easily enables the execution of Python models on demand with user-defined parameters. This allows users to obtain immediate insights based on their specific inputs and preferences by exploring data, manipulating parameters, and visualizing results in real time.
Happy reporting!
From Data to Dashboard: Visualizing NBA Player Stats With Dash was originally published in Better Programming on Medium, where people are continuing the conversation by highlighting and responding to this story.