Siksha Sarovar

Siksha Sarovar (sikshasarovar.com) is a free educational web application that helps students in India learn programming and prepare for academic and competitive exams. The platform offers structured coding courses (C, C++, Python, Java, HTML, CSS, PHP, Power BI, AI, Machine Learning, Data Science), complete university curriculum notes for BCA/MCA students with previous year question papers, Class 10 and Class 12 CBSE/HBSE school notes, and dedicated preparation material for SSC, UPSC, Banking, Railway and other government exams. Browsing the site is completely free and requires no account. Users may optionally sign in with Google solely to save their learning progress, quiz scores and personal preferences across devices.

Privacy Policy | Terms of Service | Contact Siksha Sarovar | About Siksha Sarovar

v4.0.9 · PWA
Siksha Sarovar logo
Siksha Sarovar
Your Learning Universe

Siksha Sarovar is a free e-learning platform for coding courses, BCA university notes and competitive exam preparation. Optional Google sign-in saves your learning progress across devices.

Initializing knowledge base…
Compiling modules 0%

Unit 4 — Data Visualization with Matplotlib

Lesson 40 of 46 in the free Introduction to Data Analytics notes on Siksha Sarovar, written by Rohit Jangra.

Matplotlib

Matplotlib is Python's foundational plotting library. Its pyplot module provides a MATLAB-like interface, and virtually every other Python visualization library (Seaborn, Pandas .plot()) is built on top of it.

Two Interfaces

InterfaceStyleWhen to use
pyplot (state-based)plt.plot(), plt.title() — acts on the "current" figureQuick single plots, interactive exploration
Object-orientedfig, ax = plt.subplots() then ax.plot()Recommended — explicit, required for subplots, reusable

Anatomy of a Figure

   FIGURE (the whole canvas)
     └── AXES (one plot area; a figure can hold many)
           ├── Axis (x and y, with limits/scale)
           ├── Ticks and tick labels
           ├── Title, xlabel, ylabel
           ├── Legend
           ├── Spines (the four border lines)
           └── Artists (lines, bars, markers, text, patches)

Basic Plots

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

months = ["Jan","Feb","Mar","Apr","May","Jun"]
revenue = [420, 465, 448, 512, 590, 634]
cost = [310, 330, 340, 355, 390, 410]

fig, ax = plt.subplots(figsize=(9, 5))

ax.plot(months, revenue, marker="o", linewidth=2, color="#168B99", label="Revenue")
ax.plot(months, cost, marker="s", linewidth=2, linestyle="--",
        color="#f59e0b", label="Cost")
ax.fill_between(months, cost, revenue, alpha=0.15, color="#10b981", label="Profit")

ax.set_title("Revenue vs Cost — H1 2026", fontsize=14, fontweight="bold")
ax.set_xlabel("Month", fontsize=11)
ax.set_ylabel("Amount (Rs lakh)", fontsize=11)
ax.legend(loc="upper left", frameon=False)
ax.grid(axis="y", alpha=0.3)
ax.set_axisbelow(True)
ax.spines[["top", "right"]].set_visible(False)

plt.tight_layout()
plt.show()

The Main Chart Types

np.random.seed(42)
fig, axes = plt.subplots(2, 3, figsize=(17, 9))

# 1. LINE — trend over time
x = np.linspace(0, 10, 100)
axes[0,0].plot(x, np.sin(x), color="#168B99", linewidth=2)
axes[0,0].set_title("Line Plot — trends")

# 2. BAR — category comparison
cats = ["Electronics", "Clothing", "Books", "Home"]
vals = [4200, 3100, 1800, 2600]
bars = axes[0,1].bar(cats, vals, color="#10b981", edgecolor="white")
axes[0,1].set_title("Bar Chart — comparison")
axes[0,1].tick_params(axis="x", rotation=30)
for bar in bars:                                    # direct labels
    axes[0,1].text(bar.get_x() + bar.get_width()/2, bar.get_height() + 60,
                   f"{int(bar.get_height())}", ha="center", fontsize=9)

# 3. HORIZONTAL BAR — long category names
axes[0,2].barh(cats, vals, color="#6366f1")
axes[0,2].set_title("Horizontal Bar")

# 4. HISTOGRAM — distribution (Unit 2)
data = np.random.normal(65, 12, 500)
axes[1,0].hist(data, bins=25, color="#f59e0b", edgecolor="white")
axes[1,0].axvline(data.mean(), color="#ef4444", linestyle="--",
                  label=f"mean = {data.mean():.1f}")
axes[1,0].set_title("Histogram — distribution")
axes[1,0].legend()

# 5. SCATTER — relationship (Unit 2)
xs = np.random.uniform(1, 12, 100)
ys = 5.5 * xs + 30 + np.random.normal(0, 6, 100)
axes[1,1].scatter(xs, ys, alpha=0.6, c=ys, cmap="viridis", s=45)
axes[1,1].set_title("Scatter Plot — relationship")

# 6. PIE — composition (few slices only)
axes[1,2].pie(vals, labels=cats, autopct="%1.1f%%", startangle=90,
              colors=["#168B99", "#10b981", "#f59e0b", "#6366f1"],
              explode=[0.05, 0, 0, 0])
axes[1,2].set_title("Pie Chart — composition")

plt.tight_layout()
plt.show()

Subplots and Layout

# Grid of subplots
fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(11, 8),
                         sharex=False, sharey=False)
axes = axes.flatten()               # convenient 1-D access

for i, ax in enumerate(axes):
    ax.plot(np.random.randn(50).cumsum(), color=f"C{i}")
    ax.set_title(f"Series {i+1}")

fig.suptitle("Four Random Walks", fontsize=15, fontweight="bold")
plt.tight_layout()
plt.show()
# Unequal layouts with GridSpec
fig = plt.figure(figsize=(12, 7))
gs = fig.add_gridspec(2, 3, hspace=0.35, wspace=0.3)

ax_big = fig.add_subplot(gs[0, :])          # top row, all columns
ax1 = fig.add_subplot(gs[1, 0])
ax2 = fig.add_subplot(gs[1, 1])
ax3 = fig.add_subplot(gs[1, 2])

ax_big.plot(months, revenue, marker="o", color="#168B99", linewidth=2)
ax_big.set_title("Main Metric — Revenue")

for ax, (title, data) in zip([ax1, ax2, ax3], [
    ("Distribution", np.random.normal(0, 1, 200)),
    ("Categories", None),
    ("Correlation", None),
]):
    if data is not None:
        ax.hist(data, bins=20, color="#10b981")
    else:
        ax.bar(cats[:3], vals[:3], color="#f59e0b")
    ax.set_title(title, fontsize=10)

plt.show()

Customisation Reference

fig, ax = plt.subplots(figsize=(9, 5))
ax.plot(months, revenue,
        color="#168B99",           # line colour (name, hex, or "C0")
        linewidth=2.5,             # thickness
        linestyle="-",             # '-', '--', '-.', ':'
        marker="o",                # 'o','s','^','D','*','x','+'
        markersize=8,
        markerfacecolor="white",
        markeredgewidth=2,
        alpha=0.9,
        label="Revenue")

ax.set_title("Fully Customised Chart", fontsize=15, fontweight="bold", pad=15)
ax.set_xlabel("Month", fontsize=12)
ax.set_ylabel("Revenue (Rs lakh)", fontsize=12)
ax.set_ylim(0, 700)                          # axis limits
ax.set_yticks(range(0, 701, 100))            # explicit ticks
ax.tick_params(axis="both", labelsize=10)
ax.legend(loc="lower right", fontsize=11, frameon=True, shadow=False)
ax.grid(True, axis="y", alpha=0.3, linestyle=":")
ax.set_axisbelow(True)

# Annotate a specific point
peak_idx = int(np.argmax(revenue))
ax.annotate(f"Peak: {revenue[peak_idx]}",
            xy=(peak_idx, revenue[peak_idx]),
            xytext=(peak_idx - 1.5, revenue[peak_idx] + 60),
            arrowprops=dict(arrowstyle="->", color="#ef4444", linewidth=1.5),
            fontsize=11, color="#ef4444", fontweight="bold")

ax.axhline(np.mean(revenue), color="gray", linestyle="--", alpha=0.7,
           label=f"Average = {np.mean(revenue):.0f}")

plt.tight_layout()
plt.show()

Colour and Style Reference

print(plt.style.available)
# ['ggplot', 'seaborn-v0_8', 'fivethirtyeight', 'bmh', 'dark_background', ...]

plt.style.use("seaborn-v0_8-whitegrid")     # apply a style globally
# plt.style.use("default")                   # reset

# Colormaps
# Sequential:  viridis, plasma, Blues, YlOrRd   -> ordered data (low to high)
# Diverging:   coolwarm, RdBu, seismic          -> data with a meaningful midpoint
# Qualitative: tab10, Set1, Set2, Paired        -> unordered categories

Plotting Directly from Pandas

df = pd.DataFrame({
    "month": months, "revenue": revenue, "cost": cost,
}).set_index("month")

fig, axes = plt.subplots(2, 2, figsize=(13, 8))

df.plot(ax=axes[0,0], marker="o", title="Line — .plot()")
df.plot(kind="bar", ax=axes[0,1], title="Grouped Bar", rot=0)
df.plot(kind="bar", stacked=True, ax=axes[1,0], title="Stacked Bar", rot=0)
df.plot(kind="area", ax=axes[1,1], alpha=0.6, title="Area Chart")

plt.tight_layout()
plt.show()

# Available kinds: 'line','bar','barh','hist','box','kde','density',
#                  'area','pie','scatter','hexbin'

Dual Axes and Secondary Scales

fig, ax1 = plt.subplots(figsize=(9, 5))

ax1.bar(months, revenue, color="#168B99", alpha=0.75, label="Revenue")
ax1.set_ylabel("Revenue (Rs lakh)", color="#168B99", fontsize=12)
ax1.tick_params(axis="y", labelcolor="#168B99")

ax2 = ax1.twinx()                       # share the x-axis, new y-axis
margin = [(r - c) / r * 100 for r, c in zip(revenue, cost)]
ax2.plot(months, margin, color="#ef4444", marker="o", linewidth=2.5, label="Margin %")
ax2.set_ylabel("Profit Margin (%)", color="#ef4444", fontsize=12)
ax2.tick_params(axis="y", labelcolor="#ef4444")
ax2.set_ylim(0, 50)

ax1.set_title("Revenue and Profit Margin", fontsize=14, fontweight="bold")
plt.tight_layout()
plt.show()
Use dual axes sparingly — they can imply a relationship between two series that does not exist. When in doubt, use two stacked subplots sharing the x-axis instead.

Saving Figures

fig, ax = plt.subplots(figsize=(8, 5))
ax.plot(months, revenue, marker="o", color="#168B99")
ax.set_title("Revenue Trend")

fig.savefig("revenue.png", dpi=300, bbox_inches="tight")       # raster, presentations
fig.savefig("revenue.pdf", bbox_inches="tight")                # vector, print/reports
fig.savefig("revenue.svg", bbox_inches="tight")                # vector, web
fig.savefig("revenue_transparent.png", dpi=300, transparent=True,
            bbox_inches="tight", facecolor="none")

plt.close(fig)     # free memory when generating many figures in a loop
ParameterPurpose
dpiResolution — 300 for print, 100 for screen
bbox_inches="tight"Trims excess whitespace
transparent=TrueTransparent background for slides
formatInferred from the extension; PNG/JPG raster, PDF/SVG vector

A Complete Analytical Dashboard

np.random.seed(42)
sales = pd.DataFrame({
    "date": pd.date_range("2026-01-01", periods=180),
    "revenue": (np.random.randn(180).cumsum() * 200 + 5000).clip(1000),
    "category": np.random.choice(["Electronics","Clothing","Books","Home"], 180),
    "units": np.random.randint(5, 60, 180),
})

fig = plt.figure(figsize=(15, 9))
gs = fig.add_gridspec(3, 3, hspace=0.45, wspace=0.3)

# KPI row
kpis = [("Total Revenue", f"Rs {sales['revenue'].sum()/1e5:.1f}L", "#168B99"),
        ("Total Units", f"{sales['units'].sum():,}", "#10b981"),
        ("Avg Daily Rev", f"Rs {sales['revenue'].mean():,.0f}", "#f59e0b")]
for i, (label, value, colour) in enumerate(kpis):
    ax = fig.add_subplot(gs[0, i])
    ax.text(0.5, 0.62, value, ha="center", fontsize=22, fontweight="bold", color=colour)
    ax.text(0.5, 0.28, label, ha="center", fontsize=11, color="gray")
    ax.axis("off")

# Trend
ax_trend = fig.add_subplot(gs[1, :])
ax_trend.plot(sales["date"], sales["revenue"], color="#168B99", alpha=0.4, linewidth=1)
ax_trend.plot(sales["date"], sales["revenue"].rolling(14).mean(),
              color="#ef4444", linewidth=2.5, label="14-day moving average")
ax_trend.set_title("Daily Revenue Trend", fontweight="bold")
ax_trend.legend(); ax_trend.grid(alpha=0.3)

# Category breakdown
ax_cat = fig.add_subplot(gs[2, 0])
cat_rev = sales.groupby("category")["revenue"].sum().sort_values()
ax_cat.barh(cat_rev.index, cat_rev.values, color="#10b981")
ax_cat.set_title("Revenue by Category", fontsize=11)

# Distribution
ax_dist = fig.add_subplot(gs[2, 1])
ax_dist.hist(sales["revenue"], bins=25, color="#6366f1", edgecolor="white")
ax_dist.set_title("Revenue Distribution", fontsize=11)

# Monthly
ax_month = fig.add_subplot(gs[2, 2])
monthly = sales.set_index("date").resample("ME")["revenue"].sum()
ax_month.bar(range(len(monthly)), monthly.values, color="#f59e0b")
ax_month.set_xticks(range(len(monthly)))
ax_month.set_xticklabels([d.strftime("%b") for d in monthly.index])
ax_month.set_title("Monthly Revenue", fontsize=11)

fig.suptitle("Sales Analytics Dashboard — H1 2026", fontsize=17, fontweight="bold")
plt.show()

Matplotlib gives complete control at the cost of verbosity. The next lesson, Seaborn, provides statistical charts in a fraction of the code.