Introduction
Modern data analysis requires interactive visualizations that allow users to explore data dynamically. Today we'll explore three powerful Python libraries: Streamlit, Dash, and Bokeh - each offering unique advantages for different use cases.
π Streamlit: Simple and Fast
Streamlit is perfect for rapid prototyping with minimal code. It's the go-to choice for data scientists who want to create web apps without web development knowledge.
Key Features
β
Zero HTML/CSS knowledge required
β
Real-time updates
β
Easy deployment
β
Rich widget ecosystem
Example: Sales Dashboard
import streamlit as st
import pandas as pd
import plotly.express as px
import numpy as np
# Page configuration
st.set_page_config(page_title="Sales Dashboard", layout="wide")
st.title("π Sales Dashboard")
# Generate sample data
@st.cache_data
def load_data():
dates = pd.date_range('2024-01-01', '2024-12-31', freq='D')
data = {
'date': dates,
'sales': np.random.normal(1000, 200, len(dates)),
'region': np.random.choice(['North', 'South', 'East', 'West'], len(dates)),
'product': np.random.choice(['Product A', 'Product B', 'Product C'], len(dates))
}
df = pd.DataFrame(data)
df['sales'] = df['sales'].clip(lower=0) # No negative sales
return df
df = load_data()
# Sidebar filters
st.sidebar.header("Filters")
region = st.sidebar.selectbox("Select Region", df['region'].unique())
product = st.sidebar.selectbox("Select Product", df['product'].unique())
# Filter data
filtered_df = df[(df['region'] == region) & (df['product'] == product)]
# Metrics row
col1, col2, col3 = st.columns(3)
with col1:
st.metric("Total Sales", f"${filtered_df['sales'].sum():,.2f}")
with col2:
st.metric("Average Daily Sales", f"${filtered_df['sales'].mean():.2f}")
with col3:
st.metric("Number of Days", len(filtered_df))
# Charts
col1, col2 = st.columns(2)
with col1:
# Time series chart
fig_line = px.line(
filtered_df,
x='date',
y='sales',
title=f"Sales Trend - {region} ({product})"
)
st.plotly_chart(fig_line, use_container_width=True)
with col2:
# Distribution chart
fig_hist = px.histogram(
filtered_df,
x='sales',
title="Sales Distribution",
nbins=30
)
st.plotly_chart(fig_hist, use_container_width=True)
# Data table
if st.checkbox("Show Raw Data"):
st.subheader("Raw Data")
st.dataframe(filtered_df.head(100), use_container_width=True)
π’ Dash: Enterprise-Ready
Dash offers more control and customization for production applications. It's built on Flask and React, making it perfect for enterprise-grade dashboards.
Key Features
β
Production-ready
β
Highly customizable
β
Advanced callback system
β
Enterprise authentication support
Example: Stock Portfolio Tracker
import dash
from dash import dcc, html, Input, Output, callback
import plotly.express as px
import plotly.graph_objects as go
import pandas as pd
import yfinance as yf
# Initialize the Dash app
app = dash.Dash(__name__)
# Portfolio data
STOCKS = ['AAPL', 'GOOGL', 'TSLA', 'MSFT', 'AMZN']
# Layout
app.layout = html.Div([
html.Div([
html.H1("πΌ Stock Portfolio Tracker",
style={'textAlign': 'center', 'color': '#2E86AB', 'marginBottom': 30}),
]),
html.Div([
html.Div([
html.Label("Select Stock:", style={'fontWeight': 'bold'}),
dcc.Dropdown(
id='stock-dropdown',
options=[{'label': stock, 'value': stock} for stock in STOCKS],
value='AAPL',
style={'marginBottom': 20}
),
html.Label("Time Period:", style={'fontWeight': 'bold'}),
dcc.Dropdown(
id='period-dropdown',
options=[
{'label': '1 Month', 'value': '1mo'},
{'label': '3 Months', 'value': '3mo'},
{'label': '6 Months', 'value': '6mo'},
{'label': '1 Year', 'value': '1y'}
],
value='3mo'
)
], style={'width': '30%', 'display': 'inline-block', 'verticalAlign': 'top'}),
html.Div([
html.Div(id='stock-info')
], style={'width': '70%', 'display': 'inline-block', 'paddingLeft': 20})
]),
html.Div([
dcc.Graph(id='stock-chart')
]),
html.Div([
dcc.Graph(id='volume-chart')
])
])
@app.callback(
[Output('stock-chart', 'figure'),
Output('volume-chart', 'figure'),
Output('stock-info', 'children')],
[Input('stock-dropdown', 'value'),
Input('period-dropdown', 'value')]
)
def update_dashboard(selected_stock, period):
# Fetch stock data
try:
stock_data = yf.download(selected_stock, period=period)
# Stock price chart
fig_price = go.Figure()
fig_price.add_trace(go.Candlestick(
x=stock_data.index,
open=stock_data['Open'],
high=stock_data['High'],
low=stock_data['Low'],
close=stock_data['Close'],
name=selected_stock
))
fig_price.update_layout(
title=f"{selected_stock} Stock Price",
yaxis_title="Price ($)",
xaxis_title="Date"
)
# Volume chart
fig_volume = px.bar(
x=stock_data.index,
y=stock_data['Volume'],
title=f"{selected_stock} Trading Volume"
)
# Stock info
current_price = stock_data['Close'].iloc[-1]
price_change = current_price - stock_data['Close'].iloc[-2]
price_change_pct = (price_change / stock_data['Close'].iloc[-2]) * 100
info = html.Div([
html.H3(f"{selected_stock} Information"),
html.P(f"Current Price: ${current_price:.2f}"),
html.P(f"Change: ${price_change:.2f} ({price_change_pct:.2f}%)",
style={'color': 'green' if price_change >= 0 else 'red'}),
html.P(f"Period High: ${stock_data['High'].max():.2f}"),
html.P(f"Period Low: ${stock_data['Low'].min():.2f}")
])
return fig_price, fig_volume, info
except Exception as e:
# Error handling
empty_fig = go.Figure()
error_info = html.P(f"Error loading data: {str(e)}")
return empty_fig, empty_fig, error_info
if __name__ == '__main__':
app.run_server(debug=True)
β‘ Bokeh: Advanced Visualizations
Bokeh handles large datasets and complex interactions efficiently. It's perfect for creating sophisticated, publication-ready visualizations.
Key Features
β
High performance with large datasets
β
Advanced interactions
β
Real-time capabilities
β
Server and standalone applications
Example: Real-time Data Monitor
from bokeh.plotting import figure, curdoc
from bokeh.layouts import column, row
from bokeh.models import ColumnDataSource, Select, Button
from bokeh.models.widgets import DataTable, TableColumn
import numpy as np
import pandas as pd
from datetime import datetime
from functools import partial
# Global data source
source = ColumnDataSource(data=dict(
x=[], y=[], timestamp=[], sensor_id=[], color=[]
))
# Create main plot
def create_time_plot():
p = figure(
title="Real-time Sensor Data Monitor",
x_axis_label="Time",
y_axis_label="Value",
width=800,
height=400,
x_axis_type='datetime'
)
p.line('timestamp', 'y', source=source, line_width=2, color='blue', alpha=0.8)
p.circle('timestamp', 'y', source=source, size=8, color='color', alpha=0.6)
return p
# Create histogram
def create_histogram():
p = figure(
title="Value Distribution",
x_axis_label="Value",
y_axis_label="Frequency",
width=400,
height=300
)
return p
# Controls
sensor_select = Select(
title="Sensor ID:",
value="All",
options=["All", "Sensor_1", "Sensor_2", "Sensor_3"]
)
start_button = Button(label="Start Monitoring", button_type="success")
stop_button = Button(label="Stop Monitoring", button_type="danger")
# Data table
columns = [
TableColumn(field="timestamp", title="Timestamp"),
TableColumn(field="y", title="Value"),
TableColumn(field="sensor_id", title="Sensor ID")
]
data_table = DataTable(source=source, columns=columns, width=400, height=200)
# Callback for data generation
def update_data():
"""Generate new data point"""
sensors = ["Sensor_1", "Sensor_2", "Sensor_3"]
colors = ["red", "blue", "green"]
new_data = {
'timestamp': [datetime.now()],
'y': [np.random.normal(50, 10)],
'sensor_id': [np.random.choice(sensors)],
'color': [np.random.choice(colors)]
}
# Update data source
current_data = source.data.copy()
for key in new_data:
if key in current_data:
current_data[key].extend(new_data[key])
# Keep only last 100 points
if len(current_data[key]) > 100:
current_data[key] = current_data[key][-100:]
else:
current_data[key] = new_data[key]
source.data = current_data
# Button callbacks
def start_monitoring():
curdoc().add_periodic_callback(update_data, 1000)
def stop_monitoring():
curdoc().remove_periodic_callback(update_data)
start_button.on_click(start_monitoring)
stop_button.on_click(stop_monitoring)
# Create plots
time_plot = create_time_plot()
histogram = create_histogram()
# Layout
controls = column(sensor_select, row(start_button, stop_button))
plots = row(time_plot, column(histogram, data_table))
layout = column(controls, plots)
# Add to document
curdoc().add_root(layout)
curdoc().title = "Real-time Data Monitor"
π¦ Deployment Guide
Streamlit Cloud (Recommended)
Create requirements.txt:
txtstreamlit==1.28.0
pandas==2.0.3
plotly==5.15.0
numpy==1.24.3
Deploy Steps:
# Push to GitHub
git add .
git commit -m "Initial commit"
git push origin main
# Visit share.streamlit.io
# Connect your GitHub repository
# Auto-deploy!
Dash on Heroku
Prepare files:
# Add to your Dash app
server = app.server
if __name__ == '__main__':
app.run_server(debug=False)
Create Procfile:
web: gunicorn app:server
Deploy:
heroku create your-app-name
git push heroku main
***Bokeh Server*
Start Bokeh server:**
bashbokeh serve --show --allow-websocket-origin=yourdomain.com app.py
For cloud deployment:
bashbokeh serve --port=$PORT --allow-websocket-origin=* app.py
π Comparison Matrix
Conclusion
Interactive data visualization has never been more accessible. Whether you choose Streamlit for rapid prototyping, Dash for enterprise applications, or Bokeh for advanced visualizations, you now have the power to create compelling, interactive dashboards that transform how users interact with data.
Start with Streamlit to get your feet wet, graduate to Dash when you need production features, and leverage Bokeh when performance and advanced interactions are critical.
The future of data visualization is interactive - and these tools are your gateway to that future.
Top comments (1)
nice