Week 7: Univariate Analysis & Visualization
🎯 Learning objectives
By the end of this week you can:
- Visualize a numeric column's distribution with a histogram, and a categorical column's distribution with a bar chart.
- Read a boxplot as a compact summary of a distribution's quartiles and outliers.
- Choose the right chart type for a variable's type, and label charts so they're readable on their own.
- Explain why the number of histogram bins is itself a meaningful choice, not an arbitrary default.
Lesson
Why visualize, not just summarize with numbers?
Week 6's mean/median/std describe a distribution with three numbers — but very different distributions can share the same mean and std. A chart shows shape: is it symmetric, skewed, bimodal (two humps), full of outliers? This is the core reason univariate visualization comes before anything more advanced.
Histograms: distribution of a numeric variable
A histogram bins a numeric column's values and shows how many fall in each bin — a direct visual of the frequency distribution, the plotted version of last week's mean/median/std numbers:
import matplotlib.pyplot as plt
df["math_score"].hist(bins=20, edgecolor="black")
plt.xlabel("Math score")
plt.ylabel("Number of students")
plt.title("Distribution of math scores")
plt.show()
Always label your axes and title your chart — a chart that only makes sense with the code that produced it next to it isn't finished.
Bin count is a real choice, not a default to ignore
Too few bins hides real structure (everything blurs into one or two lumps); too many bins shows noise as if it were meaningful structure (each bin has too few points to say anything reliable). There's no single universally correct number — try a couple of values and see which one tells the clearest, most honest story:
fig, axes = plt.subplots(1, 3, figsize=(12, 4))
for ax, n_bins in zip(axes, [5, 20, 50]):
df["math_score"].hist(bins=n_bins, ax=ax, edgecolor="black")
ax.set_title(f"{n_bins} bins")
plt.tight_layout()
plt.show()
plt.subplots(1, 3, figsize=(12, 4)) creates one figure with 3 side-by-side panels (axes is then a list of 3 individual plot areas) — a useful pattern whenever you want to compare a few variations of the same chart at once, which you'll reuse in Week 9.
Boxplots: quartiles and outliers at a glance
A boxplot draws the 25th percentile, median, and 75th percentile as a box, with "whiskers" extending to the typical range and individual points beyond that marked as potential outliers — a compact visual of exactly the quartile numbers .describe() already gave you, and exactly the same rule from last week, drawn out visually:
df.boxplot(column="math_score")
plt.ylabel("Math score")
plt.title("Math score spread")
plt.show()
Boxplots become especially useful once you compare several groups' distributions side by side — which is exactly next week's bivariate topic.
Bar charts: distribution of a categorical variable
For a categorical column, .value_counts() (Week 6) plotted directly as a bar chart shows which categories are common vs. rare:
df["parental_level_of_education"].value_counts().plot(kind="bar")
plt.xlabel("Parental level of education")
plt.ylabel("Number of students")
plt.title("Parental education levels in the dataset")
plt.xticks(rotation=45, ha="right")
plt.tight_layout()
plt.show()
plt.xticks(rotation=45, ha="right") rotates long category labels so they don't overlap — a small but common fix for readability once labels get longer than a couple of characters. plt.tight_layout() prevents rotated/long labels from getting cut off at the figure's edge.
Choosing the right chart
| Variable type | Chart |
|---|---|
| Numeric, want distribution shape | Histogram |
| Numeric, want quartiles/outliers, especially across groups | Boxplot |
| Categorical, want frequency of each category | Bar chart |
A bar chart of a numeric column (treating each unique score as its own "category") or a histogram of a categorical column are both usually wrong choices — matching the chart to the variable's actual type is the first design decision, before any styling.
⚠️ Common pitfalls
- Forgetting axis labels and a title. A chart that requires the reader to already know what it shows isn't finished — always label both axes and add a title, even for a "just exploring" chart you'll delete later.
- Picking one bin count and never questioning it. As shown above, the same data can tell noticeably different visual stories at 5 vs. 20 vs. 50 bins — always sanity-check with at least two bin counts before trusting what a histogram's shape seems to say.
- Using the wrong chart for the variable type. A histogram of a categorical column (like
gender) or a bar chart of raw, un-aggregated numeric values are both signs the chart-choice table above wasn't consulted first. - Forgetting
plt.show()(or a trailing bare expression) to actually display the chart, especially when chaining several plotting calls — depending on your environment, a chart built across multiple lines may not render until explicitly shown.
🧩 Challenges
Plot a histogram of reading_score with labeled axes and a title.
Create a boxplot of writing_score. Looking at it, does the distribution appear symmetric, or is the median noticeably closer to one edge of the box?
Plot a bar chart of the test_preparation_course column's category counts, with labeled axes.
Someone plots df["gender"].hist(). Explain why this is the wrong chart choice, and what they should use instead.
Plot writing_score as a histogram with 5 bins, then again with 50 bins. Describe how the apparent shape of the distribution changes between the two.
Create a single boxplot showing all three score columns (math_score, reading_score, writing_score) side by side, so their spreads can be compared at a glance.
🤔 Socratic Questions
- Two distributions can have identical mean and standard deviation but very different shapes (e.g. one symmetric, one with two separate humps). Why does a histogram catch this difference while
mean/stdalone cannot? - A boxplot marks points beyond the whiskers as potential "outliers." Does that automatically mean those data points are errors that should be removed? What else might explain a legitimately unusual but correct value?
- Why does
plt.xticks(rotation=45, ha="right")matter more forparental_level_of_education(long category names) than forgender(short ones)? What general rule about label length and chart readability does this suggest? - If a colleague showed you only a 50-bin histogram of a variable and claimed to see "three distinct clusters," what would you want to check before trusting that claim, given what you now know about bin-count sensitivity?