Introduction
Data is everywhere — spreadsheets, CSV exports, survey results, sales records, and API responses. The ability to quickly explore, filter, and visualise a dataset is one of the most valuable skills a developer or analyst can have.
In this tutorial you will build a fully interactive Data Dashboard using Pandas and Streamlit. Users can upload any CSV file and immediately get column statistics, apply filters, sort data, and view bar charts, line charts, and scatter plots — all without writing a single line of code after the app is deployed.
This project pairs well with the File Organizer tutorial for working with local files, and with Sentiment Analysis for exploring text data.
What You Will Build
- CSV Upload — drag-and-drop any CSV file
- Data Preview — first N rows with column types
- Summary Statistics — count, mean, min, max, std for numeric columns
- Column Filter — select which columns to display
- Value Filter — filter rows by any column value
- Sorting — sort by any column, ascending or descending
- Charts — bar, line, and scatter plots
- Download — export the filtered dataset as a new CSV
Prerequisites
- Python 3.8+ — python.org
- Libraries:
pip install streamlit pandas
| Package | Version | Purpose |
|---|---|---|
streamlit |
≥ 1.32 | Web app framework |
pandas |
≥ 2.0 | Data loading, filtering, aggregation |
io |
built-in | In-memory CSV export |
Step 1: Page Setup and File Upload
Create dashboard.py:
import io
import pandas as pd
import streamlit as st
st.set_page_config(
page_title="Data Dashboard",
page_icon="📊",
layout="wide"
)
st.title("📊 Interactive Data Dashboard")
st.write("Upload a CSV file to explore, filter, and visualise your data instantly.")
uploaded_file = st.file_uploader(
"Choose a CSV file",
type=["csv"],
help="The file should have a header row. Max recommended size: 50 MB."
)
Step 2: Load and Cache the Data
Use st.cache_data to avoid reloading the file on every Streamlit rerun:
@st.cache_data
def load_data(file) -> pd.DataFrame:
return pd.read_csv(file)
if uploaded_file is not None:
df = load_data(uploaded_file)
st.success(f"✅ Loaded **{len(df):,} rows** and **{len(df.columns)} columns**")
else:
st.info("👆 Upload a CSV file above to get started.")
st.stop()
st.cache_data stores the parsed DataFrame in memory. The function only runs again if the uploaded file changes, which keeps the app snappy even for large files.
Step 3: Data Overview
Show the shape, column types, and a configurable number of preview rows:
with st.expander("🔍 Data Overview", expanded=True):
col1, col2, col3 = st.columns(3)
col1.metric("Rows", f"{len(df):,}")
col2.metric("Columns", len(df.columns))
col3.metric("Missing Values", int(df.isnull().sum().sum()))
preview_rows = st.slider("Rows to preview", 5, 50, 10)
st.dataframe(df.head(preview_rows), use_container_width=True)
st.write("**Column Types:**")
col_types = pd.DataFrame({
"Column": df.columns,
"Type": df.dtypes.astype(str).values,
"Non-Null Count": df.notnull().sum().values,
"Null Count": df.isnull().sum().values,
})
st.dataframe(col_types, use_container_width=True, hide_index=True)
Step 4: Summary Statistics
with st.expander("📈 Summary Statistics"):
numeric_cols = df.select_dtypes(include="number").columns.tolist()
if numeric_cols:
st.dataframe(
df[numeric_cols].describe().round(2),
use_container_width=True
)
else:
st.info("No numeric columns found in this dataset.")
df.describe() returns count, mean, std, min, 25th/50th/75th percentile, and max for every numeric column — a one-liner that saves hours of manual calculation.
Step 5: Column and Value Filters
Let users narrow down the data using the sidebar:
st.sidebar.header("🔧 Filters")
# Column selector
all_cols = df.columns.tolist()
selected_cols = st.sidebar.multiselect(
"Show columns:", all_cols, default=all_cols[:min(6, len(all_cols))]
)
# Value filter for categorical columns
cat_cols = df.select_dtypes(include="object").columns.tolist()
filtered_df = df.copy()
for col in cat_cols[:3]: # limit to first 3 categorical columns
unique_vals = df[col].dropna().unique().tolist()
if len(unique_vals) <= 30: # only show filter for low-cardinality columns
chosen = st.sidebar.multiselect(
f"Filter by {col}:", unique_vals, default=unique_vals
)
filtered_df = filtered_df[filtered_df[col].isin(chosen)]
# Numeric range filters
for col in df.select_dtypes(include="number").columns[:2]:
min_val = float(df[col].min())
max_val = float(df[col].max())
chosen_range = st.sidebar.slider(
f"{col} range:",
min_value=min_val,
max_value=max_val,
value=(min_val, max_val)
)
filtered_df = filtered_df[
filtered_df[col].between(chosen_range[0], chosen_range[1])
]
Step 6: Sortable Filtered Table
st.subheader("📋 Filtered Data")
col_sort, col_order = st.columns(2)
with col_sort:
sort_by = st.selectbox("Sort by:", selected_cols)
with col_order:
ascending = st.radio("Order:", ["Ascending", "Descending"]) == "Ascending"
display_df = (
filtered_df[selected_cols]
.sort_values(by=sort_by, ascending=ascending)
.reset_index(drop=True)
)
st.info(f"Showing **{len(display_df):,}** of **{len(df):,}** rows")
st.dataframe(display_df, use_container_width=True)
Step 7: Charts
Provide three chart types — bar, line, and scatter:
st.divider()
st.subheader("📊 Visualisations")
numeric_cols = filtered_df.select_dtypes(include="number").columns.tolist()
all_cols_list = filtered_df.columns.tolist()
if not numeric_cols:
st.warning("No numeric columns available for charting.")
else:
chart_type = st.radio(
"Chart Type:", ["Bar Chart", "Line Chart", "Scatter Plot"],
horizontal=True
)
if chart_type in ["Bar Chart", "Line Chart"]:
x_col = st.selectbox("X axis:", all_cols_list, key="x")
y_col = st.selectbox("Y axis:", numeric_cols, key="y")
chart_data = filtered_df[[x_col, y_col]].dropna()
if chart_type == "Bar Chart":
st.bar_chart(chart_data.set_index(x_col))
else:
st.line_chart(chart_data.set_index(x_col))
elif chart_type == "Scatter Plot":
x_col = st.selectbox("X axis:", numeric_cols, key="sx")
y_col = st.selectbox("Y axis:", numeric_cols, key="sy")
st.scatter_chart(
filtered_df[[x_col, y_col]].dropna(),
x=x_col, y=y_col
)
Step 8: Download Filtered Data
st.divider()
st.subheader("⬇️ Export")
buffer = io.StringIO()
display_df.to_csv(buffer, index=False)
st.download_button(
label="⬇️ Download Filtered CSV",
data=buffer.getvalue(),
file_name="filtered_data.csv",
mime="text/csv",
use_container_width=True,
)
Run the App
streamlit run dashboard.py
Pandas Cheat Sheet for This Project
| Task | Pandas Code |
|---|---|
| Load CSV | pd.read_csv("file.csv") |
| First N rows | df.head(N) |
| Column data types | df.dtypes |
| Count missing values | df.isnull().sum() |
| Select numeric columns | df.select_dtypes(include="number") |
| Filter rows by value | df[df["col"] == value] |
| Filter rows by range | df[df["col"].between(a, b)] |
| Sort rows | df.sort_values(by="col", ascending=True) |
| Summary statistics | df.describe() |
| Select columns | df[["col1", "col2"]] |
| Reset index | df.reset_index(drop=True) |
| Export to CSV string | df.to_csv(index=False) |
Streamlit Data Display Components
| Component | Best For |
|---|---|
st.dataframe() |
Interactive, sortable, resizable table |
st.table() |
Static, read-only table |
st.bar_chart() |
Quick bar charts from a DataFrame |
st.line_chart() |
Time series and trend lines |
st.scatter_chart() |
Correlation and distribution plots |
st.metric() |
Single KPI numbers with delta |
st.json() |
Display raw JSON data |
Handling Common CSV Issues
| Problem | Fix |
|---|---|
| Wrong encoding | pd.read_csv(file, encoding="latin-1") |
| Semicolon delimiter | pd.read_csv(file, sep=";") |
| No header row | pd.read_csv(file, header=None) |
| Mixed numeric/text in a column | pd.to_numeric(df["col"], errors="coerce") |
| Date columns read as strings | pd.to_datetime(df["date_col"]) |
| Memory error on large file | pd.read_csv(file, chunksize=10000) |
What to Try Next
- Add correlation heatmap using
st.pyplot()withseaborn.heatmap(df.corr()). - Support Excel files (
.xlsx) by addingopenpyxland usingpd.read_excel(). - Add group-by aggregation — group by a categorical column and show mean/sum per group.
- Show outlier detection — flag rows where any numeric value is more than 3 standard deviations from the mean.
- Cache results to SQLite using the built-in
sqlite3module.
Related Projects
- Sentiment Analysis App — Another data-focused Streamlit app, this time analysing the polarity of text rather than numeric data.
- File Organizer — Uses Python’s
pathlibandshutilto work with files on disk, a great complement to CSV-based data work. - Recipe Finder — Shows how to work with structured JSON data from an API, similar to working with tabular CSV data.
Conclusion
You have built a fully featured interactive data dashboard in Python using Pandas and Streamlit. The app handles CSV uploads, shows summary statistics, provides multi-column filtering, supports three chart types, and exports the filtered result — all with minimal code and zero frontend experience required.
Pandas and Streamlit together are one of the most productive combinations in the Python ecosystem for rapid data exploration and sharing.
Resources: