Chapter 59 Intermediate ~50 min read

Building Analytics Dashboards

Practical guide to creating effective data visualization dashboards for basketball analysis.

Dashboard Design Principles

Effective dashboards present complex information clearly for quick understanding. The goal is enabling decisions, not showing all available data. Dashboard design requires understanding what decisions users make and what information supports those decisions.

Tool Selection

Various tools enable dashboard creation: Tableau and Power BI for business intelligence, R Shiny and Python Dash for custom analytics applications, and web frameworks for production deployment. Tool choice depends on user needs, update frequency, and technical resources.

import dash
from dash import dcc, html
import plotly.express as px

def create_player_dashboard(player_data):
    """Create interactive player analysis dashboard"""
    app = dash.Dash(__name__)

    app.layout = html.Div([
        html.H1("Player Analytics Dashboard"),

        dcc.Dropdown(
            id='player-select',
            options=[{'label': p, 'value': p} for p in player_data['PLAYER_NAME'].unique()],
            value=player_data['PLAYER_NAME'].iloc[0]
        ),

        dcc.Graph(id='shooting-chart'),
        dcc.Graph(id='performance-trend')
    ])

    return app

Key Visualizations

Basketball dashboards often include: shot charts, performance trends over time, player comparisons, lineup analysis, and opponent scouting reports. Each visualization type serves specific analytical purposes and requires appropriate design choices.

Interactivity and Exploration

Interactive dashboards enable users to explore data rather than viewing static reports. Filters, drill-downs, and hover details let analysts investigate questions that emerge during analysis. Good dashboard design anticipates exploration paths and enables fluid investigation.

Implementation in R

# Analytics pipeline in R
library(tidyverse)
library(DBI)
library(RSQLite)

# Database connection
create_analytics_db <- function(db_path) {
  con <- dbConnect(SQLite(), db_path)

  # Create tables
  dbExecute(con, "
    CREATE TABLE IF NOT EXISTS players (
      player_id INTEGER PRIMARY KEY,
      name TEXT,
      team_id INTEGER,
      position TEXT
    )
  ")

  dbExecute(con, "
    CREATE TABLE IF NOT EXISTS game_stats (
      game_id INTEGER,
      player_id INTEGER,
      pts INTEGER,
      ast INTEGER,
      reb INTEGER,
      PRIMARY KEY (game_id, player_id)
    )
  ")

  con
}

# ETL pipeline
run_etl <- function(con, source_file) {
  # Extract
  raw_data <- read_csv(source_file)

  # Transform
  clean_data <- raw_data %>%
    mutate(
      pts = as.integer(pts),
      ast = as.integer(ast)
    ) %>%
    filter(!is.na(pts))

  # Load
  dbWriteTable(con, "game_stats", clean_data, append = TRUE)

  nrow(clean_data)
}

db <- create_analytics_db("nba_analytics.db")
records <- run_etl(db, "daily_stats.csv")
print(paste("Loaded", records, "records"))

Implementation in Python

# Analytics pipeline in Python
import pandas as pd
import sqlite3
from datetime import datetime

class AnalyticsPipeline:
    def __init__(self, db_path):
        self.conn = sqlite3.connect(db_path)
        self._create_tables()

    def _create_tables(self):
        self.conn.execute("""
            CREATE TABLE IF NOT EXISTS players (
                player_id INTEGER PRIMARY KEY,
                name TEXT,
                team_id INTEGER,
                position TEXT
            )
        """)
        self.conn.execute("""
            CREATE TABLE IF NOT EXISTS game_stats (
                game_id INTEGER,
                player_id INTEGER,
                pts INTEGER,
                ast INTEGER,
                reb INTEGER,
                PRIMARY KEY (game_id, player_id)
            )
        """)
        self.conn.commit()

    def extract(self, source):
        return pd.read_csv(source)

    def transform(self, df):
        df["pts"] = pd.to_numeric(df["pts"], errors="coerce")
        df["ast"] = pd.to_numeric(df["ast"], errors="coerce")
        return df.dropna(subset=["pts"])

    def load(self, df, table):
        df.to_sql(table, self.conn, if_exists="append", index=False)
        return len(df)

    def run(self, source, table):
        data = self.extract(source)
        clean = self.transform(data)
        return self.load(clean, table)

pipeline = AnalyticsPipeline("nba_analytics.db")
records = pipeline.run("daily_stats.csv", "game_stats")
print(f"Loaded {records} records")

Implementation in R

# Analytics pipeline in R
library(tidyverse)
library(DBI)
library(RSQLite)

# Database connection
create_analytics_db <- function(db_path) {
  con <- dbConnect(SQLite(), db_path)

  # Create tables
  dbExecute(con, "
    CREATE TABLE IF NOT EXISTS players (
      player_id INTEGER PRIMARY KEY,
      name TEXT,
      team_id INTEGER,
      position TEXT
    )
  ")

  dbExecute(con, "
    CREATE TABLE IF NOT EXISTS game_stats (
      game_id INTEGER,
      player_id INTEGER,
      pts INTEGER,
      ast INTEGER,
      reb INTEGER,
      PRIMARY KEY (game_id, player_id)
    )
  ")

  con
}

# ETL pipeline
run_etl <- function(con, source_file) {
  # Extract
  raw_data <- read_csv(source_file)

  # Transform
  clean_data <- raw_data %>%
    mutate(
      pts = as.integer(pts),
      ast = as.integer(ast)
    ) %>%
    filter(!is.na(pts))

  # Load
  dbWriteTable(con, "game_stats", clean_data, append = TRUE)

  nrow(clean_data)
}

db <- create_analytics_db("nba_analytics.db")
records <- run_etl(db, "daily_stats.csv")
print(paste("Loaded", records, "records"))

Implementation in Python

# Analytics pipeline in Python
import pandas as pd
import sqlite3
from datetime import datetime

class AnalyticsPipeline:
    def __init__(self, db_path):
        self.conn = sqlite3.connect(db_path)
        self._create_tables()

    def _create_tables(self):
        self.conn.execute("""
            CREATE TABLE IF NOT EXISTS players (
                player_id INTEGER PRIMARY KEY,
                name TEXT,
                team_id INTEGER,
                position TEXT
            )
        """)
        self.conn.execute("""
            CREATE TABLE IF NOT EXISTS game_stats (
                game_id INTEGER,
                player_id INTEGER,
                pts INTEGER,
                ast INTEGER,
                reb INTEGER,
                PRIMARY KEY (game_id, player_id)
            )
        """)
        self.conn.commit()

    def extract(self, source):
        return pd.read_csv(source)

    def transform(self, df):
        df["pts"] = pd.to_numeric(df["pts"], errors="coerce")
        df["ast"] = pd.to_numeric(df["ast"], errors="coerce")
        return df.dropna(subset=["pts"])

    def load(self, df, table):
        df.to_sql(table, self.conn, if_exists="append", index=False)
        return len(df)

    def run(self, source, table):
        data = self.extract(source)
        clean = self.transform(data)
        return self.load(clean, table)

pipeline = AnalyticsPipeline("nba_analytics.db")
records = pipeline.run("daily_stats.csv", "game_stats")
print(f"Loaded {records} records")
Chapter Summary

You've completed Chapter 59: Building Analytics Dashboards.