Skip to content

Univariate Analysis

Univariate analysis looks at one variable at a time. Before studying relationships, understand each column individually. What values does it take? How is it distributed? What's typical? What's rare? This is the foundation of all further analysis.

Learning Objectives

  • Analyse the distribution of numeric variables with histograms and boxplots
  • Analyse categorical variables with frequency tables and bar charts
  • Identify skewness, outliers, and unexpected distributions
  • Use seaborn and matplotlib for clean, informative charts

Numeric Variables

Histogram — shape of the distribution

import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

df = pd.read_csv("orders_clean.csv")
df["total"] = df["quantity"] * df["unit_price"]

fig, axes = plt.subplots(1, 2, figsize=(12, 5))

# Basic histogram
axes[0].hist(df["total"], bins=30, edgecolor="white", color="#0D9488")
axes[0].set_title("Order Total Distribution")
axes[0].set_xlabel("Total (£)")
axes[0].set_ylabel("Frequency")
axes[0].axvline(df["total"].mean(), color="red", linestyle="--", label=f"Mean: £{df['total'].mean():,.0f}")
axes[0].axvline(df["total"].median(), color="orange", linestyle="--", label=f"Median: £{df['total'].median():,.0f}")
axes[0].legend()

# Log-transformed (better for right-skewed data)
import numpy as np
axes[1].hist(np.log1p(df["total"]), bins=30, edgecolor="white", color="#F59E0B")
axes[1].set_title("Order Total — Log Scale")
axes[1].set_xlabel("log(1 + Total)")

plt.tight_layout()
plt.show()

Boxplot — five-number summary at a glance

fig, ax = plt.subplots(figsize=(8, 4))
ax.boxplot(df["total"].dropna(), vert=False, patch_artist=True,
           boxprops=dict(facecolor="#0D9488", alpha=0.7))
ax.set_title("Order Total — Boxplot")
ax.set_xlabel("Total (£)")

# The boxplot shows: min, Q1, median (line), Q3, max (whiskers), and outlier dots
plt.tight_layout()
plt.show()

KDE plot — smooth density estimate

fig, ax = plt.subplots(figsize=(10, 5))
df["total"].plot.kde(ax=ax, color="#0D9488", linewidth=2)
ax.fill_between(ax.lines[0].get_xdata(), ax.lines[0].get_ydata(), alpha=0.2, color="#0D9488")
ax.axvline(df["total"].median(), color="orange", linestyle="--", label="Median")
ax.set_title("Order Total — Density Plot")
ax.set_xlabel("Total (£)")
ax.legend()
plt.tight_layout()
plt.show()

Categorical Variables

Frequency table

# Count and percentage for each category
freq = pd.DataFrame({
    "count": df["status"].value_counts(),
    "pct": df["status"].value_counts(normalize=True).mul(100).round(1),
})
print(freq)

Sample output:

status count pct
completed 420 58.3
pending 180 25.0
cancelled 90 12.5
refunded 30 4.2

Bar chart

fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# Status distribution
status_counts = df["status"].value_counts()
axes[0].bar(status_counts.index, status_counts.values, color=["#0D9488", "#F59E0B", "#EF4444", "#94A3B8"])
axes[0].set_title("Orders by Status")
axes[0].set_ylabel("Count")

# Category distribution — horizontal for long labels
cat_counts = df["category"].value_counts()
axes[1].barh(cat_counts.index, cat_counts.values, color="#0D9488", alpha=0.8)
axes[1].set_title("Orders by Category")
axes[1].set_xlabel("Count")

plt.tight_layout()
plt.show()

Analysing Multiple Numeric Columns Together

numeric_cols = ["quantity", "unit_price", "total"]

# Pairwise distribution overview
df[numeric_cols].hist(bins=30, figsize=(12, 4), edgecolor="white", color="#0D9488")
plt.suptitle("Distribution of Numeric Columns", y=1.02)
plt.tight_layout()
plt.show()

# Boxplots for comparison
fig, ax = plt.subplots(figsize=(10, 5))
df[numeric_cols].boxplot(ax=ax)
ax.set_title("Numeric Columns — Boxplots")
plt.tight_layout()
plt.show()

What to Look For

After running univariate analysis, document your findings:

Finding What it tells you What to do
Right-skewed distribution A few high-value transactions pull the mean up Report median instead of mean; consider log transform
Bimodal distribution (two peaks) Two distinct customer segments Segment and analyse separately
Values clustered at round numbers Manual data entry (people enter £100, £500) Normal — note it
Unexpected mode A suspicious default value (0, -1, 9999) Investigate — may be a NULL placeholder
Flat distribution Very uniform data — may be randomly generated Investigate data source

Practice Exercises

Warm-up 1. Plot a histogram of unit_price. Add vertical lines for mean and median. 2. Print value counts (count and %) for the category column. 3. Plot a horizontal bar chart of order count by category.

Main 4. Plot distributions for all numeric columns using df.hist(). 5. Create boxplots for total grouped by status. Do any statuses have very different order value distributions? 6. The quantity column is right-skewed. Apply a log transform and compare the distribution before and after.

Stretch 7. Build a function univariate_report(df, col) that: (a) prints descriptive stats, (b) plots a histogram with mean/median lines, (c) plots a boxplot, and (d) if categorical, shows value counts. 8. Use sns.violinplot to compare the distribution of total across category. What do you notice?


Interview Questions

[Beginner] What information does a histogram show that a single number (like the mean) cannot?

[Mid-level] You have a column of customer order values. The histogram shows a bimodal distribution (two peaks). What might explain this, and how would you investigate?

[Senior] How would you summarise the univariate analysis findings from a 50-column dataset in a 5-minute presentation to a non-technical stakeholder?


← Descriptive Statistics · Next: Bivariate Analysis →