Data Science

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

JrCodex·6 min read

Jr Codex Data Science Notes

Level: Intermediate Prerequisites: Chapter 2 Time to complete: ~25 minutes


Table of Contents

  1. From "One Column" to "How Columns Relate"
  2. Numeric vs Numeric
  3. Categorical vs Numeric
  4. Categorical vs Categorical
  5. Multivariate Analysis — Bringing in a Third Variable
  6. The Correlation Heatmap, Revisited
  7. 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

  1. Which technique would you use to compare income across four different city categories?
  2. Why is a crosstab described as the categorical equivalent of a correlation coefficient?
  3. 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