How to make subplots in Python
Master Python subplots. Learn various methods, get tips, see real-world applications, and debug common errors in your visualizations.

Subplots allow you to display multiple graphs within a single figure. This technique is essential for data visualization, as it helps you compare different datasets side by side with clarity.
You'll explore techniques to create subplots, from basic grids to complex custom layouts. You'll also get practical tips, see real-world applications, and find debugging advice for your own projects.
Basic subplots with matplotlib.pyplot.subplots()
import matplotlib.pyplot as plt
fig, (ax1, ax2) = plt.subplots(1, 2)
ax1.plot([1, 2, 3, 4])
ax2.scatter([1, 2, 3, 4], [1, 4, 9, 16])
plt.show()--OUTPUT--[Output: A figure with two side-by-side plots - a line plot on the left showing an ascending line and a scatter plot on the right showing points with quadratic growth]
The matplotlib.pyplot.subplots() function is the most direct way to create a grid of plots. The arguments (1, 2) tell Matplotlib to arrange the subplots in one row and two columns. This function returns both the main figure container and the individual subplot axes.
The key is unpacking these results into fig, (ax1, ax2). This gives you separate variables for each subplot's drawing area. You can then call plotting methods like plot() or scatter() directly on ax1 and ax2 to populate each graph independently, giving you precise control over your layout.
Intermediate subplot techniques
Beyond the basic grid created with subplots(), you can gain finer control over your figures by adjusting dimensions, linking axes, and designing more intricate layouts.
Creating subplots with custom dimensions
import matplotlib.pyplot as plt
fig, axs = plt.subplots(2, 2, figsize=(10, 8))
axs[0, 0].plot([1, 2, 3, 4])
axs[0, 1].scatter([1, 2, 3, 4], [1, 4, 9, 16])
axs[1, 0].bar([1, 2, 3, 4], [1, 4, 9, 16])
axs[1, 1].pie([15, 25, 35, 25])
plt.tight_layout()
plt.show()--OUTPUT--[Output: A 2×2 grid of plots with a line plot (top-left), scatter plot (top-right), bar chart (bottom-left), and pie chart (bottom-right)]
You can easily customize the figure's dimensions by passing the figsize argument to subplots(), which sets the width and height in inches. Since you're creating a 2x2 grid, subplots() returns a two-dimensional array of axes, which we've named axs.
- You access each subplot using its array index, like
axs[0, 0]for the top-left plot andaxs[1, 1]for the bottom-right. - Finally,
plt.tight_layout()is a useful function that automatically adjusts the spacing between subplots to prevent labels from overlapping.
Using shared axes with sharex and sharey
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 2*np.pi, 100)
fig, (ax1, ax2) = plt.subplots(2, 1, sharex=True)
ax1.plot(x, np.sin(x))
ax2.plot(x, np.cos(x))
plt.show()--OUTPUT--[Output: Two vertically stacked plots showing sine wave (top) and cosine wave (bottom) with the same x-axis range]
When comparing plots, it's often helpful to link their axes. The sharex and sharey parameters in subplots() allow you to do this directly. By setting sharex=True, you ensure that all subplots in a column use the same x-axis ticks and limits, which is ideal for vertically stacked graphs like the sine and cosine example.
- This automatically hides the x-axis labels on all but the bottom plot, creating a cleaner look.
- Any interaction, like zooming on one plot, will simultaneously update all other shared axes.
Creating subplots with different layouts
import matplotlib.pyplot as plt
fig, axs = plt.subplots(2, 3, constrained_layout=True)
for i, ax in enumerate(axs.flat):
ax.plot([i, i+1], [i, i+2])
ax.set_title(f'Plot {i+1}')
plt.show()--OUTPUT--[Output: A 2×3 grid of plots, each containing a simple line with incrementing values and titled "Plot 1" through "Plot 6"]
For more complex arrangements, you can create grids of any size, like the 2x3 layout in this example. The key here is how you can efficiently manage all the individual plots.
- Instead of using nested loops,
axs.flatprovides a one-dimensional iterator. This lets you cycle through every subplot in the grid with a singleforloop, which is much cleaner. - The
constrained_layout=Trueparameter is a powerful alternative totight_layout(). It automatically adjusts subplot spacing to prevent your titles and labels from overlapping.
Advanced subplot techniques
When your visualizations demand more than a simple grid, you can turn to advanced techniques for creating complex, non-uniform layouts with tools like GridSpec.
Using GridSpec for complex layouts
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
fig = plt.figure(figsize=(10, 6))
gs = gridspec.GridSpec(2, 3, height_ratios=[2, 1], width_ratios=[1, 2, 1])
ax1 = fig.add_subplot(gs[0, 0:2])
ax2 = fig.add_subplot(gs[0, 2])
ax3 = fig.add_subplot(gs[1, :])
ax1.plot([1, 2, 3])
ax2.scatter([1, 2, 3], [1, 4, 9])
ax3.bar([1, 2, 3], [1, 4, 9])
plt.tight_layout()
plt.show()--OUTPUT--[Output: A complex layout with a wide plot spanning two columns (top-left), a narrow plot (top-right), and a full-width plot along the bottom]
GridSpec offers a powerful way to build non-uniform layouts. You first define a grid structure—in this case, a 2x3 grid—and can specify the relative dimensions with parameters like height_ratios and width_ratios. This gives you a flexible canvas for arranging plots of different sizes.
- You add subplots to the figure using
fig.add_subplot()and standard Python slicing to define their position and span. - For example,
gs[0, 0:2]places a plot in the top row across the first two columns, whilegs[1, :]creates a plot that spans the entire second row.
Mixing different visualization libraries
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
sns.heatmap(np.random.rand(5, 5), ax=ax1, cmap="viridis")
data = np.random.normal(size=(100, 3))
ax2.boxplot(data)
ax2.set_xticklabels(['A', 'B', 'C'])
plt.show()--OUTPUT--[Output: Two plots side by side - a Seaborn heatmap with colorbar on the left and a Matplotlib boxplot with three boxes on the right]
Matplotlib's subplot structure is highly versatile, allowing you to integrate plots from other libraries like Seaborn. The key is passing a Matplotlib axis object directly to another library's plotting function, which acts as a canvas.
- In the example,
sns.heatmap()is instructed to draw on the first subplot by settingax=ax1. - Meanwhile, the second subplot,
ax2, is used for a standard Matplotlibboxplot(). This lets you combine the strengths of different libraries within a single, unified figure.
Creating nested subplots with insets
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots(figsize=(8, 6))
x = np.linspace(0, 10, 100)
y = np.sin(x)
ax.plot(x, y)
# Create an inset
axins = ax.inset_axes([0.6, 0.1, 0.3, 0.3])
axins.plot(x[30:50], y[30:50])
axins.set_xticklabels([])
ax.indicate_inset_zoom(axins)
plt.show()--OUTPUT--[Output: A sine wave plot with a zoomed-in inset in the bottom-right showing a magnified portion of the curve, connected to the main plot with box connectors]
You can create an inset plot—a smaller graph inside a larger one—to highlight a specific area, like a magnified view. It’s a powerful way to add detail without cluttering your main visualization.
- The
ax.inset_axes()method adds a new set of axes within your main plot. Its arguments, such as[0.6, 0.1, 0.3, 0.3], define the inset's position and size relative to the parent axes. - After creating the inset, you can plot data on it just like any other subplot.
- The helper function
ax.indicate_inset_zoom()automatically draws connector lines, visually linking the magnified area to the main graph.
Move faster with Replit
Replit is an AI-powered development platform that transforms natural language into working applications. Describe what you want to build, and Replit Agent creates it—complete with databases, APIs, and deployment.
For the subplot techniques we've explored, Replit Agent can turn them into production-ready tools:
- Build a financial dashboard that uses
GridSpecto display a large stock chart alongside smaller plots for trading volume and key indicators. - Create a data comparison utility that leverages
sharexto align multiple time-series datasets vertically for easy analysis. - Deploy an image analysis tool that shows a full image and uses
inset_axesto provide a magnified view of a specific region of interest.
Describe your app idea, and Replit Agent writes the code, tests it, and fixes issues automatically, all in your browser. Get started with Replit Agent.
Common errors and challenges
Even experienced developers run into a few common snags when creating subplots, but they're usually simple to fix.
Correcting the subplot() vs subplots() confusion
It's easy to mix up plt.subplot() (singular) and plt.subplots() (plural), but they work quite differently. The plural version, subplots(), is what you'll use most often. It creates and returns a figure and a full grid of subplot axes all at once, which you can then access from an array. This is the modern, recommended approach.
The singular version, subplot(), is an older function that adds one subplot at a time to the current figure. If you call it multiple times, you might accidentally overwrite previous plots instead of creating new ones, leading to confusing results. Stick with subplots() for cleaner, more predictable code.
Fixing inconsistent color scales in subplot heatmaps
When you place multiple heatmaps or other color-mapped plots side by side, Matplotlib often gives each one its own independent color scale. This can be misleading because the same color might represent different values in adjacent plots, making direct visual comparison impossible.
To fix this, you need to synchronize the color limits across all your subplots. You can find the global minimum and maximum values across all your datasets and then pass them to each plotting function using the vmin and vmax arguments. This ensures a single, consistent color bar applies to all plots, making your comparisons accurate.
Resolving axes labeling issues with sharex=True
Using sharex=True is great for cleaning up vertically stacked plots, as it automatically hides the x-axis tick labels on all but the bottom subplot. However, you might find that you still need a specific label on an upper plot or want to customize the text. The automatic behavior can feel a bit restrictive.
You still have full control. You can directly access the axis object for any subplot—even one with hidden labels—and use methods like set_xlabel() to add a label. If you need the tick labels to reappear on a specific upper plot, you can use ax.tick_params(labelbottom=True) to override the default setting for that axis alone.
Correcting the subplot() vs subplots() confusion
It's easy to confuse plt.subplot() with plt.subplots() because they tackle the same problem differently. The singular version adds plots one by one using a three-digit code, which can feel less intuitive. The code below demonstrates this older method in action.
import matplotlib.pyplot as plt
# Incorrect: mixing subplot() and subplots()
fig = plt.figure(figsize=(10, 4))
ax1 = plt.subplot(121)
ax1.plot([1, 2, 3, 4])
ax2 = plt.subplot(122)
ax2.scatter([1, 2, 3, 4], [1, 4, 9, 16])
plt.show()
The challenge here isn't a bug, but a lack of clarity. Using three-digit codes like 121 with plt.subplot() is less intuitive and harder to debug than the modern approach. See how the recommended method improves readability below.
import matplotlib.pyplot as plt
# Correct: consistently using subplots()
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 4))
ax1.plot([1, 2, 3, 4])
ax2.scatter([1, 2, 3, 4], [1, 4, 9, 16])
plt.show()
The solution uses plt.subplots() to create the figure and axes together. It’s much clearer than the older plt.subplot() method because you get explicit variables like ax1 and ax2 to work with. This avoids the confusing three-digit codes and prevents you from accidentally overwriting a plot. Always prefer plt.subplots() (plural) for creating grids, as it makes your code more predictable and easier to read, especially in complex layouts.
Fixing inconsistent color scales in subplot heatmaps
You'll often face inconsistent scaling with subplot heatmaps. When you generate multiple plots, each gets its own color range, making it impossible to compare them accurately. The code below, which uses imshow(), clearly illustrates this common problem in action.
import matplotlib.pyplot as plt
import numpy as np
fig, (ax1, ax2) = plt.subplots(1, 2)
data1 = np.random.rand(10, 10)
data2 = np.random.rand(10, 10) * 3 # Different scale
im1 = ax1.imshow(data1) # Different color scale
im2 = ax2.imshow(data2) # Different color scale
plt.show()
Because data2 contains values up to three times larger than data1, each imshow() plot gets its own color scale. This makes a direct comparison misleading. The corrected code below shows how to align them for an accurate visualization.
import matplotlib.pyplot as plt
import numpy as np
fig, (ax1, ax2) = plt.subplots(1, 2)
data1 = np.random.rand(10, 10)
data2 = np.random.rand(10, 10) * 3
# Set the same color limits for both plots
vmin, vmax = 0, 3
im1 = ax1.imshow(data1, vmin=vmin, vmax=vmax)
im2 = ax2.imshow(data2, vmin=vmin, vmax=vmax)
fig.colorbar(im2, ax=[ax1, ax2])
plt.show()
The solution is to manually synchronize the color scale. By defining a global minimum and maximum with vmin and vmax, you force both imshow() calls to use the same range. This ensures a specific color represents the same value in each subplot, making your comparison accurate.
Finally, fig.colorbar() creates a single, shared color bar for the entire figure. It's crucial to do this whenever you're comparing datasets with different value ranges in heatmaps or similar plots.
Resolving axes labeling issues with sharex=True
While sharex=True automatically hides redundant x-axis labels for a cleaner look, you might accidentally create clutter by setting them manually. This defeats the purpose of sharing axes and makes your visualization look messy. The following code demonstrates this exact issue.
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 10, 100)
fig, (ax1, ax2) = plt.subplots(2, 1, sharex=True)
ax1.plot(x, np.sin(x))
ax2.plot(x, np.cos(x))
# Redundant labels cause cluttered appearance
ax1.set_xlabel('x values')
ax2.set_xlabel('x values')
plt.show()
The code creates a conflict by manually setting labels on both axes with set_xlabel(), which counteracts the automatic label hiding from sharex=True. The result is a messy, redundant chart. The corrected version below cleans this up.
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 10, 100)
fig, (ax1, ax2) = plt.subplots(2, 1, sharex=True)
ax1.plot(x, np.sin(x))
ax2.plot(x, np.cos(x))
# Only label the bottom x-axis when using sharex=True
ax2.set_xlabel('x values')
plt.show()
The solution is to work with the default behavior of sharex=True, not against it. Since it automatically hides upper x-axis labels to prevent redundancy, you should only apply a label to the bottom-most plot using ax2.set_xlabel(). This approach avoids visual clutter and keeps your stacked graphs clean and readable. This issue often arises when you manually override settings that Matplotlib is already managing for you, so let the library do the work.
Real-world applications
Putting it all together, functions like subplots() let you build powerful visualizations for comparing metrics and analyzing real-world trends.
Comparing metrics across categories with subplots()
A grid of subplots is perfect for breaking down a dataset into smaller, comparable parts, like creating a separate bar chart to track sales performance in different regions.
import matplotlib.pyplot as plt
import numpy as np
# Sales data for different regions
months = ['Jan', 'Feb', 'Mar', 'Apr']
regions = ['North', 'South', 'East', 'West']
sales_data = np.random.randint(50, 200, size=(4, 4))
fig, axs = plt.subplots(2, 2, figsize=(10, 8))
for i, region in enumerate(regions):
row, col = i // 2, i % 2
axs[row, col].bar(months, sales_data[i])
axs[row, col].set_title(f'{region} Region Sales')
plt.tight_layout()
plt.show()
This example shows how to populate a subplot grid dynamically. A for loop with enumerate iterates through the data for each region, using the loop index to place each bar chart onto the 2x2 grid.
- The key is the math used to find each plot’s position. Integer division (
i // 2) calculates the row, while the modulo operator (i % 2) determines the column. - This technique efficiently maps a one-dimensional list, like
regions, onto a two-dimensional grid of axes, making your code scalable and clean.
Visualizing time series data with custom formatting
Subplots are particularly effective for visualizing time series data, where you can compare trends like stock prices and use custom formatting to make the date axis easy to read.
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import pandas as pd
from datetime import datetime, timedelta
# Create sample stock price data
dates = [datetime(2023, 1, 1) + timedelta(days=i) for i in range(90)]
stock_a = [100 + i + i*np.sin(i/10) for i in range(90)]
stock_b = [120 - i*0.5 + i*np.cos(i/10) for i in range(90)]
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 8), sharex=True)
ax1.plot(dates, stock_a, 'b-', label='Stock A')
ax1.set_ylabel('Price ($)')
ax1.legend()
ax1.grid(True)
ax2.plot(dates, stock_b, 'r-', label='Stock B')
ax2.set_ylabel('Price ($)')
ax2.legend()
ax2.grid(True)
# Format x-axis to show dates nicely
ax2.xaxis.set_major_formatter(mdates.DateFormatter('%b %d'))
ax2.xaxis.set_major_locator(mdates.WeekdayLocator(interval=2))
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
This example shows how to compare two time-series datasets using vertically stacked plots. The key is the subplots(2, 1, sharex=True) function, which creates a figure with two rows and one column. This setup ensures both plots align perfectly on the same timeline, which is ideal for comparing trends.
- Each stock's data is plotted on its own axis—
ax1for Stock A andax2for Stock B—allowing for direct visual comparison. - Using
sharex=Trueis crucial, as it links the x-axes and removes redundant labels, creating a clean visualization for tracking trends side-by-side.
Get started with Replit
Turn your knowledge into a real tool. Give Replit Agent a prompt like: “Build a weather dashboard with a main plot and two subplots below” or “Compare two datasets with stacked charts sharing an x-axis.”
Replit Agent writes the code, tests for errors, and deploys your app. Start building with Replit.
Create and deploy websites, automations, internal tools, data pipelines and more in any programming language without setup, downloads or extra tools. All in a single cloud workspace with AI built in.
Create & deploy websites, automations, internal tools, data pipelines and more in any programming language without setup, downloads or extra tools. All in a single cloud workspace with AI built in.


.png)
.png)