DS DevShelfHub Projects · AI tools
Articles / Learn Matplotlib in 30 Minutes: A Practical Python Plotting Tutorial

AI Engineering

Learn Matplotlib in 30 Minutes: A Practical Python Plotting Tutorial

By DevShelfHub

A complete, plain-English tour of Matplotlib — the Figure / Axes / Axis mental model, line plots, scatter plots with colour maps, single and grouped bar charts, histograms with overlapping distributions, subplots, plotting from Pandas DataFrames, advanced annotations, themes and pie charts, and how to save figures to PNG / PDF / SVG. The fundamentals that get you to confident usage without 12 hours of docs.

Learn Matplotlib in 30 Minutes: A Practical Python Plotting Tutorial

Introduction

If you’re working in data science, machine learning, scientific computing, or any flavour of analysis where you need to see your data, Matplotlib is the foundational plotting library to learn. It’s not the prettiest by default and it isn’t the most modern API in Python, but it’s the one that runs underneath most of the others (Seaborn, Pandas .plot()) and the one you’ll see in nearly every real codebase.

This guide takes you from zero to comfortable: the mental model, the architecture, every chart type worth knowing, the customisations that make plots look like something you’d ship, and how to integrate with NumPy and Pandas. By the end you’ll be able to look at a chart in someone’s notebook and reproduce it.

📚 Table of contents

  • What Matplotlib is and why it’s still the default
  • Setup — install, Jupyter notebook, imports
  • The Figure / Axes / Axis mental model
  • Line plots — the simplest case
  • Object-oriented vs PLT global style
  • Scatter plots with colour maps
  • Bar charts, grouped bars, and horizontal bars
  • Histograms and overlapping distributions
  • Subplots and tight layouts
  • Plotting from Pandas DataFrames
  • Advanced customisation — annotations, vertical lines, text boxes
  • Themes, styles, and pie charts
  • Saving figures to PNG / PDF / SVG
  • Frequently asked questions

📊 What Matplotlib is

Matplotlib is a comprehensive library for creating static and interactive visualisations in Python, with a MATLAB-like interface for plotting data. It pairs constantly with NumPy and Pandas — NumPy provides the numerical arrays you’re plotting, Pandas the DataFrames, and Matplotlib draws the picture.

Strengths: massive flexibility, supports nearly every chart type, infinite customisation, runs everywhere. Trade-offs: the API is sprawling, defaults are dated, and you’ll spend the first few sessions getting used to the dual interface (state-based plt.… and object-oriented fig, ax).

🛠️ Setup

Install Matplotlib along with NumPy and Pandas:

pip install matplotlib numpy pandas    # Windows
pip3 install matplotlib numpy pandas   # macOS / Linux

The easiest place to play with plots is a Jupyter notebook. In VS Code or Cursor, hit Ctrl/Cmd + Shift + P, type Jupyter: Create New Notebook, and pick a Python kernel. Cells run inline, plots render under the cell.

Standard imports for any Matplotlib work:

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

%matplotlib inline          # auto-show plots in Jupyter
plt.style.use('default')    # or 'seaborn-v0_8', 'ggplot', 'dark_background', etc.

🏛️ The Figure / Axes / Axis mental model

Three terms that sound alike and confuse everyone:

Figure

The top-level container — the whole window or page that holds your plots. Think canvas.

Axes

A single plot region inside the figure. A figure can hold many. Think individual painting on the canvas.

Axis

A single dimension on a plot — the x-axis, the y-axis. Think the rulers on each painting.

📈 Line plots — the simplest case

x = [1, 2, 3, 4, 5]
y = [2, 4, 6, 8, 10]

plt.plot(x, y)
plt.xlabel("X axis")
plt.ylabel("Y axis")
plt.title("My first plot")
plt.show()

In a notebook you can skip plt.show(); the cell auto-renders. In a regular Python script you need it to actually display the window. Multiple plt.plot() calls before plt.show() overlay lines on the same axes.

🧱 Object-oriented vs PLT global

Two equivalent ways to make the same plot:

# State-based (PLT global)
plt.plot(x, y)
plt.title("Hello")

# Object-oriented (recommended for complex plots)
fig, ax = plt.subplots()
ax.plot(x, y)
ax.set_title("Hello")

The object-oriented style scales better when you have multiple subplots or want explicit control over which axes you’re drawing on. Use it for anything beyond a single quick chart.

⚫ Scatter plots with colour maps

n = 100
x = np.random.rand(n)
y = np.random.rand(n)
colors = np.random.rand(n)        # 0..1 mapped through a colour map
sizes  = 1000 * np.random.rand(n) # marker sizes in points^2

plt.figure(figsize=(8, 5))
plt.scatter(x, y, c=colors, s=sizes, alpha=0.6, cmap='viridis')
plt.colorbar()
plt.show()

Useful colour maps: viridis (perceptually uniform default), plasma, magma, coolwarm, Set1 for categorical. Pass alpha less than 1 to make overlapping points visible.

📊 Bar charts, grouped bars, horizontal bars

Basic vertical bar:

categories = ['A', 'B', 'C', 'D']
values     = [10, 24, 17, 32]
plt.bar(categories, values, color='steelblue', edgecolor='black', linewidth=1)

Horizontal? Swap bar for barh and everything else stays the same.

Grouped bars (e.g. product A/B/C across Q1–Q4) need a little arithmetic on the x-axis to shift each group’s bars left or right:

categories = ['Q1', 'Q2', 'Q3', 'Q4']
x = np.arange(len(categories))
width = 0.25

plt.bar(x - width, product_a, width, label='A')
plt.bar(x,         product_b, width, label='B')
plt.bar(x + width, product_c, width, label='C')

plt.xticks(x, categories)   # show categorical labels at the centre positions
plt.legend()

Mental model: x is the centre, you shift the left bar by -width and the right by +width, then re-label the ticks so they read as Q1–Q4.

📉 Histograms & overlapping distributions

data = np.random.randn(1000)
plt.hist(data, bins=30, color='steelblue', edgecolor='black')

# Multiple distributions overlaid
plt.hist(data1, bins=30, alpha=0.5, label='Group 1')
plt.hist(data2, bins=30, alpha=0.5, label='Group 2')
plt.legend()

With alpha<1, overlapping regions blend visually — useful to compare distributions. With alpha=1, the second histogram completely covers the first wherever they overlap, which is occasionally what you want.

🧩 Subplots

fig, axes = plt.subplots(2, 2, figsize=(10, 8))
axes[0, 0].plot(x, np.sin(x))
axes[0, 1].plot(x, np.cos(x))
axes[1, 0].scatter(np.random.rand(50), np.random.rand(50))
axes[1, 1].bar(['A', 'B', 'C'], [3, 7, 5])
plt.tight_layout()
plt.show()

Index axes like a 2D array: [0, 0] top-left, [1, 1] bottom-right. tight_layout() compacts the spacing so subplots don’t bleed into each other.

🐼 Plotting from Pandas DataFrames

Two paths:

  • Pass DataFrame columns into Matplotlib directly: plt.plot(df['date'], df['sales']).
  • Use df.plot(): df.plot(x='date', y='sales', ax=ax) — cleaner when you have many columns and want quick visuals.

Both produce real Matplotlib axes you can keep customising afterwards.

🎯 Advanced — annotations, vertical lines, text boxes

fig, ax = plt.subplots(figsize=(10, 5))
ax.plot(x, y, 'b-', linewidth=2)

# Annotation with arrow pointing at a peak
peak = np.argmax(y)
ax.annotate('Maximum',
            xy=(x[peak], y[peak]),
            xytext=(x[peak] + 1, y[peak] - 0.3),
            arrowprops=dict(arrowstyle='->', color='red', lw=2),
            fontsize=12, fontweight='bold')

# Text box anchored to axes coords
ax.text(0.05, 0.95, 'Sine wave',
        transform=ax.transAxes,
        fontsize=11,
        verticalalignment='top',
        bbox=dict(boxstyle='round', facecolor='wheat'))

# Vertical and horizontal reference lines
ax.axvline(x=5, color='grey', linestyle='--')
ax.axhline(y=0, color='grey', linestyle=':')

annotate() draws an arrow from xytext to xy. axvline and axhline draw reference lines across the whole plot — perfect for marking thresholds, current dates, or boundaries.

🎨 Themes, styles, pie charts

Try different themes:

print(plt.style.available)
plt.style.use('ggplot')   # or 'seaborn-v0_8', 'dark_background', 'fivethirtyeight'

Pie chart with an exploded slice:

sizes  = [30, 25, 20, 15, 10]
labels = ['A', 'B', 'C', 'D', 'E']
explode = (0.1, 0, 0, 0, 0)   # pop out the first slice

plt.pie(sizes, explode=explode, labels=labels, autopct='%1.1f%%',
        shadow=True, startangle=90)
plt.show()

💾 Saving figures

plt.savefig('chart.png', dpi=300, bbox_inches='tight')
plt.savefig('chart.pdf')           # vector format, scales cleanly
plt.savefig('chart.svg')           # for the web

Use PNG for slides, PDF or SVG when scale matters. bbox_inches='tight' trims excess whitespace; without it your saved chart will have padding the notebook hides.

✨ Best practices & common mistakes

✅ Do

  • Use the object-oriented API for anything non-trivial.
  • Always label axes and add a title.
  • Call tight_layout() before showing or saving.
  • Use alpha when points overlap.

❌ Don’t

  • Mix state-based and object-oriented styles in the same plot.
  • Forget plt.show() in scripts (notebooks auto-render).
  • Use red/green for categorical contrast — accessibility-hostile.
  • Hard-code dpi without checking resolution at output size.

Explore More on DevShelf

Learn Matplotlib in 30 Minutes: A Practical Python Plotting Tutorial FAQ

Should I use Matplotlib or Seaborn?

Seaborn for statistical plots with nicer defaults; Matplotlib when you need full control. Seaborn is built on top of Matplotlib, so they compose.

Is Matplotlib slow?

For up to ~10K points it's instant. Above 100K, plot a sampled subset or switch to Datashader / Plotly for interactivity.

What about interactive plots?

Plotly and Bokeh are better choices. Matplotlib's strength is static publication-quality output.

How do I make plots that don’t look like Excel 2003?

Apply a modern style (seaborn-v0_8, fivethirtyeight), set a custom figure size, use restrained colour palettes, and call tight_layout().

How do I save high-resolution figures for print?

Use savefig('file.pdf') for vector or savefig('file.png', dpi=300) for raster. Skip JPG — lossy compression butchers thin lines.