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?