Matplotlib·Lesson 14·11 min·0/3 exercises

Uneven layouts

Draw the arrangement you want and get back Axes named after it

plt.subplot_mosaicfig.add_gridspecwidth_ratiosfig.add_subplotfig.add_axes

Watch it happen

Play it through, or step back and forth yourself.

fig, axes = plt.subplots(2, 3)

# every cell the same size.
# what if the top one should
# be the width of all three?

Real figures are lopsided: one wide chart across the top, three small ones beneath. A plain subplots(2, 3) can't express that, because every cell is the same size.

The idea

Real figures are lopsided. One wide trend across the top, three small panels beneath it; a big chart with a strip of context down the side. An even grid can't express that, because every cell is the same size.

subplot_mosaic

The modern answer, and it's a good one — you draw the layout:

fig, axd = plt.subplot_mosaic(
    """
    AAA
    BCD
    """,
    figsize=(9, 5),
    layout="constrained",
)

axd["A"].plot(when, cups)
axd["B"].barh(items, sold)

Repeat a name and that panel spans the cells you repeated it across. Two things follow, and both matter more than they sound:

The source looks like the figure. Anyone reading the code knows the arrangement without running it, which is not true of a page of add_subplot(gs[0, :2]).

You get a dict, keyed by your names. axd["A"], not axes[0, 1]. Insert a panel later and nothing renumbers — every existing reference keeps working.

One character per cell

Here's the catch, and it will bite you within about ten minutes. In the string form, every cell is a single character. matplotlib reads the string character-by-character, so this:

plt.subplot_mosaic("""
    trend trend
    items wait
""")

does not make two named panels. It tries to make an 11-column grid out of the letters of t, r, e, n, d… and raises ValueError: All of the rows must be the same length, which is a very confusing message if you thought you were writing words.

For readable names, pass nested lists — and this is the form worth defaulting to:

fig, axd = plt.subplot_mosaic([
    ["trend", "trend", "trend"],
    ["items", "wait",  "temp" ],
])

axd["trend"].plot(when, cups)

So: single letters for a quick sketch, nested lists for anything you'll keep. The letters are faster to type; the names are what makes the drawing code readable, which was the point of using mosaic in the first place.

Blanks

plt.subplot_mosaic("""
    AAB
    C.B
""")

plt.subplot_mosaic([["trend", "trend"],
                    ["items", "."     ]])

A "." leaves the cell empty — room for a figure-level legend, or a deliberately ragged shape. It never appears in the returned dict, and it's visible in the source, which a deleted Axes isn't.

GridSpec

Mosaic is built on GridSpec, and you drop down to it when the cells shouldn't be equal:

fig = plt.figure(figsize=(9, 5), layout="constrained")
gs = fig.add_gridspec(2, 3, width_ratios=[2, 1, 1], height_ratios=[1, 2])

top   = fig.add_subplot(gs[0, :])     # the whole top row
left  = fig.add_subplot(gs[1, 0])
mid   = fig.add_subplot(gs[1, 1])

gs slices like an array, so gs[0, :] is the top row and gs[:, 0] the left column. width_ratios=[2, 1, 1] makes the first column twice as wide as the others. GridSpec also nests — gs[1, 0].subgridspec(2, 1) — for the rare figure that needs it.

Mosaic accepts the same ratios, so you often don't need to drop down at all: subplot_mosaic(..., width_ratios=[2, 1, 1]).

Exact placement

inset = fig.add_axes([0.62, 0.62, 0.25, 0.22])   # [left, bottom, w, h]

Fractions of the figure, ignoring every grid. This is how you put an inset zoom in a corner or place a colourbar exactly. It's also outside the layout engine's control, so it won't be moved out of the way of anything — use it deliberately, not as a default.

(There's also ax.inset_axes([...]), which positions relative to a parent Axes rather than the figure — usually what you actually want for a zoom.)

Which to reach for

  • subplots — an even grid. Still the common case.
  • subplot_mosaic — anything lopsided. The right default now.
  • GridSpec — explicit ratios or nesting.
  • add_axes / inset_axes — insets and colourbars, placed by hand.

All four build the same kind of Axes. They differ only in how you describe where it goes.

See it run

The lesson's code, ready to run and to fiddle with.

Putting the kettle on…

Starting up…

Worked example

not graded

Already written and ready to go — press Run to see what it does, then change a number, a column name, anything, and run it again.

tryrearranging the lists to put "trend" down the left instead: ["trend", "items"], ["trend", "wait"].

Press Run — the output appears here.

Your turn

3 exercises. Write the code yourself, then press Check — a nudge and the answer are there if you want them.

Build a mosaic with a panel called top spanning two columns, and left and right beneath it. Return [sorted(axd), len(fig.axes)].

your answer

Same layout, but leave the bottom-right cell empty using ".". Return [sorted(axd), len(fig.axes)] — there should now be two panels, not three.

your answer

Use fig.add_gridspec to make a 2×3 grid whose first column is twice as wide as the others, then add one Axes spanning the whole top row. Return [gs.get_width_ratios(), len(fig.axes)].

your answer