Exploratory Data Analysis
Bivariate & Multivariate Analysis
Chapter 2 examined columns in isolation. Bivariate analysis looks at two variables together — does one relate to the other? Multivariate analysis extends this t
Jr Codex Data Science Notes
Level: Intermediate Prerequisites: Chapter 2 Time to complete: ~25 minutes
Table of Contents
- From "One Column" to "How Columns Relate"
- Numeric vs Numeric
- Categorical vs Numeric
- Categorical vs Categorical
- Multivariate Analysis — Bringing in a Third Variable
- The Correlation Heatmap, Revisited
- Summary & Next Steps
1. From "One Column" to "How Columns Relate"
Chapter 2 examined columns in isolation. Bivariate analysis looks at two variables together — does one relate to the other? Multivariate analysis extends this to three or more, often to check whether a relationship holds within different subgroups.
Univariate → Bivariate → Multivariate
─────────────────────────────────────────
"What does age look like?"
↓
"Does age relate to income?"
↓
"Does the age-income relationship differ by region?"
─────────────────────────────────────────
The right technique depends entirely on whether each variable involved is numeric or categorical — this chapter is organized around that distinction.
2. Numeric vs Numeric
Directly builds on Module 1's correlation chapter — now applied as an actual EDA step, not an isolated formula.
import pandas as pd
import matplotlib.pyplot as plt
df = pd.read_csv("customer_data.csv")
print(df["age"].corr(df["income"])) # Module 1, Ch.5
plt.scatter(df["age"], df["income"], alpha=0.3)
plt.xlabel("Age")
plt.ylabel("Income")
plt.title(f"Age vs Income (r = {df['age'].corr(df['income']):.2f})")What to Look For in a Scatter Plot
─────────────────────────────────────────
Linear trend → correlation coefficient captures it well
Curved trend → correlation UNDERSTATES the relationship (Module 1, Ch.5's parabola case)
No visible pattern → little/no relationship, or the relationship is more complex
Distinct clusters → possible SUBGROUPS worth splitting (see Section 5)
─────────────────────────────────────────
Always plot the scatter, even when you already have the correlation number — a correlation coefficient alone (as Module 1 demonstrated with the y = x² example) can badly understate or completely miss a real, non-linear relationship.
3. Categorical vs Numeric
The question here is usually: "does this numeric variable differ meaningfully across these categories?"
import pandas as pd
# Group-wise summary statistics — the natural extension of Module 1's descriptive stats
print(df.groupby("city")["income"].describe())
# A quick comparison of means
print(df.groupby("city")["income"].mean().sort_values(ascending=False))import matplotlib.pyplot as plt
# Box plots side-by-side — the single best tool for comparing a numeric variable ACROSS categories
df.boxplot(column="income", by="city", figsize=(8, 5))
plt.title("Income Distribution by City")
plt.suptitle("") # removes pandas' automatic (ugly) subtitle
plt.ylabel("Income")Reading Grouped Box Plots
─────────────────────────────────────────
City A: ┌──┬──┐ City B: ┌────┬────┐
──────────┤ │ ├────── ─────────────┤ │ ├─────────
└──┴──┘ └────┴────┘
(tight, low income) (wider spread, higher median)
→ City B likely has both a higher AND more variable income —
worth investigating WHY (see Section 5's multivariate follow-up)
─────────────────────────────────────────
This is also exactly the visual, exploratory precursor to the formal two-sample t-test from Module 1, Chapter 6 — EDA spots the pattern; hypothesis testing (or Module 5's A/B testing) confirms whether it's statistically real.
4. Categorical vs Categorical
import pandas as pd
# A contingency table — counts of every combination of two categorical variables
contingency = pd.crosstab(df["city"], df["subscription_tier"])
print(contingency)
# subscription_tier basic premium
# city
# Boston 1800 1400
# Chicago 900 600
# LA 1300 800
# New York 1500 1300
# As percentages WITHIN each row (city) — often more interpretable than raw counts
contingency_pct = pd.crosstab(df["city"], df["subscription_tier"], normalize="index") * 100
print(contingency_pct)import matplotlib.pyplot as plt
contingency.plot(kind="bar", stacked=True, figsize=(8, 5))
plt.title("Subscription Tier by City")
plt.ylabel("Count")A crosstab is the categorical-vs-categorical equivalent of a correlation coefficient — it reveals whether the distribution of one variable depends on the value of another (e.g. "premium subscribers are proportionally more common in Boston than Chicago").
5. Multivariate Analysis — Bringing in a Third Variable
Real relationships are often more nuanced than a single pair suggests — a third variable can reveal that a bivariate pattern is misleading on its own.
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
# Does the age-income relationship hold WITHIN each city, or does it differ?
sns.scatterplot(data=df, x="age", y="income", hue="city", alpha=0.5)
plt.title("Age vs Income, colored by City")# Faceting — a separate small plot PER category, for direct comparison
g = sns.FacetGrid(df, col="city", col_wrap=2, height=3)
g.map(plt.scatter, "age", "income", alpha=0.4)Simpson's Paradox — Why Multivariate Analysis Matters
─────────────────────────────────────────
A famous real-world caution: a trend that appears in AGGREGATE data
can REVERSE once you split by a third variable.
Example: overall, a new drug looks WORSE than the old one.
But split by patient severity (mild vs severe):
- Mild patients: new drug performs BETTER
- Severe patients: new drug performs BETTER
...yet the AGGREGATE favored the old drug, because of how many
patients of each severity happened to receive each drug.
Lesson: always check whether a bivariate relationship holds up
ACROSS relevant subgroups before trusting it.
─────────────────────────────────────────
# Checking for Simpson's-paradox-style reversals directly
overall_corr = df["treatment_dose"].corr(df["recovery_score"])
by_group_corr = df.groupby("severity").apply(
lambda g: g["treatment_dose"].corr(g["recovery_score"])
)
print(f"Overall correlation: {overall_corr:.2f}")
print(f"By severity group:\n{by_group_corr}")6. The Correlation Heatmap, Revisited
Module 1 introduced the correlation matrix; here it becomes a core EDA visualization for spotting many relationships at once.
import seaborn as sns
import matplotlib.pyplot as plt
numeric_df = df.select_dtypes(include="number")
corr_matrix = numeric_df.corr()
plt.figure(figsize=(8, 6))
sns.heatmap(corr_matrix, annot=True, cmap="coolwarm", center=0, fmt=".2f")
plt.title("Correlation Heatmap")Reading a Correlation Heatmap Efficiently
─────────────────────────────────────────
1. Scan for any cell close to +1 or -1 (excluding the diagonal, which is always 1) —
these are your strongest candidate relationships to investigate further.
2. Watch for two FEATURES highly correlated with EACH OTHER (not just the target) —
this is multicollinearity, a concern flagged again in the Machine Learning notes.
3. Remember: this only shows LINEAR relationships (Module 1, Ch.5) — always
verify anything important with an actual scatter plot.
─────────────────────────────────────────
7. Summary & Next Steps
Key Takeaways
- Bivariate analysis technique depends on variable types: scatter plots + correlation for numeric-numeric, grouped box plots for categorical-numeric, contingency tables (
crosstab) for categorical-categorical. - Always visualize a relationship, even after computing a correlation number — non-linear patterns can be invisible to Pearson correlation.
- Multivariate analysis (bringing in a third variable) can reveal that an aggregate relationship reverses within subgroups — Simpson's Paradox is the canonical warning for why this check matters.
- A correlation heatmap is the fastest way to scan many numeric-numeric relationships at once, and also the standard tool for spotting multicollinearity before modeling.
Concept Check
- Which technique would you use to compare
incomeacross four differentcitycategories? - Why is a
crosstabdescribed as the categorical equivalent of a correlation coefficient? - In Simpson's Paradox, why can an aggregate trend reverse once you split by a third variable?
Next Chapter
→ Chapter 4: Outlier Detection
Jr Codex — 1-on-1 Personalized Coaching | Back to Module Index