Matplotlib Basics
The core plot types you'll use constantly: line, scatter, bar, histogram, and box plots — plus
subplots for laying several out together.
import numpy as np
import [Link] as plt
1. The basic anatomy of a plot
Two ways to build a plot:
Quick, implicit style (fine for one-off plots):
[Link](x, y)
[Link]("My Plot")
[Link]("X")
[Link]("Y")
[Link]()
Explicit fig, ax style (recommended — scales cleanly to subplots and gives you more
control):
fig, ax = [Link]()
[Link](x, y)
ax.set_title("My Plot")
ax.set_xlabel("X")
ax.set_ylabel("Y")
[Link]()
fig is the whole figure (the canvas); ax is a single set of axes (one plot) living on it. Once
you start using subplots, the ax object is what you'll call all your plotting methods on.
2. Line plots
Good for continuous data, trends over time, or any sequence where order matters.
x = [Link](0, 10, 100)
y = [Link](x)
fig, ax = [Link]()
[Link](x, y, label="sin(x)")
[Link](x, [Link](x), label="cos(x)", linestyle="--")
[Link]()
[Link]()
Useful plot() arguments: color , linestyle ( '-' , '--' , ':' ), linewidth , marker
( 'o' , 'x' , 's' ), label (for the legend).
3. Scatter plots
Good for showing the relationship between two numeric variables, or spotting
clusters/outliers.
x = [Link](200)
y = 2 * x + [Link](200)
fig, ax = [Link]()
[Link](x, y, alpha=0.6)
ax.set_xlabel("X")
ax.set_ylabel("Y")
[Link]()
alpha (0–1) controls transparency — essential once points start overlapping. You can also
map a third variable to point color or size:
sizes = [Link](10, 200, size=200)
colors = [Link](200)
[Link](x, y, s=sizes, c=colors, cmap="viridis", alpha=0.6)
4. Bar charts
Good for comparing values across discrete categories.
categories = ["A", "B", "C", "D"]
values = [23, 45, 12, 38]
fig, ax = [Link]()
[Link](categories, values)
[Link]()
Horizontal bars ( barh ) work well when category labels are long:
[Link](categories, values)
Grouped bars — comparing two series side by side per category:
x = [Link](len(categories))
width = 0.35
fig, ax = [Link]()
[Link](x - width/2, values_2023, width, label="2023")
[Link](x + width/2, values_2024, width, label="2024")
ax.set_xticks(x)
ax.set_xticklabels(categories)
[Link]()
[Link]()
5. Histograms
Good for seeing the distribution of a single numeric variable.
data = [Link](loc=50, scale=10, size=1000)
fig, ax = [Link]()
[Link](data, bins=30, edgecolor="black")
ax.set_xlabel("Value")
ax.set_ylabel("Frequency")
[Link]()
bins controls how many buckets the data is split into — worth experimenting with (too few
hides structure, too many looks noisy). Set density=True to normalize the histogram into a
probability density (useful when comparing distributions of different sizes).
Overlaying two distributions:
[Link](data1, bins=30, alpha=0.5, label="Group 1")
[Link](data2, bins=30, alpha=0.5, label="Group 2")
[Link]()
6. Box plots
Good for comparing the spread/median/outliers of one or more groups at a glance.
group1 = [Link](0, 1, 100)
group2 = [Link](2, 1.5, 100)
group3 = [Link](-1, 0.5, 100)
fig, ax = [Link]()
[Link]([group1, group2, group3], tick_labels=["Group 1", "Group 2",
"Group 3"])
[Link]()
The box shows the interquartile range (25th–75th percentile), the line inside is the median,
whiskers extend to the rest of the distribution within 1.5×IQR, and points beyond that are
plotted individually as outliers. This is often more informative than a histogram when you're
comparing several groups at once.
7. Subplots — multiple plots in one figure
[Link](nrows, ncols) returns a figure plus a grid of axes:
fig, axes = [Link](2, 2, figsize=(10, 8))
axes[0, 0].plot(x, y)
axes[0, 0].set_title("Line")
axes[0, 1].scatter(x, y)
axes[0, 1].set_title("Scatter")
axes[1, 0].hist(data, bins=30)
axes[1, 0].set_title("Histogram")
axes[1, 1].bar(categories, values)
axes[1, 1].set_title("Bar")
plt.tight_layout() # prevents titles/labels from overlapping between
subplots
[Link]()
With a single row or column ( [Link](1, 3) ), axes is a 1D array — index it as
axes[0] , axes[1] , etc.
With nrows=1, ncols=1 (i.e. just [Link]() ), axes is a single Axes object, not
an array.
figsize=(width, height) is in inches — worth setting explicitly once you have more
than one subplot, or the default size gets cramped.
sharex=True / sharey=True link the axis scales across subplots, which makes
comparisons fairer.
Slicing the axes grid
axes from a 2D subplots() call is just a NumPy array of Axes objects, so normal NumPy
slicing works on it.
fig, axes = [Link](3, 3, figsize=(10, 10))
axes[0, 0] # single plot: row 0, column 0
axes[1, :] # the entire middle ROW (an array of 3 Axes)
axes[:, 0] # the entire first COLUMN (an array of 3 Axes)
axes[:2, :2] # the top-left 2x2 block of plots
axes[-1, -1] # bottom-right plot
This is handy for applying a setting to a whole row or column at once:
for ax in axes[0, :]: # every plot in the top row
ax.set_title("Top row")
for ax in axes[:, -1]: # every plot in the last column
ax.set_ylabel("shared label")
Looping over every subplot regardless of grid shape — .flat gives you a flat iterator
over a 2D axes array, so you don't need nested loops:
data_list = [d1, d2, d3, d4, d5, d6]
fig, axes = [Link](2, 3, figsize=(12, 6))
for ax, data in zip([Link], data_list):
[Link](data, bins=20)
plt.tight_layout()
[Link]()
[Link]() does the same thing but returns a copy (a real array) instead of a view —
use it if you need to index into the flattened result more than once ( flat is a one-shot
iterator).
Quick reference
Plot type Function Best for
Line [Link]() trends, continuous data over an ordered
axis
Scatter [Link]() relationship between two numeric
variables
Bar [Link]() / [Link]() comparing discrete categories
Histogram [Link]() distribution of one numeric variable
Box plot [Link]() comparing spread/outliers across groups
Grid of [Link](nrows, multiple plots in one figure
plots ncols)
Common finishing touches on any ax : set_title() , set_xlabel() , set_ylabel() ,
legend() , grid(True) .
Further reading
Matplotlib pyplot tutorial (official)
Matplotlib plot types gallery