Data Science

Data Visualization

Building a Dashboard with Streamlit

Every visualization so far — even Plotly's interactive charts (Chapter 4) — is still a single, static analysis output. A dashboard lets a non-technical user exp

JrCodex·7 min read

Jr Codex Data Science Notes

Level: Intermediate–Advanced Prerequisites: Chapter 4 Time to complete: ~30 minutes


Table of Contents

  1. From Charts to a Shareable App
  2. Your First Streamlit App
  3. Displaying Data & Charts
  4. Interactive Widgets
  5. Layout — Columns & Sidebars
  6. Putting It Together: a Small Sales Dashboard
  7. Caching for Performance
  8. Summary & Next Steps

1. From Charts to a Shareable App

Every visualization so far — even Plotly's interactive charts (Chapter 4) — is still a single, static analysis output. A dashboard lets a non-technical user explore data themselves: pick a date range, filter by region, and see charts update live, without touching any code.

The Progression
─────────────────────────────────────────
  Static chart (Ch.2-3)  →  Interactive chart (Ch.4)  →  Interactive APP (this chapter)
  "here's a picture"        "explore this ONE chart"      "explore the WHOLE dataset,
                                                             your own way"
─────────────────────────────────────────

Streamlit turns a plain Python script into a web app — no HTML, CSS, or JavaScript required, making it the fastest path from "I have a pandas analysis" to "I have a shareable tool."

pip install streamlit

2. Your First Streamlit App

# app.py
import streamlit as st
 
st.title("My First Dashboard")
st.write("Hello! This is a Streamlit app.")
 
name = st.text_input("What's your name?")
if name:
    st.write(f"Hello, {name}!")
streamlit run app.py

This opens a browser tab with a live, interactive page — every time a user interacts with a widget, Streamlit re-runs the entire script top to bottom, updating anything downstream of that widget automatically.

The Streamlit Mental Model
─────────────────────────────────────────
  Script runs top-to-bottom, ONE TIME per interaction.
  A widget's current value is just a normal Python variable —
  no callback functions or event listeners to write yourself.
─────────────────────────────────────────

3. Displaying Data & Charts

import streamlit as st
import pandas as pd
import matplotlib.pyplot as plt
 
st.title("Sales Overview")
 
df = pd.read_csv("sales_data.csv")
 
st.dataframe(df.head(10))          # an interactive, sortable table
st.write(df.describe())               # Module 1's descriptive stats, rendered directly
 
fig, ax = plt.subplots()
ax.hist(df["revenue"], bins=30)
st.pyplot(fig)                          # renders a matplotlib figure (Chapter 2) directly in the app
 
import plotly.express as px
fig2 = px.line(df, x="month", y="revenue")
st.plotly_chart(fig2)                     # renders a Plotly figure (Chapter 4), fully interactive

Streamlit natively displays pandas DataFrames, matplotlib figures, and Plotly charts — everything built in earlier chapters plugs directly in without conversion.


4. Interactive Widgets

import streamlit as st
import pandas as pd
 
df = pd.read_csv("sales_data.csv")
 
# Dropdown / selectbox
region = st.selectbox("Choose a region", options=df["region"].unique())
 
# Slider
min_spend, max_spend = st.slider(
    "Spend range", min_value=0, max_value=int(df["spend"].max()), value=(0, 500)
)
 
# Multi-select
selected_regions = st.multiselect("Compare regions", options=df["region"].unique())
 
# Date input
start_date = st.date_input("Start date")
 
# Checkbox
show_raw_data = st.checkbox("Show raw data")
 
# Using the widget values to FILTER the data — this is where the interactivity pays off
filtered_df = df[
    (df["region"] == region) &
    (df["spend"] >= min_spend) &
    (df["spend"] <= max_spend)
]
 
st.write(f"Showing {len(filtered_df)} of {len(df)} rows")
if show_raw_data:
    st.dataframe(filtered_df)
WidgetFunction
Dropdownst.selectbox()
Sliderst.slider()
Multi-selectst.multiselect()
Text inputst.text_input()
Checkboxst.checkbox()
Buttonst.button()

5. Layout — Columns & Sidebars

import streamlit as st
import pandas as pd
 
df = pd.read_csv("sales_data.csv")
 
# Sidebar — great for filters, keeping the main area for RESULTS
with st.sidebar:
    st.header("Filters")
    region = st.selectbox("Region", df["region"].unique())
    min_date = st.date_input("Start date")
 
# Columns — arrange elements side by side, instead of stacked vertically
col1, col2, col3 = st.columns(3)
 
with col1:
    st.metric("Total Revenue", f"${df['revenue'].sum():,.0f}")
with col2:
    st.metric("Average Order", f"${df['revenue'].mean():.2f}")
with col3:
    st.metric("Total Orders", len(df))
Layout Structure
─────────────────────────────────────────
  ┌─────────┬───────────────────────────────┐
  │         │  Total Rev │ Avg Order │ Count │  ← st.columns(3)
  │ Sidebar │───────────────────────────────┤
  │ Filters │                                │
  │         │        [ main chart area ]     │
  └─────────┴───────────────────────────────┘
─────────────────────────────────────────

st.metric() is purpose-built for exactly this kind of dashboard "headline number" display — directly connecting to Chapter 1's audience-driven visualization principle: a business stakeholder often wants three clear numbers before any chart at all.


6. Putting It Together: a Small Sales Dashboard

A complete, small dashboard combining everything above:

# sales_dashboard.py
import streamlit as st
import pandas as pd
import plotly.express as px
 
st.set_page_config(page_title="Sales Dashboard", layout="wide")
st.title("📊 Sales Dashboard")
 
@st.cache_data      # covered in Section 7
def load_data():
    return pd.read_csv("sales_data.csv", parse_dates=["order_date"])
 
df = load_data()
 
# Sidebar filters
with st.sidebar:
    st.header("Filters")
    regions = st.multiselect("Region", options=df["region"].unique(), default=df["region"].unique())
    date_range = st.date_input(
        "Date range",
        value=(df["order_date"].min(), df["order_date"].max()),
    )
 
# Apply filters — the same boolean masking from Python Notes' NumPy/Pandas primers
filtered = df[
    (df["region"].isin(regions)) &
    (df["order_date"] >= pd.Timestamp(date_range[0])) &
    (df["order_date"] <= pd.Timestamp(date_range[1]))
]
 
# Headline metrics
col1, col2, col3 = st.columns(3)
col1.metric("Total Revenue", f"${filtered['revenue'].sum():,.0f}")
col2.metric("Total Orders", f"{len(filtered):,}")
col3.metric("Avg Order Value", f"${filtered['revenue'].mean():.2f}")
 
# Charts
st.subheader("Revenue Over Time")
revenue_by_date = filtered.groupby("order_date")["revenue"].sum().reset_index()
fig_line = px.line(revenue_by_date, x="order_date", y="revenue")
st.plotly_chart(fig_line, use_container_width=True)
 
st.subheader("Revenue by Region")
revenue_by_region = filtered.groupby("region")["revenue"].sum().reset_index()
fig_bar = px.bar(revenue_by_region, x="region", y="revenue", color="region")
st.plotly_chart(fig_bar, use_container_width=True)
 
if st.checkbox("Show raw filtered data"):
    st.dataframe(filtered)
streamlit run sales_dashboard.py

This single file becomes a live, shareable tool — anyone with access to it can filter by region and date range and see every number and chart update instantly, with zero code changes required on their end.


7. Caching for Performance

Recall Section 2's mental model: Streamlit re-runs the entire script on every interaction. Without caching, this would mean re-reading sales_data.csv from disk every single time a user moves a slider — wasteful and slow.

import streamlit as st
import pandas as pd
 
@st.cache_data      # decorator (Python Notes, Module 5, Ch.2) — caches the function's return value
def load_data():
    print("Loading data from disk...")     # this only prints ONCE, even across many reruns
    return pd.read_csv("sales_data.csv")
 
df = load_data()      # subsequent calls with the SAME inputs return the cached result instantly
@st.cache_data — Directly Related to functools.lru_cache
─────────────────────────────────────────
  Python Notes, Module 5, Ch.4 covered @lru_cache for memoizing
  expensive function calls. @st.cache_data is Streamlit's version
  of the exact same idea, specialized for data loading and
  transformation steps inside a dashboard.
─────────────────────────────────────────

Rule of thumb: cache any function that loads data from disk/network or does an expensive computation that doesn't change between reruns — never cache functions whose result should change every time (like one incorporating live randomness or a live timestamp).


8. Summary & Next Steps

Key Takeaways

  • Streamlit turns a plain Python script into a shareable web app, re-running the entire script on every widget interaction — no HTML/JS/callback functions required.
  • Widgets (selectbox, slider, multiselect) become plain Python variables whose current value flows directly into filtering logic, using the same boolean masking from the Python Notes.
  • st.dataframe, st.pyplot, and st.plotly_chart display pandas, matplotlib, and Plotly output directly — everything from Chapters 2-4 plugs straight in.
  • @st.cache_data (the dashboard analog of @lru_cache) prevents expensive data loading from re-running on every single user interaction.

Module 4 Complete — Next Module

You now have the full visualization toolkit — from choosing the right chart type, to static (matplotlib/seaborn) and interactive (Plotly) charts, up to a fully shareable dashboard (Streamlit). Module 5 shifts from describing data to testing claims about it rigorously — A/B testing and applied statistics.

Concept Check

  1. What happens to the entire Streamlit script every time a user interacts with a widget?
  2. Why would you wrap a pd.read_csv() call in @st.cache_data?
  3. How does a Streamlit widget's value (e.g. from st.slider()) actually get used to filter a DataFrame?

Next Chapter

Module 5: Experimentation & Applied Statistics


Jr Codex — 1-on-1 Personalized Coaching | Back to Module Index