Exploratory Data Analysis
Univariate Analysis
Just "age" by itself: what values are common? Any weird outliers?
Jr Codex Data Science Notes
Level: Intermediate Prerequisites: Chapter 1 Time to complete: ~25 minutes
Table of Contents
- What is Univariate Analysis?
- Analyzing a Numeric Variable
- Visualizing a Numeric Distribution
- Analyzing a Categorical Variable
- Recognizing Common Distribution Shapes
- A Practical Checklist Per Column
- Summary & Next Steps
1. What is Univariate Analysis?
Univariate analysis examines one variable at a time, in isolation — building a solid understanding of each column's own shape and quirks before looking at how columns relate to each other (Chapter 3).
Univariate = "One Variable"
─────────────────────────────────────────
Just "age" by itself: what values are common? Any weird outliers?
Just "city" by itself: how many unique cities? Is one dominant?
(NOT yet: does age relate to income? — that's bivariate, Chapter 3)
─────────────────────────────────────────
2. Analyzing a Numeric Variable
Directly applying Module 1's descriptive statistics to a real column:
import pandas as pd
df = pd.read_csv("customer_data.csv")
print(df["age"].describe())
# count 10000.000000
# mean 38.452000
# std 12.891000
# min 18.000000
# 25% 28.000000
# 50% 37.000000
# 75% 48.000000
# max 95.000000
print(f"Skewness: {df['age'].skew():.3f}") # Module 1, Chapter 1
print(f"Range: {df['age'].max() - df['age'].min()}")Questions to Ask of Every Numeric Column
─────────────────────────────────────────
- Does the mean and median agree closely, or diverge (suggesting skew)?
- Is the min/max plausible for what this column represents?
(e.g. age=150 is clearly a data error, not a real value)
- How spread out is the data (std dev) relative to its mean?
─────────────────────────────────────────
3. Visualizing a Numeric Distribution
Numbers alone can hide shape that a plot reveals instantly — full visualization techniques are covered in Module 4, but these two are essential during EDA itself.
import matplotlib.pyplot as plt
import seaborn as sns
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
# Histogram — shows the SHAPE of the distribution
axes[0].hist(df["age"], bins=30, edgecolor="black")
axes[0].set_title("Age Distribution")
axes[0].set_xlabel("Age")
# Box plot — shows the five-number summary (Module 1, Ch.1) visually, including outliers
axes[1].boxplot(df["age"])
axes[1].set_title("Age Box Plot")
plt.tight_layout()
plt.show()Reading a Box Plot
─────────────────────────────────────────
┌─────┬─────┐
o │ │ │ o ← individual dots = potential outliers
──────┤ Q1 │ Q3 ├──────
└─────┴─────┘
min median max (whiskers typically extend to 1.5×IQR, Module 1 Ch.1)
─────────────────────────────────────────
A histogram and a box plot answer different questions — a histogram shows the full shape (is it bimodal? skewed?), while a box plot summarizes spread and outliers compactly, and is especially useful for comparing several groups side by side (previewed here, expanded in Chapter 3).
4. Analyzing a Categorical Variable
import pandas as pd
print(df["city"].value_counts())
# Boston 3200
# New York 2800
# LA 2100
# Chicago 1900
print(df["city"].value_counts(normalize=True) * 100) # as percentages
# Boston 32.0
# New York 28.0
# ...
print(df["city"].nunique()) # 4 — how many distinct categories exist?import matplotlib.pyplot as plt
df["city"].value_counts().plot(kind="bar")
plt.title("Customer Count by City")
plt.ylabel("Count")
plt.show()Questions to Ask of Every Categorical Column
─────────────────────────────────────────
- How many unique categories are there? (a handful, or hundreds?)
- Is one category dominant (e.g. 90% of rows), making it nearly constant?
- Do category labels look CLEAN, or do they need the standardization
from Module 2, Chapter 3 (e.g. "NYC" vs "New York" vs "new york")?
─────────────────────────────────────────
A categorical column with hundreds of unique, rarely-repeated values (e.g. free-text notes field) is often not usable directly for grouping/encoding — it usually needs to be either engineered into something coarser (Module 2, Chapter 5's binning) or excluded from this kind of analysis.
5. Recognizing Common Distribution Shapes
Building directly on Module 1's probability distributions chapter — recognizing these shapes visually is a core EDA skill.
Common Shapes to Recognize
─────────────────────────────────────────
Normal-ish (symmetric bell) → mean ≈ median; standard stats/tests apply cleanly
Right-skewed (long tail right) → MEAN > median; common for income, prices, wait times
_/\___
_/ \______
Left-skewed (long tail left) → MEAN < median; less common, e.g. age at retirement
___/\_
___/ \
Bimodal (two peaks) → suggests TWO distinct underlying groups mixed
/\ /\ together — often worth splitting and analyzing
/ \ / \ separately (a segmentation question, not a single stat)
/ \__/ \
─────────────────────────────────────────
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(6, 4))
df["age"].hist(bins=30, ax=ax)
ax.axvline(df["age"].mean(), color="red", linestyle="--", label=f"Mean: {df['age'].mean():.1f}")
ax.axvline(df["age"].median(), color="green", linestyle="--", label=f"Median: {df['age'].median():.1f}")
ax.legend()A bimodal distribution is a common and important EDA finding — it's a strong hint that the "population" you're looking at is secretly two different populations mixed together (e.g. "purchase amount" might be bimodal because it mixes individual consumers and bulk business buyers) — a single mean/median describing the whole thing would be misleading for either group.
6. A Practical Checklist Per Column
def univariate_summary(df, column):
"""A repeatable first pass on any single column."""
print(f"\n{'='*50}\nColumn: {column}\n{'='*50}")
print(f"Type: {df[column].dtype}")
print(f"Missing: {df[column].isna().sum()} ({df[column].isna().mean()*100:.1f}%)")
if pd.api.types.is_numeric_dtype(df[column]):
print(df[column].describe())
print(f"Skewness: {df[column].skew():.3f}")
else:
print(f"Unique values: {df[column].nunique()}")
print(df[column].value_counts().head())
for col in df.columns:
univariate_summary(df, col)Wrapping this into a reusable function (directly applying the Python Notes' function-writing principles) turns "look at every column" from a tedious manual task into one line, run consistently across every new dataset.
7. Summary & Next Steps
Key Takeaways
- Univariate analysis examines one column at a time — the foundation before looking at relationships between columns in Chapter 3.
.describe(), skewness, histograms, and box plots together give a complete picture of a numeric column's shape and spread..value_counts()is the primary tool for categorical columns — watch for a dominant category or an unusably high number of unique values.- Recognizing distribution shapes (normal, skewed, bimodal) at a glance is a core EDA skill — a bimodal shape in particular often signals two mixed populations worth analyzing separately.
Concept Check
- Why do a histogram and a box plot answer different questions about the same numeric column?
- What does it mean if a categorical column has hundreds of unique values, each appearing only once or twice?
- Why might a bimodal distribution be a more important finding than a simple mean/median summary?
Next Chapter
→ Chapter 3: Bivariate & Multivariate Analysis
Jr Codex — 1-on-1 Personalized Coaching | Back to Module Index