Streamlit: Deep Dive & Best Practices
Introduction
Streamlit is an open-source Python framework that revolutionizes how data scientists and machine learning engineers create and share interactive web applications. Unlike traditional web frameworks that require expertise in HTML, CSS, JavaScript, and backend development, Streamlit allows you to build beautiful, production-ready data apps using pure Pythonβoften in just a few lines of code. Created to bridge the gap between data analysis and web deployment, Streamlit automatically handles the entire web stack, from UI rendering to state management, enabling data professionals to focus on what matters most: their data and insights. This comprehensive guide explores Streamlit from fundamental concepts to advanced deployment strategies, empowering you to rapidly prototype, iterate, and deploy data-driven applications.
Table 1: Streamlit Ecosystem Terminology Mapping
Different contexts and documentation use varying terminology for similar Streamlit concepts:
| Standard Term | Alternative Names | Context/Usage |
|---|
| Widget | Input Element, Control, Interactive Component | User input elements |
| Rerun | Script Re-execution, Refresh, Update Cycle | Script execution model |
| Session State | App State, Persistent State, State Management | Data persistence across reruns |
| Magic Commands | Automatic Display, Implicit Rendering | Variables displayed without st.write() |
| st.write() | Swiss Army Knife, Universal Display | Versatile display function |
| Caching | Memoization, Cached Computation, Performance Optimization | Avoiding redundant computations |
| @st.cache_data | Data Cache, DataFrame Cache | Caching data transformations |
| @st.cache_resource | Resource Cache, Connection Cache | Caching database/ML model connections |
| Layout | Page Structure, Container Organization | Organizing app components |
| Sidebar | Side Panel, Navigation Panel | Secondary content area |
| Column | Grid Column, Layout Column | Horizontal layout division |
| Container | Layout Container, Group Container | Logical grouping of elements |
| Expander | Collapsible Section, Accordion | Expandable content section |
| Tab | Tab Panel, Tabbed Interface | Tabbed content organization |
| Form | Batch Input, Input Group | Grouped input submission |
| Progress Bar | Progress Indicator, Loading Bar | Visual progress feedback |
| Spinner | Loading Indicator, Wait Spinner | Loading state indicator |
| Component | Custom Component, Streamlit Component | JavaScript-Python bridge |
| Community Cloud | Streamlit Cloud, Streamlit Sharing | Free hosting platform |
Table 2: Hierarchical Streamlit Concept Structure
This table organizes Streamlit concepts from high-level abstractions to specific implementations:
| Level | Category | Term | Parent Concept | Description |
|---|
| L1 | Framework | Streamlit | - | Python web framework for data apps |
| L2 | Execution Model | Script-based Execution | Streamlit | Top-to-bottom script execution |
| L2 | Execution Model | Reactive Updates | Streamlit | Automatic rerun on interaction |
| L2 | Execution Model | Session State | Streamlit | State persistence across reruns |
| L3 | Display Functions | Text Elements | Display | Title, header, markdown, text |
| L3 | Display Functions | Data Display | Display | DataFrame, table, JSON, metric |
| L3 | Display Functions | Chart Elements | Display | Line chart, bar chart, scatter plot |
| L3 | Display Functions | Media Elements | Display | Image, audio, video |
| L4 | Text Elements | st.title() | Text Display | Large title text |
| L4 | Text Elements | st.header() | Text Display | Section header |
| L4 | Text Elements | st.subheader() | Text Display | Subsection header |
| L4 | Text Elements | st.markdown() | Text Display | Markdown text |
| L4 | Text Elements | st.write() | Text Display | Universal display function |
| L4 | Text Elements | st.code() | Text Display | Code block with syntax highlighting |
| L4 | Text Elements | st.latex() | Text Display | LaTeX mathematical notation |
| L5 | Data Display | st.dataframe() | Data Elements | Interactive DataFrame |
| L5 | Data Display | st.table() | Data Elements | Static table |
| L5 | Data Display | st.json() | Data Elements | JSON display |
| L5 | Data Display | st.metric() | Data Elements | Metric card with delta |
| L6 | Chart Elements | st.line_chart() | Visualization | Simple line chart |
| L6 | Chart Elements | st.bar_chart() | Visualization | Simple bar chart |
| L6 | Chart Elements | st.area_chart() | Visualization | Simple area chart |
| L6 | Chart Elements | st.pyplot() | Visualization | Matplotlib figures |
| L6 | Chart Elements | st.plotly_chart() | Visualization | Plotly interactive charts |
| L6 | Chart Elements | st.altair_chart() | Visualization | Altair declarative charts |
| L6 | Chart Elements | st.map() | Visualization | Geographic data visualization |
| L7 | Input Widgets | st.button() | User Input | Clickable button |
| L7 | Input Widgets | st.checkbox() | User Input | Boolean checkbox |
| L7 | Input Widgets | st.radio() | User Input | Single selection radio |
| L7 | Input Widgets | st.selectbox() | User Input | Dropdown selection |
| L7 | Input Widgets | st.multiselect() | User Input | Multiple selection |
| L7 | Input Widgets | st.slider() | User Input | Range slider |
| L7 | Input Widgets | st.text_input() | User Input | Single-line text |
| L7 | Input Widgets | st.text_area() | User Input | Multi-line text |
| L7 | Input Widgets | st.number_input() | User Input | Numeric input |
| L7 | Input Widgets | st.date_input() | User Input | Date picker |
| L7 | Input Widgets | st.time_input() | User Input | Time picker |
| L7 | Input Widgets | st.file_uploader() | User Input | File upload |
| L7 | Input Widgets | st.camera_input() | User Input | Camera capture |
| L7 | Input Widgets | st.color_picker() | User Input | Color selection |
| L8 | Layout Elements | st.sidebar | Layout | Sidebar container |
| L8 | Layout Elements | st.columns() | Layout | Horizontal columns |
| L8 | Layout Elements | st.container() | Layout | Logical container |
| L8 | Layout Elements | st.expander() | Layout | Collapsible section |
| L8 | Layout Elements | st.tabs() | Layout | Tabbed interface |
| L8 | Layout Elements | st.form() | Layout | Batch input submission |
| L9 | Status Elements | st.success() | Feedback | Success message |
| L9 | Status Elements | st.info() | Feedback | Information message |
| L9 | Status Elements | st.warning() | Feedback | Warning message |
| L9 | Status Elements | st.error() | Feedback | Error message |
| L9 | Status Elements | st.exception() | Feedback | Exception display |
| L9 | Status Elements | st.progress() | Feedback | Progress bar |
| L9 | Status Elements | st.spinner() | Feedback | Loading spinner |
| L9 | Status Elements | st.balloons() | Feedback | Celebratory animation |
| L9 | Status Elements | st.snow() | Feedback | Snow animation |
| L10 | Caching | @st.cache_data | Performance | Cache data transformations |
| L10 | Caching | @st.cache_resource | Performance | Cache connections/models |
| L10 | State | st.session_state | State Management | Persistent variables |
| L11 | Advanced | Custom Components | Extensibility | JavaScript-Python integration |
| L11 | Advanced | Multi-page Apps | Organization | Multiple app pages |
| L11 | Advanced | Theming | Customization | Custom color schemes |
Streamlit App Lifecycle
Understanding how Streamlit executes your code is crucial for building efficient applications.
Complete Execution Flow
graph TD
A[User Opens App] --> B[Streamlit Server Starts]
B --> C[Execute Script Top-to-Bottom]
C --> D[Render Elements in Order]
D --> E[Display App in Browser]
E --> F{User Interaction?}
F -->|No| E
F -->|Yes - Widget Change| G[Trigger Rerun]
F -->|Yes - Button Click| G
G --> H[Clear Current Display]
H --> I[Re-execute Entire Script]
I --> J[Read Session State]
J --> K[Execute Cached Functions]
K --> L[Execute Non-cached Code]
L --> M[Update Session State]
M --> D
style C fill:#e1ffe1
style G fill:#ffe1e1
style I fill:#fff4e1
style K fill:#e1f5ff
Key Characteristics:
- Top-to-Bottom Execution: Script runs from first line to last
- Automatic Reruns: Any widget interaction triggers complete rerun
- Stateless by Default: Variables reset on each rerun (unless using session_state)
- Caching: Expensive computations cached to avoid redundant work
- Sequential Rendering: Elements displayed in code order
Phase 1: Installation and Setup
1.1 Installation
1
2
3
4
5
6
7
8
9
10
11
| # Install Streamlit
pip install streamlit
# Install with additional dependencies
pip install streamlit pandas numpy matplotlib plotly
# Verify installation
streamlit --version
# Run hello world demo
streamlit hello
|
1.2 First Streamlit App
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| # app.py
import streamlit as st
# Title
st.title("My First Streamlit App π")
# Simple text
st.write("Hello, World!")
# Interactive widget
name = st.text_input("What's your name?")
if name:
st.write(f"Hello, {name}!")
|
Running the App:
1
2
3
4
5
6
7
8
9
10
11
| # Run from command line
streamlit run app.py
# Run with custom port
streamlit run app.py --server.port 8080
# Run with custom host
streamlit run app.py --server.address 0.0.0.0
# Run without browser auto-open
streamlit run app.py --server.headless true
|
Access Points:
- Local URL: http://localhost:8501
- Network URL: Displays in terminal for sharing
- Can manually refresh or enable auto-refresh
Phase 2: Display Elements
2.1 Text Elements
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
| import streamlit as st
# Title (largest)
st.title("This is a Title")
# Header
st.header("This is a Header")
# Subheader
st.subheader("This is a Subheader")
# Plain text
st.text("This is plain text")
# Markdown (supports full markdown syntax)
st.markdown("""
# H1 Heading
## H2 Heading
**Bold text** and *italic text*
- Bullet point 1
- Bullet point 2
[Link to Streamlit](https://streamlit.io)
""")
# Code block with syntax highlighting
st.code("""
def hello_world():
print("Hello, World!")
""", language="python")
# LaTeX mathematical notation
st.latex(r'''
a^2 + b^2 = c^2
''')
# Caption (small text)
st.caption("This is a caption")
# Magic commands (automatic display)
"This text is automatically displayed!"
x = 42 # This won't display
x # This WILL display (magic!)
|
2.2 Data Display
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
| import streamlit as st
import pandas as pd
import numpy as np
# Create sample data
df = pd.DataFrame({
'first column': [1, 2, 3, 4],
'second column': [10, 20, 30, 40]
})
# Interactive DataFrame
st.dataframe(df)
# Styled DataFrame with highlighting
st.dataframe(df.style.highlight_max(axis=0))
# Static table (not interactive)
st.table(df)
# JSON display
json_data = {
"name": "John",
"age": 30,
"city": "New York"
}
st.json(json_data)
# Metrics (KPI cards)
st.metric(
label="Temperature",
value="70 Β°F",
delta="1.2 Β°F"
)
# Multiple metrics in columns
col1, col2, col3 = st.columns(3)
col1.metric("Revenue", "$45,231", "+12%")
col2.metric("Users", "1,234", "-8%")
col3.metric("Conversion", "3.2%", "+0.5%")
|
2.3 Chart and Visualization
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
| import streamlit as st
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import plotly.express as px
# Sample data
chart_data = pd.DataFrame(
np.random.randn(20, 3),
columns=['a', 'b', 'c']
)
# Simple line chart
st.line_chart(chart_data)
# Simple bar chart
st.bar_chart(chart_data)
# Simple area chart
st.area_chart(chart_data)
# Matplotlib figure
fig, ax = plt.subplots()
ax.hist(np.random.randn(100), bins=20)
st.pyplot(fig)
# Plotly interactive chart
df = px.data.iris()
fig = px.scatter(df, x="sepal_width", y="sepal_length", color="species")
st.plotly_chart(fig)
# Map visualization (requires lat/lon columns)
map_data = pd.DataFrame(
np.random.randn(1000, 2) / [50, 50] + [37.76, -122.4],
columns=['lat', 'lon']
)
st.map(map_data)
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| import streamlit as st
from PIL import Image
# Display image
image = Image.open('path/to/image.jpg')
st.image(image, caption='Sample Image', use_column_width=True)
# Display from URL
st.image('https://example.com/image.jpg')
# Display audio
audio_file = open('path/to/audio.mp3', 'rb')
st.audio(audio_file.read(), format='audio/mp3')
# Display video
video_file = open('path/to/video.mp4', 'rb')
st.video(video_file.read())
# Display from YouTube
st.video('https://www.youtube.com/watch?v=dQw4w9WgXcQ')
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
| import streamlit as st
# Button
if st.button('Click me'):
st.write('Button clicked!')
# Checkbox
agree = st.checkbox('I agree to terms and conditions')
if agree:
st.write('Great!')
# Radio buttons (single selection)
genre = st.radio(
"What's your favorite movie genre?",
('Comedy', 'Drama', 'Documentary')
)
st.write(f'You selected: {genre}')
# Selectbox (dropdown)
option = st.selectbox(
'How would you like to be contacted?',
('Email', 'Phone', 'Mobile')
)
st.write(f'You selected: {option}')
# Multiselect (multiple selections)
options = st.multiselect(
'What are your favorite colors?',
['Green', 'Yellow', 'Red', 'Blue'],
default=['Yellow', 'Red']
)
st.write(f'You selected: {options}')
# Slider
age = st.slider('How old are you?', 0, 100, 25)
st.write(f"I'm {age} years old")
# Range slider
values = st.slider(
'Select a range of values',
0.0, 100.0, (25.0, 75.0)
)
st.write('Range:', values)
|
3.2 Text and Numeric Input
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| import streamlit as st
# Text input
name = st.text_input('Enter your name', 'John Doe')
st.write(f'Hello, {name}!')
# Text area (multiline)
message = st.text_area('Enter your message', 'Type here...')
st.write(f'Message length: {len(message)} characters')
# Number input
number = st.number_input('Enter a number', min_value=0, max_value=100, value=50, step=1)
st.write(f'You entered: {number}')
# Password input
password = st.text_input('Enter password', type='password')
|
1
2
3
4
5
6
7
8
9
10
| import streamlit as st
from datetime import date, time
# Date input
d = st.date_input("When's your birthday", date(2000, 1, 1))
st.write('Your birthday is:', d)
# Time input
t = st.time_input('Set an alarm for', time(8, 45))
st.write('Alarm is set for:', t)
|
3.4 File Upload
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| import streamlit as st
import pandas as pd
# File uploader
uploaded_file = st.file_uploader("Choose a CSV file", type=['csv'])
if uploaded_file is not None:
# Read file
df = pd.read_csv(uploaded_file)
st.write("File uploaded successfully!")
st.dataframe(df)
# File details
st.write(f"Filename: {uploaded_file.name}")
st.write(f"File size: {uploaded_file.size} bytes")
# Multiple file upload
uploaded_files = st.file_uploader(
"Choose multiple files",
accept_multiple_files=True
)
for uploaded_file in uploaded_files:
st.write(f"Filename: {uploaded_file.name}")
# Camera input
camera_photo = st.camera_input("Take a picture")
if camera_photo:
st.image(camera_photo)
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| import streamlit as st
# Color picker
color = st.color_picker('Pick a color', '#00f900')
st.write(f'The current color is {color}')
# Download button
csv_data = df.to_csv(index=False)
st.download_button(
label="Download data as CSV",
data=csv_data,
file_name='data.csv',
mime='text/csv'
)
# Link button
st.link_button("Go to Streamlit", "https://streamlit.io")
|
Phase 4: Layout and Containers
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| import streamlit as st
# Add to sidebar
st.sidebar.title("Sidebar Title")
st.sidebar.write("This is in the sidebar")
# Sidebar widgets
option = st.sidebar.selectbox(
'Select an option',
['Option 1', 'Option 2', 'Option 3']
)
# Sidebar slider
slider_value = st.sidebar.slider('Select a value', 0, 100, 50)
# Main content
st.title("Main Content")
st.write(f"Selected option: {option}")
st.write(f"Slider value: {slider_value}")
|
4.2 Columns
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| import streamlit as st
# Create two equal columns
col1, col2 = st.columns(2)
with col1:
st.header("Column 1")
st.write("This is column 1")
st.button("Button 1")
with col2:
st.header("Column 2")
st.write("This is column 2")
st.button("Button 2")
# Columns with custom widths
col1, col2, col3 = st.columns([3, 1, 1])
col1.write("This column is 3x wider")
col2.write("Column 2")
col3.write("Column 3")
|
4.3 Containers and Expanders
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| import streamlit as st
# Container (logical grouping)
with st.container():
st.write("This is inside a container")
st.button("Container button")
# Expander (collapsible section)
with st.expander("Click to expand"):
st.write("Hidden content that can be expanded")
st.image("image.jpg")
# Multiple expanders
with st.expander("Section 1"):
st.write("Content of section 1")
with st.expander("Section 2"):
st.write("Content of section 2")
|
4.4 Tabs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| import streamlit as st
# Create tabs
tab1, tab2, tab3 = st.tabs(["π Chart", "π Data", "π Notes"])
with tab1:
st.header("Chart Tab")
st.line_chart([1, 2, 3, 4])
with tab2:
st.header("Data Tab")
st.write("Data goes here")
with tab3:
st.header("Notes Tab")
st.text_area("Enter your notes")
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| import streamlit as st
# Form (batch input submission)
with st.form("my_form"):
st.write("Inside the form")
name = st.text_input("Name")
age = st.slider("Age", 0, 100, 25)
submit = st.form_submit_button("Submit")
if submit:
st.write(f"Name: {name}, Age: {age}")
# Form outside with statement
form = st.form("another_form")
form.text_input("Email")
form.slider("Rating", 1, 5)
submitted = form.form_submit_button("Submit")
|
Phase 5: Session State
5.1 Understanding Session State
Session State allows you to persist variables across reruns.
1
2
3
4
5
6
7
8
9
10
11
12
13
| import streamlit as st
# Initialize session state
if 'counter' not in st.session_state:
st.session_state.counter = 0
# Button that increments counter
if st.button('Increment'):
st.session_state.counter += 1
st.write(f"Counter: {st.session_state.counter}")
# Without session state, counter would reset to 0 on every click!
|
5.2 Session State Patterns
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
| import streamlit as st
# Pattern 1: Simple counter
if 'count' not in st.session_state:
st.session_state.count = 0
increment = st.button('Increment')
if increment:
st.session_state.count += 1
decrement = st.button('Decrement')
if decrement:
st.session_state.count -= 1
st.write('Count:', st.session_state.count)
# Pattern 2: Form with persistent data
if 'user_data' not in st.session_state:
st.session_state.user_data = {}
name = st.text_input('Name', value=st.session_state.user_data.get('name', ''))
email = st.text_input('Email', value=st.session_state.user_data.get('email', ''))
if st.button('Save'):
st.session_state.user_data = {'name': name, 'email': email}
st.success('Data saved!')
# Pattern 3: Page navigation
if 'page' not in st.session_state:
st.session_state.page = 'home'
if st.session_state.page == 'home':
st.title("Home Page")
if st.button('Go to Settings'):
st.session_state.page = 'settings'
elif st.session_state.page == 'settings':
st.title("Settings Page")
if st.button('Go to Home'):
st.session_state.page = 'home'
|
5.3 Callbacks with Session State
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
| import streamlit as st
# Callback function
def increment_counter():
st.session_state.counter += 1
def decrement_counter():
st.session_state.counter -= 1
# Initialize
if 'counter' not in st.session_state:
st.session_state.counter = 0
# Buttons with callbacks
st.button('Increment', on_click=increment_counter)
st.button('Decrement', on_click=decrement_counter)
st.write(f"Counter: {st.session_state.counter}")
# Widget with callback
def on_change():
st.session_state.last_selection = st.session_state.selectbox
option = st.selectbox(
'Choose option',
['A', 'B', 'C'],
key='selectbox',
on_change=on_change
)
if 'last_selection' in st.session_state:
st.write(f"Last selection: {st.session_state.last_selection}")
|
6.1 Understanding Caching
Streamlit caches function results to avoid redundant computations.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| import streamlit as st
import pandas as pd
import time
# Without caching (SLOW - runs every rerun)
def load_data_slow():
time.sleep(5) # Simulate expensive operation
return pd.DataFrame({'col1': [1, 2], 'col2': [3, 4]})
# With caching (FAST - runs once, cached thereafter)
@st.cache_data
def load_data_fast():
time.sleep(5) # Only slow first time
return pd.DataFrame({'col1': [1, 2], 'col2': [3, 4]})
# Use cached function
df = load_data_fast()
st.dataframe(df)
|
6.2 Cache Data vs Cache Resource
@st.cache_data - For data transformations:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| import streamlit as st
import pandas as pd
@st.cache_data
def load_csv(file_path):
"""Cache data loading"""
return pd.read_csv(file_path)
@st.cache_data
def process_data(df):
"""Cache data processing"""
# Expensive data transformation
return df.groupby('category').sum()
# Use cached functions
df = load_csv('data.csv')
processed = process_data(df)
st.dataframe(processed)
|
@st.cache_resource - For connections and models:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| import streamlit as st
import joblib
from sqlalchemy import create_engine
@st.cache_resource
def init_database():
"""Cache database connection"""
return create_engine('postgresql://user:pass@localhost/db')
@st.cache_resource
def load_model():
"""Cache ML model"""
return joblib.load('model.pkl')
# Use cached resources
db = init_database()
model = load_model()
# Make prediction
input_data = st.number_input('Input value')
prediction = model.predict([[input_data]])
st.write(f"Prediction: {prediction[0]}")
|
6.3 Cache Control
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| import streamlit as st
# Cache with TTL (time to live)
@st.cache_data(ttl=3600) # Cache for 1 hour
def get_api_data():
# Fetch from API
return data
# Cache with custom key
@st.cache_data
def load_data(file_path, columns):
return pd.read_csv(file_path, usecols=columns)
# Clear specific cache
st.cache_data.clear()
# Clear all caches
st.cache_resource.clear()
# Manual cache control
if st.button("Clear Cache"):
st.cache_data.clear()
st.success("Cache cleared!")
|
Phase 7: Status and Progress
7.1 Status Messages
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| import streamlit as st
# Success message
st.success("Operation completed successfully!")
# Information message
st.info("This is an information message")
# Warning message
st.warning("This is a warning")
# Error message
st.error("An error occurred!")
# Exception display
try:
result = 1 / 0
except Exception as e:
st.exception(e)
|
7.2 Progress Indicators
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| import streamlit as st
import time
# Progress bar
progress_bar = st.progress(0)
for i in range(100):
time.sleep(0.01)
progress_bar.progress(i + 1)
st.success("Complete!")
# Spinner (loading indicator)
with st.spinner('Loading...'):
time.sleep(3)
st.success('Done!')
# Status container
with st.status("Downloading data...") as status:
st.write("Searching for data...")
time.sleep(2)
st.write("Found URL.")
time.sleep(1)
st.write("Downloading data...")
time.sleep(1)
status.update(label="Download complete!", state="complete", expanded=False)
|
7.3 Celebrations
1
2
3
4
5
6
7
8
9
| import streamlit as st
# Balloons
if st.button("Celebrate!"):
st.balloons()
# Snow
if st.button("Let it snow!"):
st.snow()
|
Phase 8: Advanced Features
8.1 Multi-page Apps
Project Structure:
1
2
3
4
5
6
| myapp/
βββ Home.py (or main.py)
βββ pages/
βββ 1_π_Dashboard.py
βββ 2_π_Analytics.py
βββ 3_βοΈ_Settings.py
|
Home.py:
1
2
3
4
5
6
7
8
9
10
11
| import streamlit as st
st.set_page_config(
page_title="My App",
page_icon="π ",
layout="wide"
)
st.title("Home Page")
st.write("Welcome to my multi-page app!")
st.write("Use the sidebar to navigate")
|
pages/1_π_Dashboard.py:
1
2
3
4
5
6
7
8
9
10
11
12
13
| import streamlit as st
import pandas as pd
import numpy as np
st.title("Dashboard")
# Generate sample data
chart_data = pd.DataFrame(
np.random.randn(20, 3),
columns=['a', 'b', 'c']
)
st.line_chart(chart_data)
|
Run with: streamlit run Home.py
8.2 Configuration and Theming
.streamlit/config.toml:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| [theme]
primaryColor = "#F63366"
backgroundColor = "#FFFFFF"
secondaryBackgroundColor = "#F0F2F6"
textColor = "#262730"
font = "sans serif"
[server]
port = 8501
headless = false
enableCORS = false
[browser]
gatherUsageStats = false
serverAddress = "localhost"
|
Programmatic Configuration:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| import streamlit as st
# Page configuration
st.set_page_config(
page_title="My App",
page_icon="π",
layout="wide", # or "centered"
initial_sidebar_state="expanded", # or "collapsed"
menu_items={
'Get Help': 'https://www.example.com/help',
'Report a bug': "https://www.example.com/bug",
'About': "# This is a header. This is an *extremely* cool app!"
}
)
|
8.3 Custom Components
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| import streamlit as st
import streamlit.components.v1 as components
# Embed HTML
html_code = """
<div style="background-color: lightblue; padding: 10px;">
<h2>Custom HTML Component</h2>
<p>This is rendered from HTML</p>
</div>
"""
components.html(html_code, height=200)
# Embed iframe
components.iframe("https://www.example.com", height=600)
# JavaScript communication
js_code = """
<script>
const value = Math.random();
window.parent.postMessage({type: 'streamlit:setComponentValue', value: value}, '*');
</script>
"""
result = components.html(js_code, height=0)
if result:
st.write(f"Random value from JS: {result}")
|
8.4 Query Parameters
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| import streamlit as st
# Get query parameters
query_params = st.query_params
# Read parameter
page = query_params.get("page", "home")
user_id = query_params.get("user_id", None)
st.write(f"Current page: {page}")
if user_id:
st.write(f"User ID: {user_id}")
# Set query parameters
if st.button("Go to Dashboard"):
st.query_params.update({"page": "dashboard"})
st.rerun()
# URL: http://localhost:8501/?page=dashboard&user_id=123
|
Phase 9: Working with Data
9.1 Data Loading and Display
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
| import streamlit as st
import pandas as pd
import numpy as np
@st.cache_data
def load_data():
# Load from CSV
df = pd.read_csv('data.csv')
return df
# Load and display
df = load_data()
# Interactive dataframe
st.dataframe(
df,
use_container_width=True,
height=400
)
# Column configuration
st.dataframe(
df,
column_config={
"price": st.column_config.NumberColumn(
"Price (USD)",
help="Product price in USD",
format="$%.2f"
),
"rating": st.column_config.ProgressColumn(
"Rating",
help="Customer rating",
min_value=0,
max_value=5
),
"url": st.column_config.LinkColumn(
"Website",
display_text="Visit"
)
}
)
|
9.2 Data Editing
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| import streamlit as st
import pandas as pd
# Create sample dataframe
df = pd.DataFrame({
'name': ['Alice', 'Bob', 'Charlie'],
'age': [25, 30, 35],
'city': ['NYC', 'LA', 'Chicago']
})
# Editable dataframe
edited_df = st.data_editor(
df,
num_rows="dynamic", # Allow adding/removing rows
use_container_width=True
)
if st.button("Save Changes"):
st.session_state.saved_data = edited_df
st.success("Data saved!")
# Display saved data
if 'saved_data' in st.session_state:
st.write("Saved data:")
st.dataframe(st.session_state.saved_data)
|
9.3 Data Filtering
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
| import streamlit as st
import pandas as pd
@st.cache_data
def load_data():
return pd.read_csv('data.csv')
df = load_data()
# Sidebar filters
st.sidebar.header("Filters")
# Numeric filter
min_age, max_age = st.sidebar.slider(
'Select age range',
int(df['age'].min()),
int(df['age'].max()),
(20, 50)
)
# Category filter
categories = st.sidebar.multiselect(
'Select categories',
options=df['category'].unique(),
default=df['category'].unique()
)
# Apply filters
filtered_df = df[
(df['age'] >= min_age) &
(df['age'] <= max_age) &
(df['category'].isin(categories))
]
# Display results
st.write(f"Showing {len(filtered_df)} of {len(df)} records")
st.dataframe(filtered_df)
|
9.4 Data Export
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
| import streamlit as st
import pandas as pd
df = pd.read_csv('data.csv')
# Export to CSV
csv = df.to_csv(index=False).encode('utf-8')
st.download_button(
label="Download CSV",
data=csv,
file_name='data.csv',
mime='text/csv'
)
# Export to Excel
from io import BytesIO
buffer = BytesIO()
with pd.ExcelWriter(buffer, engine='xlsxwriter') as writer:
df.to_excel(writer, sheet_name='Sheet1', index=False)
writer.close()
st.download_button(
label="Download Excel",
data=buffer,
file_name='data.xlsx',
mime='application/vnd.ms-excel'
)
# Export to JSON
json_data = df.to_json(orient='records')
st.download_button(
label="Download JSON",
data=json_data,
file_name='data.json',
mime='application/json'
)
|
Phase 10: Machine Learning Integration
10.1 Loading and Using ML Models
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
| import streamlit as st
import joblib
import pandas as pd
@st.cache_resource
def load_model():
"""Load trained model"""
return joblib.load('model.pkl')
@st.cache_resource
def load_scaler():
"""Load feature scaler"""
return joblib.load('scaler.pkl')
# Load resources
model = load_model()
scaler = load_scaler()
# UI for input
st.title("ML Model Prediction")
col1, col2 = st.columns(2)
with col1:
feature1 = st.number_input("Feature 1", value=0.0)
feature2 = st.number_input("Feature 2", value=0.0)
with col2:
feature3 = st.number_input("Feature 3", value=0.0)
feature4 = st.number_input("Feature 4", value=0.0)
if st.button("Predict"):
# Prepare input
input_data = pd.DataFrame({
'feature1': [feature1],
'feature2': [feature2],
'feature3': [feature3],
'feature4': [feature4]
})
# Scale features
scaled_data = scaler.transform(input_data)
# Make prediction
prediction = model.predict(scaled_data)
probability = model.predict_proba(scaled_data)
# Display results
st.success(f"Prediction: {prediction[0]}")
st.write(f"Probability: {probability[0][1]:.2%}")
|
10.2 Real-time Predictions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
| import streamlit as st
import numpy as np
from sklearn.datasets import load_iris
from sklearn.ensemble import RandomForestClassifier
@st.cache_resource
def train_model():
"""Train and cache model"""
iris = load_iris()
model = RandomForestClassifier()
model.fit(iris.data, iris.target)
return model, iris
model, iris = train_model()
st.title("Iris Flower Classifier")
# Sliders for input
sepal_length = st.slider('Sepal Length', 4.0, 8.0, 5.0)
sepal_width = st.slider('Sepal Width', 2.0, 4.5, 3.0)
petal_length = st.slider('Petal Length', 1.0, 7.0, 4.0)
petal_width = st.slider('Petal Width', 0.1, 2.5, 1.0)
# Real-time prediction (no button needed!)
input_data = np.array([[sepal_length, sepal_width, petal_length, petal_width]])
prediction = model.predict(input_data)[0]
probability = model.predict_proba(input_data)[0]
# Display prediction
st.subheader("Prediction")
st.write(f"Class: {iris.target_names[prediction]}")
# Display probabilities
st.subheader("Confidence")
for i, class_name in enumerate(iris.target_names):
st.write(f"{class_name}: {probability[i]:.2%}")
st.progress(float(probability[i]))
|
10.3 Model Comparison
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
| import streamlit as st
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC
from sklearn.metrics import accuracy_score, classification_report
st.title("Model Comparison")
# Load data
@st.cache_data
def load_data():
# Your data loading logic
return X, y
X, y = load_data()
# Split data
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
# Model selection
models = {
'Random Forest': RandomForestClassifier(),
'Logistic Regression': LogisticRegression(),
'SVM': SVC()
}
selected_model = st.selectbox("Select Model", list(models.keys()))
if st.button("Train and Evaluate"):
with st.spinner(f"Training {selected_model}..."):
model = models[selected_model]
model.fit(X_train, y_train)
# Predictions
y_pred = model.predict(X_test)
# Metrics
accuracy = accuracy_score(y_test, y_pred)
st.success(f"Training Complete!")
st.metric("Accuracy", f"{accuracy:.2%}")
# Classification report
report = classification_report(y_test, y_pred, output_dict=True)
st.dataframe(pd.DataFrame(report).transpose())
|
Phase 11: Best Practices
11.1 Code Organization
Recommended Structure:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
| # app.py
import streamlit as st
from utils import load_data, process_data
from models import predict
# Page config at the top
st.set_page_config(
page_title="My App",
layout="wide"
)
# Functions
@st.cache_data
def get_data():
return load_data()
# Main app
def main():
st.title("My Streamlit App")
# Load data
data = get_data()
# Sidebar
with st.sidebar:
st.header("Settings")
option = st.selectbox("Choose option", ['A', 'B', 'C'])
# Main content
if option == 'A':
show_page_a(data)
elif option == 'B':
show_page_b(data)
else:
show_page_c(data)
def show_page_a(data):
st.header("Page A")
st.dataframe(data)
def show_page_b(data):
st.header("Page B")
st.line_chart(data)
def show_page_c(data):
st.header("Page C")
st.write("Page C content")
if __name__ == "__main__":
main()
|
β
DO:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
| # 1. Cache expensive operations
@st.cache_data
def load_large_dataset():
return pd.read_csv('large_file.csv')
# 2. Cache ML models
@st.cache_resource
def load_model():
return joblib.load('model.pkl')
# 3. Use columns for better layout
col1, col2 = st.columns(2)
with col1:
st.write("Column 1")
with col2:
st.write("Column 2")
# 4. Use forms to batch inputs
with st.form("my_form"):
name = st.text_input("Name")
age = st.slider("Age", 0, 100)
submitted = st.form_submit_button("Submit")
# 5. Use session state for persistence
if 'data' not in st.session_state:
st.session_state.data = expensive_computation()
# 6. Minimize reruns with callbacks
def update_value():
st.session_state.result = process(st.session_state.input_value)
st.text_input("Input", key="input_value", on_change=update_value)
|
β DONβT:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
| # 1. Don't load data without caching
def load_data(): # Bad - loads on every rerun
return pd.read_csv('data.csv')
@st.cache_data
def load_data(): # Good - cached
return pd.read_csv('data.csv')
# 2. Don't use global variables
counter = 0 # Bad - resets on rerun
if st.button("Increment"):
counter += 1 # Doesn't work!
# Use session state instead
if 'counter' not in st.session_state:
st.session_state.counter = 0
if st.button("Increment"):
st.session_state.counter += 1 # Works!
# 3. Don't repeatedly create widgets in loops
for i in range(1000): # Bad - creates 1000 widgets
st.write(f"Item {i}")
# Use dataframe instead
df = pd.DataFrame({'Item': [f"Item {i}" for i in range(1000)]})
st.dataframe(df) # Good - single widget
# 4. Don't ignore wide layout for data apps
st.set_page_config(layout="wide") # Good for data-heavy apps
# 5. Don't forget to handle errors
try:
result = risky_operation()
except Exception as e:
st.error(f"Error: {str(e)}")
|
11.3 User Experience Tips
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
| import streamlit as st
# 1. Loading states
with st.spinner("Loading data..."):
data = load_data()
# 2. Clear feedback
if st.button("Save"):
save_data()
st.success("Data saved successfully!")
# 3. Help text
st.text_input("Email", help="Enter your email address")
# 4. Expanders for optional content
with st.expander("Advanced Settings"):
st.slider("Parameter", 0, 100)
# 5. Empty placeholders for dynamic content
placeholder = st.empty()
for i in range(100):
placeholder.write(f"Progress: {i}%")
time.sleep(0.1)
placeholder.success("Complete!")
# 6. Conditional display
if st.checkbox("Show raw data"):
st.write(df)
# 7. Clear instructions
st.markdown("""
### How to use this app:
1. Upload your data
2. Select parameters
3. Click 'Run Analysis'
""")
|
Phase 12: Deployment
Deployment Steps:
- Prepare your repository:
1
2
3
4
5
6
| myapp/
βββ app.py
βββ requirements.txt
βββ .streamlit/
β βββ config.toml
βββ README.md
|
requirements.txt:
1
2
3
4
| streamlit==1.28.0
pandas==2.0.0
numpy==1.24.0
plotly==5.17.0
|
Push to GitHub
Deploy on Streamlit Cloud:
- Go to share.streamlit.io
- Connect GitHub repository
- Select main file (app.py)
- Click βDeployβ
12.2 Docker Deployment
Dockerfile:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8501
HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health
ENTRYPOINT ["streamlit", "run", "app.py", "--server.port=8501", "--server.address=0.0.0.0"]
|
docker-compose.yml:
1
2
3
4
5
6
7
8
9
10
11
12
| version: '3.8'
services:
streamlit:
build: .
ports:
- "8501:8501"
volumes:
- ./data:/app/data
environment:
- STREAMLIT_SERVER_PORT=8501
restart: unless-stopped
|
Build and run:
1
2
3
4
5
6
7
8
| # Build
docker build -t my-streamlit-app .
# Run
docker run -p 8501:8501 my-streamlit-app
# Or with docker-compose
docker-compose up -d
|
12.3 Production Configuration
.streamlit/config.toml (production):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| [server]
port = 8501
enableCORS = false
enableXsrfProtection = true
maxUploadSize = 200
[browser]
gatherUsageStats = false
[client]
toolbarMode = "minimal"
showErrorDetails = false
[runner]
magicEnabled = true
fastReruns = true
|
12.4 Environment Variables
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| import streamlit as st
import os
# Read from environment
API_KEY = os.getenv('API_KEY')
DATABASE_URL = os.getenv('DATABASE_URL')
# Secrets management (Streamlit Cloud)
# .streamlit/secrets.toml (local, not committed)
# [connections.database]
# url = "postgresql://..."
# Access secrets
db_url = st.secrets["connections"]["database"]["url"]
api_key = st.secrets["api_key"]
|
Streamlit vs Traditional Web Frameworks
| Metric | Streamlit | Flask + HTML/JS | Django |
|---|
| Lines of Code (Simple Dashboard) | 50 | 300+ | 400+ |
| Development Time | Hours | Days | Days |
| Learning Curve | Gentle | Steep | Steep |
| Ideal For | Data Apps, Prototypes | General Web Apps | Full Web Apps |
| Frontend Skills Required | None | HTML/CSS/JS | HTML/CSS/JS |
| Backend Complexity | Low | Medium | High |
Caching Impact
Without Caching: $ \text{Total Time} = N_{\text{reruns}} \times T_{\text{computation}} $
With Caching: $ \text{Total Time} = T_{\text{computation}} + (N_{\text{reruns}} - 1) \times T_{\text{cache lookup}} $
Where $T_{\text{cache lookup}} \ll T_{\text{computation}}$
Example:
- Computation time: 10 seconds
- Cache lookup: 0.01 seconds
- 100 reruns
Without cache: $100 \times 10 = 1000$ seconds With cache: $10 + (99 \times 0.01) = 10.99$ seconds
Speedup: $\frac{1000}{10.99} \approx 91\times$
Common Patterns and Solutions
Pattern 1: Multi-Page Navigation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| import streamlit as st
def render_page_home():
st.title("Home")
if st.button("Go to Dashboard"):
st.session_state.page = "dashboard"
st.rerun()
def render_page_dashboard():
st.title("Dashboard")
if st.button("Go to Home"):
st.session_state.page = "home"
st.rerun()
# Initialize
if 'page' not in st.session_state:
st.session_state.page = "home"
# Route
if st.session_state.page == "home":
render_page_home()
else:
render_page_dashboard()
|
Pattern 2: Data Pipeline
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| import streamlit as st
import pandas as pd
st.title("Data Processing Pipeline")
# Step 1: Upload
uploaded_file = st.file_uploader("Upload CSV", type=['csv'])
if uploaded_file:
# Step 2: Load
df = pd.read_csv(uploaded_file)
st.success("File loaded!")
# Step 3: Preview
with st.expander("Preview Raw Data"):
st.dataframe(df.head())
# Step 4: Clean
if st.checkbox("Remove missing values"):
df = df.dropna()
st.info(f"Removed missing values. New shape: {df.shape}")
# Step 5: Transform
if st.checkbox("Normalize numeric columns"):
numeric_cols = df.select_dtypes(include=['float64', 'int64']).columns
df[numeric_cols] = (df[numeric_cols] - df[numeric_cols].mean()) / df[numeric_cols].std()
st.info("Normalized numeric columns")
# Step 6: Export
csv = df.to_csv(index=False).encode('utf-8')
st.download_button("Download Processed Data", csv, "processed.csv")
|
Pattern 3: Dashboard with Filters
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
| import streamlit as st
import pandas as pd
import plotly.express as px
st.set_page_config(layout="wide")
# Load data
@st.cache_data
def load_data():
return pd.read_csv('sales_data.csv')
df = load_data()
# Sidebar filters
with st.sidebar:
st.header("Filters")
date_range = st.date_input("Date Range", [df['date'].min(), df['date'].max()])
regions = st.multiselect("Regions", df['region'].unique(), default=df['region'].unique())
# Apply filters
filtered_df = df[
(df['date'] >= date_range[0]) &
(df['date'] <= date_range[1]) &
(df['region'].isin(regions))
]
# Metrics
col1, col2, col3, col4 = st.columns(4)
col1.metric("Total Sales", f"${filtered_df['sales'].sum():,.0f}")
col2.metric("Total Orders", f"{len(filtered_df):,}")
col3.metric("Avg Order Value", f"${filtered_df['sales'].mean():,.2f}")
col4.metric("Unique Customers", f"{filtered_df['customer_id'].nunique():,}")
# Charts
col1, col2 = st.columns(2)
with col1:
fig = px.line(filtered_df, x='date', y='sales', title='Sales Over Time')
st.plotly_chart(fig, use_container_width=True)
with col2:
fig = px.bar(filtered_df.groupby('region')['sales'].sum().reset_index(),
x='region', y='sales', title='Sales by Region')
st.plotly_chart(fig, use_container_width=True)
# Data table
st.dataframe(filtered_df, use_container_width=True)
|
Conclusion
Streamlit has democratized web app development for data scientists and machine learning engineers, eliminating the traditional barriers of frontend development. Its intuitive Python-first approach, combined with powerful caching mechanisms and automatic UI rendering, enables rapid prototyping and deployment of data-driven applications.
Key Takeaways:
- Pure Python - No HTML, CSS, or JavaScript required
- Reactive by default - Automatic reruns on interaction
- Caching is essential - Use @st.cache_data and @st.cache_resource
- Session state - Persist data across reruns
- Layout flexibility - Columns, tabs, expanders, sidebar
- Quick prototyping - Hours instead of days
- Easy deployment - Streamlit Community Cloud, Docker, cloud platforms
- Data-focused - Built for DataFrames, charts, and ML models
- Component ecosystem - Extend with custom components
- Production-ready - With proper caching and optimization
By following the practices outlined in this guide and understanding Streamlitβs execution model, youβll be equipped to rapidly build, iterate, and deploy interactive data applications that transform insights into impact.
References
Streamlit Official Documentation
Streamlit API Reference
Streamlit Getting Started Guide
Streamlit Caching Guide
Streamlit Session State
Multi-page Apps
Streamlit Community Cloud
Streamlit Components
Streamlit GitHub Repository
Streamlit Blog
Last Updated: November 12, 2025