Streamlit is a Python framework for creating interactive web applications with minimal code. It transforms Python scripts into shareable web apps, making it perfect for researchers who want to build interactive visualizations and dashboards without learning web technologies.
Key Features
- Pure Python: No HTML, CSS, or JavaScript required
- Reactive updates: UI automatically updates when inputs change
- Rich widgets: Sliders, selectboxes, file uploaders, and more
- Built-in components: Charts, maps, tables, and media
- Easy deployment: Share apps via Streamlit Cloud
- Caching: Optimize performance with
@st.cache_data
Getting Started
import streamlit as st
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# Add title and description
st.title("Neural Data Explorer")
st.write("Interactive visualization of spike train data")
# Sidebar controls
st.sidebar.header("Parameters")
n_neurons = st.sidebar.slider("Number of neurons", 1, 100, 10)
duration = st.sidebar.slider("Duration (s)", 1, 60, 10)
firing_rate = st.sidebar.slider("Mean firing rate (Hz)", 1, 50, 10)
# Generate synthetic spike data
spike_times = []
for neuron in range(n_neurons):
n_spikes = np.random.poisson(firing_rate * duration)
times = np.sort(np.random.uniform(0, duration, n_spikes))
spike_times.append(times)
# Raster plot
fig, ax = plt.subplots(figsize=(10, 6))
for i, times in enumerate(spike_times):
ax.scatter(times, [i] * len(times), s=1, color='black')
ax.set_xlabel('Time (s)')
ax.set_ylabel('Neuron #')
ax.set_title('Spike Raster')
st.pyplot(fig)
# Compute and display statistics
all_spikes = np.concatenate(spike_times)
st.metric("Total spikes", len(all_spikes))
st.metric("Average firing rate", f"{len(all_spikes) / (n_neurons * duration):.2f} Hz")
Run with:
streamlit run app.py
Real-World Example: Calcium Imaging Viewer
import streamlit as st
import numpy as np
import matplotlib.pyplot as plt
from pathlib import Path
st.title("Calcium Imaging ROI Explorer")
# File upload
uploaded_file = st.file_uploader("Upload calcium traces (.npy)", type="npy")
if uploaded_file is not None:
# Load data
traces = np.load(uploaded_file) # Shape: (n_rois, n_timepoints)
n_rois, n_timepoints = traces.shape
# Sidebar controls
st.sidebar.header("Display Settings")
roi_idx = st.sidebar.slider("ROI Index", 0, n_rois - 1, 0)
window_start = st.sidebar.slider("Time window start", 0, n_timepoints - 100, 0)
window_end = st.sidebar.slider("Time window end", window_start + 1, n_timepoints,
min(window_start + 1000, n_timepoints))
baseline_method = st.sidebar.selectbox("Baseline correction",
["None", "Mean", "Percentile"])
# Process trace
trace = traces[roi_idx, window_start:window_end]
time = np.arange(len(trace)) / 30.0 # Assuming 30 Hz
if baseline_method == "Mean":
trace = (trace - np.mean(trace)) / np.std(trace)
elif baseline_method == "Percentile":
baseline = np.percentile(trace, 10)
trace = (trace - baseline) / baseline
# Plot trace
fig, ax = plt.subplots(figsize=(12, 4))
ax.plot(time, trace, linewidth=0.5)
ax.set_xlabel('Time (s)')
ax.set_ylabel('ΔF/F' if baseline_method == "Percentile" else 'Signal')
ax.set_title(f'ROI {roi_idx}')
st.pyplot(fig)
# Summary statistics
col1, col2, col3 = st.columns(3)
col1.metric("Mean", f"{np.mean(trace):.3f}")
col2.metric("Std Dev", f"{np.std(trace):.3f}")
col3.metric("Max", f"{np.max(trace):.3f}")
# Show peak detection
if st.checkbox("Detect peaks"):
from scipy.signal import find_peaks
threshold = st.slider("Peak threshold", 0.0, 5.0, 2.0, 0.1)
peaks, _ = find_peaks(trace, height=threshold)
st.write(f"Found {len(peaks)} peaks")
# Overlay peaks on plot
fig, ax = plt.subplots(figsize=(12, 4))
ax.plot(time, trace, linewidth=0.5)
ax.scatter(time[peaks], trace[peaks], color='red', s=20, zorder=5)
ax.axhline(threshold, color='gray', linestyle='--', alpha=0.5)
ax.set_xlabel('Time (s)')
ax.set_ylabel('Signal')
st.pyplot(fig)
else:
st.info("Upload a file to get started")
Caching for Performance
import streamlit as st
import pandas as pd
@st.cache_data
def load_large_dataset(filepath):
"""Cache expensive data loading operations."""
return pd.read_parquet(filepath)
@st.cache_data
def compute_statistics(df):
"""Cache expensive computations."""
return df.describe()
# Data only loaded once, then cached
df = load_large_dataset("experiment_data.parquet")
stats = compute_statistics(df)
st.dataframe(stats)
Multi-Page Applications
# pages/1_Data_Explorer.py
import streamlit as st
st.title("Data Explorer")
st.write("Explore your experimental data")
# pages/2_Analysis.py
import streamlit as st
st.title("Statistical Analysis")
st.write("Run statistical tests")
Streamlit automatically creates navigation for files in the pages/ directory.
When to Use Streamlit
Best for:
- Rapid prototyping of data applications
- Interactive parameter exploration
- Sharing results with collaborators
- Building internal lab tools
- Educational demonstrations
Consider alternatives for:
- Complex multi-user applications (use Django/Flask)
- Real-time high-frequency updates (use Dash)
- Static reports (use Jupyter notebooks)
- Publication figures (use matplotlib/seaborn directly)
Deployment
Streamlit Cloud (free):
# Push to GitHub, then deploy at share.streamlit.io
Local network:
streamlit run app.py --server.port 8501
Integration with Research Tools
- Plotly: Interactive plots with zoom/pan
- Altair: Declarative visualization grammar
- PyDeck: 3D geographical visualizations
- NetworkX: Graph visualizations
- PIL/OpenCV: Image processing and display
Common Patterns
# Session state for maintaining state across reruns
if 'analysis_results' not in st.session_state:
st.session_state.analysis_results = None
if st.button("Run Analysis"):
results = expensive_computation()
st.session_state.analysis_results = results
# Forms for grouped inputs
with st.form("analysis_params"):
param1 = st.slider("Parameter 1", 0, 100, 50)
param2 = st.selectbox("Method", ["A", "B", "C"])
submitted = st.form_submit_button("Submit")
if submitted:
run_analysis(param1, param2)
# Columns for layout
col1, col2 = st.columns(2)
with col1:
st.write("Left panel")
with col2:
st.write("Right panel")