Data Science

Exploratory Data Analysis

Univariate Analysis

Just "age" by itself: what values are common? Any weird outliers?

JrCodex·6 min read

Jr Codex Data Science Notes

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


Table of Contents

  1. What is Univariate Analysis?
  2. Analyzing a Numeric Variable
  3. Visualizing a Numeric Distribution
  4. Analyzing a Categorical Variable
  5. Recognizing Common Distribution Shapes
  6. A Practical Checklist Per Column
  7. 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

  1. Why do a histogram and a box plot answer different questions about the same numeric column?
  2. What does it mean if a categorical column has hundreds of unique values, each appearing only once or twice?
  3. 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