Axes object is not subscriptable means you’re indexing a single Matplotlib Axes as if it were a list or array.
You run a plot, it renders, and then Python stops with a TypeError. The message often reads “’AxesSubplot’ object is not subscriptable” or “’Axes’ object is not subscriptable.” It feels odd because you can see your chart, yet a later line crashes the cell.
The fix is simple once you know one fact: sometimes Matplotlib gives you one Axes, and sometimes it gives you a collection of Axes. Your code only breaks when you treat one like many.
This article shows the small set of patterns that cause the error, the quickest way to check what your variable holds, and the fixes that stay stable when your subplot grid changes. You can copy the snippets into a notebook and move on with less friction.
Why The Error Happens In Plain Terms
Square brackets mean “pick an item.” Lists, tuples, NumPy arrays, and dictionaries allow that. A single Matplotlib Axes does not. An Axes is a plotting surface with methods like plot(), scatter(), imshow(), and set_title(). It’s a workbench, not a shelf of items.
What “Subscriptable” Means In This Case
When Python says an object is “not subscriptable,” it’s telling you the object does not implement indexing with []. That usually means you mixed up a container and a plain object. In Matplotlib terms, you wrote code like ax[0] or axs[0, 0] on a variable that holds just one Axes.
Why Matplotlib Sometimes Returns One Axes
plt.subplots() is a convenience helper. If you ask for one subplot, Matplotlib returns a Figure and a single Axes. If you ask for multiple subplots, Matplotlib returns a Figure and an array of Axes. The return type changes with your grid shape because Matplotlib squeezes extra dimensions by default.
That “squeeze” behavior is the core reason this error shows up during refactors. A notebook starts as a 2×2 set of plots, then you switch to a 1×1 or 1×N layout, and indexing that used to be valid is now aimed at a single Axes.
Spot What You Actually Have In Your Variable
Before rewriting code, confirm what your axes variable actually is. Two checks catch most cases.
Use Type And Shape Checks
- Print the type — Run
print(type(axs))to see whether you have anAxesor an array of Axes. - Check for shape — If
hasattr(axs, "shape")is true, you likely have a NumPy object array. - Check for ravel — If
hasattr(axs, "ravel")is true, you can flatten it for safe looping.
Read The Subplots Call That Created It
Look at the exact line that created your axes. These calls look close, yet they do different things:
fig, ax = plt.subplots() # one Axes
fig, axs = plt.subplots(2, 2) # array of Axes
A subtle twist happens with a row or column of plots. A 1×3 grid gives a 1D array. A 3×1 grid gives a 1D array. A 2×2 grid gives a 2D array. A 1×1 grid gives a single Axes unless you disable squeezing.
Read The Traceback Like A Map
When the exception fires, the traceback points to the first place you indexed an Axes. Don’t chase the last plotting call. Chase the first bracket that follows an axes variable.
- Confirm the variable name — Make sure the bracket is on
axoraxs, not on your data. - Check earlier cells — A variable can change type across cells, so the creation line might be above the visible error.
- Print then delete — A single
print(type(...))is faster than guessing.
Know The Two Indexing Styles
NumPy arrays let you use comma indexing like axs[0, 0]. Plain Python lists use nested indexing like axs[0][0]. If you normalize to lists, stick to the nested style.
If you see axs[0, 0] in code copied from a tutorial, that implies axs is a NumPy array, not a list of lists. It’s fine either way, just stay consistent.
Axes Object Is Not Subscriptable In Matplotlib Subplots
This is the core fix area. Match your indexing style to what Matplotlib returned, and the error disappears.
Fix A 1×1 Plot By Dropping Indexing
If you created one subplot, treat it like one subplot. Don’t index it. Call methods directly on the Axes.
fig, ax = plt.subplots()
ax.plot(x, y)
ax.set_title("My plot")
- Use ax directly — Replace
ax[0]withax. - Keep names honest — Use
axfor one andaxsfor many, so your eyes catch mistakes.
Fix 1×N Or N×1 Grids With One Index
When squeezing applies to a single row or column, Matplotlib returns a 1D array of Axes. One index works. Two indices will crash.
fig, axs = plt.subplots(1, 3, figsize=(9, 3))
axs[0].plot(a, b)
axs[1].plot(c, d)
axs[2].plot(e, f)
- Index once — Use
axs[i]for 1×N and N×1 grids. - Loop cleanly —
for ax in axs: ...reads well for a row or column of plots.
Fix N×M Grids With Two Indices
When both dimensions are above 1, Matplotlib returns a 2D array of Axes. Two indices are correct there.
fig, axs = plt.subplots(2, 2, figsize=(8, 6))
axs[0, 0].plot(x1, y1)
axs[0, 1].plot(x2, y2)
axs[1, 0].plot(x3, y3)
axs[1, 1].plot(x4, y4)
Force A 2D Return With squeeze=False
If your grid size can drop to 1 in either dimension, you can remove the guesswork by turning squeezing off. Matplotlib will return a 2D array in each case, even 1×1.
fig, axs = plt.subplots(nrows, ncols, squeeze=False)
axs[0, 0].plot(x, y)
This is a solid choice for reusable plotting code because indexing stays the same even when nrows or ncols changes.
If you teach plots to teammates, squeeze=False stops the “works on my machine” moment. It makes your return shape boring and predictable. You can still loop with ravel, yet you keep the option to place titles and annotations by row and column later without rewriting the whole cell.
| Subplots Call | What You Get Back | Indexing That Works |
|---|---|---|
plt.subplots() |
single Axes | ax.plot(...) |
plt.subplots(1, 3) |
1D array of Axes | axs[0].plot(...) |
plt.subplots(2, 2) |
2D array of Axes | axs[0, 0].plot(...) |
plt.subplots(1, 1, squeeze=False) |
2D array (shape 1×1) | axs[0, 0].plot(...) |
Fixing The Axes Not Subscriptable Error When Grid Size Changes
This error loves variables. If you see this TypeError right after changing rows or columns, a squeezed return is the usual cause. You start with a 2×2 grid, write axs[r, c], then later set rows or cols to 1. Now the return type changes, and the same indexing line breaks.
Normalize To A Flat List And Loop
If placement does not matter, treat each axes the same by normalizing to a flat list. This gives you one code path for one plot, a row of plots, or a full grid.
fig, axs = plt.subplots(nrows, ncols)
axes_list = axs.ravel() if hasattr(axs, "ravel") else [axs]
for ax, series in zip(axes_list, series_list):
ax.plot(series.x, series.y)
- Wrap a single Axes — If you got one Axes, put it inside
[...]so the loop still works. - Trim extras — If you created more axes than data, slice:
axes_list[:len(series_list)].
Normalize To A 2D Array And Keep r, c Indexing
If you place plots by row and column, create your subplots with squeeze=False and keep two-index access in all spots. This avoids special cases for 1×N layouts.
fig, axs = plt.subplots(nrows, ncols, squeeze=False)
for r in range(nrows):
for c in range(ncols):
axs[r, c].grid(True)
Write A Small Helper You Can Reuse
If you tend to forget which shape you have, add a tiny helper. It makes your intent obvious and keeps notebook cells shorter.
def as_2d_axes(axs):
if hasattr(axs, "shape"):
if len(axs.shape) == 1:
return axs.reshape(1, -1)
return axs
return [[axs]]
Call it once right after plt.subplots(), then always use axs[r][c] style indexing with plain lists, or adjust it to return a NumPy array if you prefer comma indexing.
Other Places The Same Pattern Shows Up
The message can appear outside plt.subplots(). The root issue stays the same: indexing is being applied to an Axes object.
Mixing add_subplot With Container-Style Access
fig.add_subplot() returns one Axes each time. If you want indexing, you must store those Axes in your own list or array.
fig = plt.figure()
axes = []
axes.append(fig.add_subplot(1, 2, 1))
axes.append(fig.add_subplot(1, 2, 2))
axes[0].plot(x, y)
axes[1].plot(x, z)
- Store axes on creation — Keep them in a list if you plan to loop by index.
- Skip brackets on a single Axes — If you named it
ax, treat it like one object.
Reassigning axs Mid-Notebook
This one is sneaky. A cell earlier created axs as an array. A later cell assigns axs to a single Axes. Your old indexing code keeps running and fails.
- Search for reassignment — Look for later cells that run
axs =orax =on the same name. - Restart and run all — A clean run often reveals the first cell where the type changes.
Looping The Wrong Way After plt.subplot
plt.subplot() returns a single Axes each time you call it. It’s easy to assume you’re building an array, then try to index the last Axes returned. If you want an array, switch to plt.subplots(), or store each Axes you create.
Using subplot_mosaic And Expecting A Grid
Some Matplotlib helpers return a dictionary of Axes labeled by labels. In that case, bracket access works, yet the label is a string, not a row and column. If you treat that dict like a 2D grid, you’ll get different errors. A quick type() check saves time.
A Debug Checklist That Ends The Loop
If you hit the crash again, walk this list in order. It’s short, and it points to the line that needs a change.
- Find the first bracket — Look for the first
[used on an axes variable in the traceback line. - Print the type once — Add
print(type(axs))right above the failing line for one run. - Match indexing to shape — Use no index for one Axes, one index for 1D arrays, two indices for 2D arrays.
- Pick a stable approach — Use
squeeze=Falseor normalize to a list so later edits don’t re-break the plot. - Rename variables — Use
axfor one Axes andaxsfor many, and avoid reusing them for other types. - Run a clean pass — Restart the kernel and run top to bottom to confirm the type stays consistent.
If you want a quick sanity check line, this one is handy: the phrase “axes object is not subscriptable” nearly always means your variable is a single Axes right now, even if it used to be an array five cells ago.
References
- Matplotlib subplots() API — Return shapes and the
squeezeparameter: matplotlib.org - Matplotlib subplots demo — Notes on one subplot returning one Axes: matplotlib.org
- Q&A thread — Common fixes and
squeeze=Falseusage: Stack Overflow
