Streamlit · 05 — Charts and Interactive EDA¶
Time: ~75 min · CLOs: CLO-1, CLO-3 · Level: Beginner Prerequisite: Matplotlib/Seaborn (Module A). Your existing plotting skills transfer directly.
1. Three ways to chart in Streamlit¶
A. Built-in charts — zero code, data in, chart out¶
st.line_chart(df) # line chart of numeric columns
st.bar_chart(df) # bar chart
st.area_chart(df) # area chart
Give them a dataframe (or a Series) and they plot every numeric column. Perfect for quick looks; limited control.
B. Matplotlib — st.pyplot¶
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.hist(df["tip"], bins=20)
ax.set_title("Tip distribution")
st.pyplot(fig) # <-- pass the figure object
Key detail: create the fig and ax with plt.subplots(), draw on
ax, and pass the figure to st.pyplot. Because the app re-runs, the
figure is recreated and re-displayed every time a widget changes.
C. Seaborn — same rule¶
import seaborn as sns
fig, ax = plt.subplots()
sns.boxplot(data=df, x="day", y="tip", ax=ax) # draw into ax!
ax.set_title("Tips by day")
st.pyplot(fig)
Beginner trap: if you call sns.boxplot(data=df, x="day", y="tip")
without ax=ax, the plot is drawn into Matplotlib's current axes — which
st.pyplot may or may not pick up. Always pass ax=ax (or use the
returned axes object). The reliable pattern is:
fig, ax = plt.subplots(figsize=(8, 4))
sns.histplot(df["tip"], bins=20, kde=True, ax=ax)
st.pyplot(fig)
2. Interactive EDA — the pattern¶
column = st.selectbox("Column", df.select_dtypes("number").columns)
fig, ax = plt.subplots()
sns.histplot(df[column], bins=30, ax=ax)
st.pyplot(fig)
Widget picks the column → figure rebuilds → chart updates. One widget + one figure = an interactive EDA tool.
Example 4 — Interactive EDA Dashboard¶
A complete app: explore a dataset's numeric columns with a histogram and its categories with a boxplot, filtered by a checkbox.
Explanation¶
The app loads the built-in tips dataset, shows quick overview metrics, then builds two interactive figures: a histogram of whichever numeric column the user picks, and a boxplot of tip by whichever category the user picks. A checkbox filters the data first (smokers only) — watch every chart update.
Code¶
Save as app.py:
import streamlit as st
import seaborn as sns
import matplotlib.pyplot as plt
st.title("📊 Interactive EDA Dashboard")
st.markdown("Explore the **tips** dataset with live charts.")
# 1) Data
tips = sns.load_dataset("tips")
# 2) Overview metrics
st.header("Overview")
c1, c2, c3 = st.columns(3)
c1.metric("Rows", len(tips))
c2.metric("Mean tip", f"${tips['tip'].mean():.2f}")
c3.metric("Mean bill", f"${tips['total_bill'].mean():.2f}")
# 3) A global filter (checkbox)
only_smokers = st.checkbox("Only smokers")
if only_smokers:
tips = tips[tips["smoker"] == "Yes"]
# 4) Interactive histogram
st.header("Histogram — pick a numeric column")
num_col = st.selectbox("Numeric column",
tips.select_dtypes("number").columns)
fig, ax = plt.subplots(figsize=(8, 4))
sns.histplot(tips[num_col], bins=30, kde=True, ax=ax)
ax.set_title(f"Distribution of {num_col}")
st.pyplot(fig)
# 5) Interactive boxplot
st.header("Boxplot — tip by category")
cat_col = st.selectbox("Category column", ["day", "sex", "smoker", "time"])
fig, ax = plt.subplots(figsize=(8, 4))
sns.boxplot(data=tips, x=cat_col, y="tip", ax=ax)
ax.set_title(f"Tip by {cat_col}")
st.pyplot(fig)
Expected result¶
- Three metric cards: 244 rows, mean tip ≈ $3.00, mean bill ≈ $19.79.
- Histogram section: a dropdown of numeric columns (total_bill, tip, size); the chart redraws when you switch columns. Default shows the tip distribution with a smooth KDE curve.
- Boxplot section: a dropdown (day/sex/smoker/time); the boxplot of tip by that category redraws on change.
- Tick Only smokers: row count drops to 93, the histogram narrows, and the boxplots rebuild — every chart reflects the filter.
Exercise¶
- Add a
st.slider("Minimum total bill", 0, 60, 0)that filterstips = tips[tips["total_bill"] >= min_bill]before the charts. - Change the histogram dropdown to
st.multiselectof columns and overlay two histograms (ax.histtwice withalpha=0.5), or usesns.histplot(..., multiple="dodge")with a hue. - Add a correlation heatmap for numeric columns
(
sns.heatmap(tips.select_dtypes("number").corr(), annot=True, ax=ax)).
Challenge¶
Build an interactive outlier inspector: a slider for a threshold, then a
scatter plot of total_bill vs tip with points above the threshold
highlighted in red:
threshold = st.slider("Outlier threshold (tip)", 0.0, 10.0, 5.0)
fig, ax = plt.subplots(figsize=(8, 5))
sns.scatterplot(data=tips, x="total_bill", y="tip", ax=ax)
outliers = tips[tips["tip"] > threshold]
ax.scatter(outliers["total_bill"], outliers["tip"],
color="red", s=60, label=f"tip > {threshold}")
ax.legend()
st.pyplot(fig)
Add a metric showing how many rows are outliers at the current threshold.
Common mistakes¶
| Mistake | Fix |
|---|---|
| Plot doesn't appear | forget st.pyplot(fig) — drawing alone doesn't display |
| Seaborn plot "lost" | always pass ax=ax when using plt.subplots() |
| Chart updates slowly | heavy data processing should be cached (document 6) |
| Empty plot after filtering | the filter emptied the dataframe — check len(df) and show an info message |
| Two charts share state | give each its own fig, ax = plt.subplots() |
Checkpoint questions¶
- What three arguments does the reliable
st.pyplotpattern need? CLO-1 · Understand · Easy Answer:fig, ax = plt.subplots(), drawing onax(withax=axfor seaborn), andst.pyplot(fig). - Why does the chart update when the user changes a selectbox? (CLO-1 · Understand · Medium) Answer: the rerun model — changing the widget re-runs the script, the new value flows into the figure code, and a fresh figure is displayed.
- What happens if a filter leaves zero rows, and how do you handle it?
CLO-1 · Evaluate · Medium
Answer: the chart becomes empty/errors; guard with
if len(df) == 0: st.warning("No data for this filter")and skip plotting.