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
Prerequisites
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.pyPandas 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
st.pyplot() with seaborn.heatmap(df.corr())..xlsx) by adding openpyxl and using pd.read_excel().sqlite3 module.Related Projects
pathlib and shutil to work with files on disk, a great complement to CSV-based data work.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: