Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions python-matplotlib-guide/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Python Plotting With Matplotlib (Guide)

This folder provides the code examples for the Real Python tutorial [Python Plotting With Matplotlib (Guide)](https://realpython.com/python-matplotlib-guide/).

The tutorial works through its examples in the REPL. Here, each section of the tutorial is collected into a script you can run end to end, so you can see every result at once and then change the code to see what happens. Lines that the tutorial shows as REPL output are wrapped in `print()` calls, and the canonical `import matplotlib.pyplot as plt` plus `np.random.seed(444)` from the tutorial's opening sit at the top of each script that needs them.

## Setup

Create and activate a virtual environment, then install the requirements:

```bash
$ python -m venv venv
$ source venv/bin/activate
(venv) $ python -m pip install -r requirements.txt
```

`requirements.txt` pins the versions the tutorial's examples were verified against. Newer releases will usually work too, but matplotlib changes the repr of its objects and the list of bundled style sheets between releases, so output from other versions won't match the tutorial exactly.

## Usage

After creating and activating your virtual environment, and installing the dependencies, you should be able to run each individual file normally:

```bash
(venv) $ python subplots_notation.py
```

Each script prints its results to the terminal and opens a Matplotlib window for each figure it builds — close each window to move on to the next.

| File | Tutorial section |
| --- | --- |
| `object_hierarchy.py` | The Matplotlib Object Hierarchy |
| `subplots_notation.py` | Understanding `plt.subplots()` Notation |
| `gridspec_housing.py` | Understanding `plt.subplots()` Notation (the `subplot2grid()` and California housing examples) |
| `figures_behind_the_scenes.py` | The "Figures" Behind The Scenes |
| `imshow_and_matshow.py` | A Burst of Color: `imshow()` and `matshow()` |
| `plotting_in_pandas.py` | Plotting in Pandas |
| `appendix_a_configuration.py` | Appendix A: Configuration and Styling |
| `appendix_b_interactive_mode.py` | Appendix B: Interactive Mode |

Two of the scripts download their data at runtime, so they need an internet connection: `gridspec_housing.py` pulls the California housing archive from figshare, and `plotting_in_pandas.py` pulls the CBOE VIX series from FRED.

A few results can't match the tutorial byte for byte. The `id()` values in `figures_behind_the_scenes.py` are memory addresses, so only the comparisons between them are meaningful. And `appendix_b_interactive_mode.py` first prints `False` rather than the tutorial's `True`, because matplotlib starts a script with interactive mode off, while the tutorial's session already had it on.

Two code blocks in the tutorial aren't shipped here, because there's nothing for you to run: the single-line canonical imports in "Pylab: What Is It, and Should I Use It?", and the abridged excerpt of matplotlib's own `pyplot.py` source in "Stateful Versus Stateless Approaches".

You can find more information and context on the code in [Python Plotting With Matplotlib (Guide)](https://realpython.com/python-matplotlib-guide/).
27 changes: 27 additions & 0 deletions python-matplotlib-guide/appendix_a_configuration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""Configure matplotlib styles and rc parameters.

Covers "Appendix A: Configuration and Styling" of
https://realpython.com/python-matplotlib-guide/
"""

import matplotlib.pyplot as plt


def main():
# All of the module objects starting with "rc" are a means to
# interact with your plot styles and settings.
print([attr for attr in dir(plt) if attr.startswith("rc")])

# These two syntaxes are equivalent for adjusting settings.
plt.rc("lines", linewidth=2, color="r") # Syntax 1

plt.rcParams["lines.linewidth"] = 2 # Syntax 2
plt.rcParams["lines.color"] = "r"

# A style is just a predefined cluster of custom settings.
print(plt.style.available)
plt.style.use("fivethirtyeight")


if __name__ == "__main__":
main()
37 changes: 37 additions & 0 deletions python-matplotlib-guide/appendix_b_interactive_mode.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""Toggle matplotlib's interactive mode on and off.

Covers "Appendix B: Interactive Mode" of
https://realpython.com/python-matplotlib-guide/

The tutorial runs these lines in an interactive session, where
interactive mode is already on, so the first value it prints is True.
Run as a script, matplotlib starts with interactive mode off, so the
first value below prints False.
"""

import matplotlib.pyplot as plt
import numpy as np


def main():
print(plt.rcParams["interactive"]) # or: plt.isinteractive()

plt.ioff()
print(plt.rcParams["interactive"])

# With interactive mode off, plt.show() is what displays the figure.
x = np.arange(-4, 5)
y1 = x**2
y2 = 10 / (x**2 + 1)
fig, ax = plt.subplots()
ax.plot(x, y1, "rx", x, y2, "b+", linestyle="solid")
ax.fill_between(
x, y1, y2, where=y2 > y1, interpolate=True, color="green", alpha=0.3
)
lgnd = ax.legend(["y1", "y2"], loc="upper center", shadow=True)
lgnd.get_frame().set_facecolor("#ffb19a")
plt.show()


if __name__ == "__main__":
main()
37 changes: 37 additions & 0 deletions python-matplotlib-guide/figures_behind_the_scenes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""Inspect the Figures that matplotlib keeps around in memory.

Covers the "The 'Figures' Behind The Scenes" section of
https://realpython.com/python-matplotlib-guide/

The id() values below are memory addresses, so your numbers will differ
from the ones printed in the tutorial. Only the comparisons match.
"""

import matplotlib.pyplot as plt


def get_all_figures():
return [plt.figure(i) for i in plt.get_fignums()]


def main():
fig1, ax1 = plt.subplots()

print(id(fig1))
print(id(plt.gcf())) # `fig1` is the current figure.

fig2, ax2 = plt.subplots()
print(id(fig2) == id(plt.gcf())) # The current figure is now `fig2`.

# Both figures are still hanging around in memory, each with a
# corresponding ID number (1-indexed, in MATLAB style).
print(plt.get_fignums())
print(get_all_figures())

# Close each figure after use to avoid a MemoryError.
plt.close("all")
print(get_all_figures())


if __name__ == "__main__":
main()
76 changes: 76 additions & 0 deletions python-matplotlib-guide/gridspec_housing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
"""Lay out uneven subplots with gridspec and subplot2grid().

Covers the gridspec examples in the "Understanding plt.subplots()
Notation" section of https://realpython.com/python-matplotlib-guide/

The California housing data is downloaded from figshare at runtime, so
this script needs an internet connection.
"""

from io import BytesIO
import tarfile
from urllib.request import urlopen

import matplotlib.pyplot as plt
import numpy as np


def load_housing():
"""Pull the macroeconomic California housing data."""
url = "https://ndownloader.figshare.com/files/5976036"
b = BytesIO(urlopen(url).read())
fpath = "CaliforniaHousing/cal_housing.data"

with tarfile.open(mode="r", fileobj=b) as archive:
housing = np.loadtxt(archive.extractfile(fpath), delimiter=",")

return housing


def add_titlebox(ax, text):
"""Place a text box inside a plot as an "in-plot title"."""
ax.text(
0.55,
0.8,
text,
horizontalalignment="center",
transform=ax.transAxes,
bbox=dict(facecolor="white", alpha=0.6),
fontsize=12.5,
)
return ax


def main():
housing = load_housing()

# The "response" variable y is an area's average home value. pop
# and age are the area's population and average house age.
y = housing[:, -1]
pop, age = housing[:, [4, 7]].T

# A 3x2 grid where ax1 spans two columns and two rows.
gridsize = (3, 2)
# The tutorial binds this Figure to `fig`, but never uses it.
plt.figure(figsize=(12, 8))
ax1 = plt.subplot2grid(gridsize, (0, 0), colspan=2, rowspan=2)
ax2 = plt.subplot2grid(gridsize, (2, 0))
ax3 = plt.subplot2grid(gridsize, (2, 1))

ax1.set_title(
"Home value as a function of home age & area population",
fontsize=14,
)
sctr = ax1.scatter(x=age, y=pop, c=y, cmap="RdYlGn")
plt.colorbar(sctr, ax=ax1, format="$%d")
ax1.set_yscale("log")
ax2.hist(age, bins="auto")
ax3.hist(pop, bins="auto", log=True)

add_titlebox(ax2, "Histogram: home age")
add_titlebox(ax3, "Histogram: area population (log scl.)")
plt.show()


if __name__ == "__main__":
main()
45 changes: 45 additions & 0 deletions python-matplotlib-guide/imshow_and_matshow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
"""Visualize raw numerical arrays as colored grids.

Covers the "A Burst of Color: imshow() and matshow()" section of
https://realpython.com/python-matplotlib-guide/
"""

import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.axes_divider import make_axes_locatable
import numpy as np

np.random.seed(444)


def main():
# Two distinct grids, built with some fancy NumPy indexing.
x = np.diag(np.arange(2, 12))[::-1]
x[np.diag_indices_from(x[::-1])] = np.arange(2, 12)
x2 = np.arange(x.size).reshape(x.shape)

# Toggle "off" all axis labels and ticks with a dict comprehension.
sides = ("left", "right", "top", "bottom")
nolabels = {s: False for s in sides}
nolabels.update({"label%s" % s: False for s in sides})
print(nolabels)

with plt.rc_context(rc={"axes.grid": False}):
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(8, 4))
ax1.matshow(x)
img2 = ax2.matshow(x2, cmap="RdYlGn_r")
for ax in (ax1, ax2):
ax.tick_params(axis="both", which="both", **nolabels)
for i, j in zip(*x.nonzero()):
ax1.text(j, i, x[i, j], color="white", ha="center", va="center")

# The colorbar needs a new Axes within `fig`.
divider = make_axes_locatable(ax2)
cax = divider.append_axes("right", size="5%", pad=0)
plt.colorbar(img2, cax=cax, ax=[ax1, ax2])
fig.suptitle("Heatmaps with `Axes.matshow`", fontsize=16)

plt.show()


if __name__ == "__main__":
main()
22 changes: 22 additions & 0 deletions python-matplotlib-guide/object_hierarchy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"""Traverse the nested objects that make up a matplotlib graphic.

Covers the "The Matplotlib Object Hierarchy" section of
https://realpython.com/python-matplotlib-guide/
"""

import matplotlib.pyplot as plt


def main():
# A Figure is the outermost container for a matplotlib graphic.
fig, _ = plt.subplots()
print(type(fig))

# Attribute notation walks down the hierarchy: Figure -> Axes ->
# yaxis -> major ticks.
one_tick = fig.axes[0].yaxis.get_major_ticks()[0]
print(type(one_tick))


if __name__ == "__main__":
main()
79 changes: 79 additions & 0 deletions python-matplotlib-guide/plotting_in_pandas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
"""Mix pandas plotting methods with traditional matplotlib calls.

Covers the "Plotting in Pandas" section of
https://realpython.com/python-matplotlib-guide/

The VIX series is downloaded from FRED at runtime, so this script needs
an internet connection.
"""

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

np.random.seed(444)


def series_introspection():
"""Show that pandas' plot() wraps the state-based plt.plot()."""
s = pd.Series(np.arange(5), index=list("abcde"))
ax = s.plot()

print(type(ax))
print(id(plt.gca()) == id(ax))


def volatility_regime():
"""Plot the moving average of the VIX, colored by regime state."""
url = "https://fred.stlouisfed.org/graph/fredgraph.csv?id=VIXCLS"
vix = (
pd.read_csv(url, index_col=0, parse_dates=True, na_values=".")
.squeeze("columns")
.dropna()
)
ma = vix.rolling("90D").mean()
state = pd.cut(ma, bins=[-np.inf, 14, 18, 24, np.inf], labels=range(4))

cmap = plt.get_cmap("RdYlGn_r")
ma.plot(
color="black",
linewidth=1.5,
marker="",
figsize=(8, 4),
label="VIX 90d MA",
)
ax = plt.gca() # Get the current Axes that ma.plot() references
ax.set_xlabel("")
ax.set_ylabel("90d moving average: CBOE VIX")
ax.set_title("Volatility Regime State")
ax.grid(False)
ax.legend(loc="upper center")
ax.set_xlim(xmin=ma.index[0], xmax=ma.index[-1])

trans = mtransforms.blended_transform_factory(ax.transData, ax.transAxes)
for i, color in enumerate(cmap([0.2, 0.4, 0.6, 0.8])):
ax.fill_between(
ma.index,
0,
1,
where=state == i,
facecolor=color,
transform=trans,
)
ax.axhline(
vix.mean(),
linestyle="dashed",
color="xkcd:dark grey",
alpha=0.6,
label="Full-period mean",
marker="",
)
plt.show()


if __name__ == "__main__":
for section in (series_introspection, volatility_regime):
title = section.__name__.replace("_", " ").title()
print(f"\n{title}\n{'-' * len(title)}")
section()
3 changes: 3 additions & 0 deletions python-matplotlib-guide/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
matplotlib==3.11.2
numpy==2.5.3
pandas==3.0.5
Loading
Loading