How to plot multiple graphs in Python

Discover various ways to plot multiple graphs in Python. Get tips, see real-world examples, and learn how to debug common errors.

How to plot multiple graphs in Python
Published on: 
Tue
Mar 10, 2026
Updated on: 
Fri
Mar 13, 2026
The Replit Team

To plot multiple graphs in Python is a key skill for data visualization. It lets you compare datasets side-by-side, which helps you tell a clearer, more impactful data story.

In this article, you’ll explore techniques to create multi-plot figures. You will also find practical tips, real-world applications, and essential advice to debug your code for polished results.

Basic multiple plots with plt.subplot()

import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0, 10, 100)
plt.subplot(1, 2, 1)  # 1 row, 2 columns, first plot
plt.plot(x, np.sin(x))
plt.title('Sine')
plt.subplot(1, 2, 2)  # 1 row, 2 columns, second plot
plt.plot(x, np.cos(x))
plt.title('Cosine')
plt.tight_layout()
plt.show()--OUTPUT--[A figure with two plots side by side - left plot shows a sine wave with title 'Sine', right plot shows a cosine wave with title 'Cosine']

The plt.subplot() function is your entry point for creating a grid of plots. It tells Matplotlib how to arrange your visualizations and which one you're currently working on. The function's three arguments define the entire layout:

  • Number of rows
  • Number of columns
  • Plot index (starting from 1)

Calling plt.subplot(1, 2, 1) creates a 1x2 grid and targets the first plot. Subsequent plotting commands like plt.plot() apply to this specific subplot until you select another. The call to plt.tight_layout() is a handy final touch that cleans up the spacing between your plots.

Customizing multiple plots

While plt.subplot() is a solid starting point, you'll find that more advanced techniques give you the flexibility to create complex, customized multi-plot figures.

Using plt.subplots() for better control

import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0, 10, 100)
fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(10, 4))
axes[0].plot(x, np.sin(x), 'b-')
axes[0].set_title('Sine')
axes[1].plot(x, np.cos(x), 'r-')
axes[1].set_title('Cosine')
fig.tight_layout()
plt.show()--OUTPUT--[A figure with two plots side by side - left plot shows a blue sine wave, right plot shows a red cosine wave, with a larger figure size (10x4 inches)]

The plt.subplots() function—notice the 's'—offers a more object-oriented approach. It returns two valuable objects: a fig for the entire figure and an axes array for the individual subplots. This structure gives you explicit control over each element.

  • You can modify each subplot by indexing the axes array, like axes[0].
  • Functions like plot() and set_title() are called directly on the axis object.
  • The figsize argument lets you easily define the figure's dimensions.

Creating a grid of plots with different sizes

import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0, 10, 100)
plt.figure(figsize=(10, 6))
plt.subplot2grid((2, 3), (0, 0), colspan=2)
plt.plot(x, np.sin(x))
plt.title('Sine - Wide Plot')
plt.subplot2grid((2, 3), (0, 2))
plt.plot(x, np.cos(x))
plt.title('Cosine')
plt.subplot2grid((2, 3), (1, 0), colspan=3)
plt.plot(x, np.tan(x), 'g-')
plt.title('Tangent - Full Width')
plt.tight_layout()
plt.show()--OUTPUT--[A figure with three plots arranged in a grid - top row has a wide sine plot spanning 2 columns and a cosine plot in the third column, bottom row has a full-width green tangent plot spanning all 3 columns]

When you need a non-uniform grid, plt.subplot2grid() is the tool for the job. It lets you place plots of varying sizes within a single figure. You specify three main things:

  • The overall grid dimensions, like (2, 3) for a 2x3 grid.
  • The starting location of your plot, such as (0, 0) for the top-left cell.
  • How many columns (colspan) or rows (rowspan) the plot should occupy. This is how you create larger, spanning plots.

Sharing axes between multiple plots

import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0, 10, 100)
fig, (ax1, ax2) = plt.subplots(2, 1, sharex=True, figsize=(8, 6))
ax1.plot(x, np.sin(x))
ax1.set_title('Sine')
ax2.plot(x, np.cos(x))
ax2.set_title('Cosine')
ax2.set_xlabel('x values')
plt.tight_layout()
plt.show()--OUTPUT--[A figure with two plots stacked vertically - top plot shows a sine wave, bottom plot shows a cosine wave, both sharing the same x-axis with 'x values' label at the bottom]

When you're comparing datasets on the same scale, sharing an axis is a great way to clean up your figure. In plt.subplots(), you can set the sharex or sharey arguments to True. This links the axes across all subplots in a row or column, which has a few key benefits.

  • It declutters the visualization by automatically hiding redundant axis labels and ticks.
  • You only need to set the axis label once on the outermost plot.
  • Any zooming or panning on one plot will sync across all shared axes.

Advanced plotting techniques

Building on these techniques, you can create more polished figures by combining matplotlib with seaborn, adding interactivity with plotly, or mastering complex layouts with GridSpec.

Combining matplotlib with seaborn

import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
import pandas as pd

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4))
# Matplotlib plot
x = np.linspace(0, 10, 100)
ax1.plot(x, np.sin(x))
ax1.set_title('Matplotlib: Sine Wave')
# Seaborn plot
data = pd.DataFrame({'x': range(10), 'y': np.random.rand(10)})
sns.barplot(x='x', y='y', data=data, ax=ax2)
ax2.set_title('Seaborn: Random Bar Plot')
plt.tight_layout()
plt.show()--OUTPUT--[A figure with two plots side by side - left plot shows a sine wave created with matplotlib, right plot shows a bar chart of random data created with seaborn]

Seaborn works seamlessly with Matplotlib, letting you combine its statistical plots with Matplotlib’s flexibility. You start by creating your figure layout using plt.subplots(), which gives you the familiar fig and axes objects.

  • You can create a standard Matplotlib plot on one axis, like ax1.
  • For the Seaborn plot, you simply pass the target axis (e.g., ax2) to the ax parameter within the Seaborn function.

This approach gives you precise control, allowing you to place a high-level Seaborn visualization right next to a custom Matplotlib plot within the same figure.

Creating interactive multiple plots with plotly

import plotly.graph_objects as go
from plotly.subplots import make_subplots
import numpy as np

x = np.linspace(0, 10, 100)
fig = make_subplots(rows=1, cols=2, subplot_titles=('Sine', 'Cosine'))
fig.add_trace(go.Scatter(x=x, y=np.sin(x), mode='lines'), row=1, col=1)
fig.add_trace(go.Scatter(x=x, y=np.cos(x), mode='lines'), row=1, col=2)
fig.update_layout(height=500, width=900, title_text="Interactive Multiple Plots")
fig.show()--OUTPUT--[An interactive figure with two plots side by side - left plot shows a sine wave, right plot shows a cosine wave. The plots allow user interaction like zooming, panning, and hovering for data points]

For interactive visualizations, Plotly offers a distinct, object-oriented workflow. You begin by calling make_subplots() to define the grid structure and titles. This function prepares a fig object that you'll use to build and customize your entire visualization.

  • Each plot, known as a "trace" in Plotly, is added with the fig.add_trace() method.
  • You target a specific grid location for each trace using the row and col arguments.
  • Global adjustments like figure size and a main title are handled with fig.update_layout().

This approach produces a fully interactive figure, allowing users to zoom, pan, and inspect data points directly in the output.

Using GridSpec for complex layouts

import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import numpy as np

x = np.linspace(0, 10, 100)
fig = plt.figure(figsize=(10, 8))
gs = gridspec.GridSpec(3, 3)

ax1 = fig.add_subplot(gs[0, :])
ax1.plot(x, np.sin(x))
ax1.set_title('Sine - Top Row')

ax2 = fig.add_subplot(gs[1:, 0])
ax2.plot(x, np.cos(x))
ax2.set_title('Cosine - Left Column')

ax3 = fig.add_subplot(gs[1:, 1:])
ax3.plot(x, np.tan(x))
ax3.set_title('Tangent - Bottom Right')

plt.tight_layout()
plt.show()--OUTPUT--[A figure with a complex layout - top row shows a full-width sine plot, bottom section has a cosine plot in the left column and a larger tangent plot occupying the bottom right area (2x2 grid space)]

For the most intricate layouts, GridSpec offers unmatched flexibility. You'll start by creating a grid object, for instance gridspec.GridSpec(3, 3). The real power comes from using Python's slicing syntax to place your subplots precisely where you want them.

  • A slice like gs[0, :] instructs Matplotlib to position a plot in the top row, spanning all available columns.
  • You can also select a block of cells, like gs[1:, 1:], to create a larger subplot that covers multiple rows and columns.

This approach gives you fine-grained control to build highly customized, non-uniform figures.

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 multi-plot techniques we've explored, Replit Agent can turn them into production-ready applications:

  • Build a financial dashboard that uses plt.subplots() to compare a stock's price against its trading volume over time.
  • Create a weather analysis tool that leverages GridSpec to display a large map of temperature contours alongside smaller plots for humidity and wind speed.
  • Deploy an interactive fitness tracker that uses plotly to visualize multiple health metrics—like heart rate and sleep patterns—on a shared timeline.

Describe your app idea, and Replit Agent will write the code, test it, and fix issues automatically, all within your browser.

Common errors and challenges

Plotting multiple graphs can introduce some tricky errors, but most have straightforward solutions.

A frequent stumble with plt.subplot() is the indexing. Unlike Python's usual 0-based lists, the plot index for this function starts at 1. If you have a 2x2 grid and want the top-right plot, you'd use plt.subplot(2, 2, 2). Trying to use an index of 0 will cause an error—a common mix-up if you're switching from the axes array returned by plt.subplots(), which does use 0-based indexing.

You'll often find that titles and axis labels can overlap, making your figure look messy. The quickest fix is calling plt.tight_layout() right before plt.show(), which automatically adjusts subplot parameters. For more fine-grained control, plt.subplots_adjust() lets you manually set specific spacing parameters, such as:

  • wspace: the width between columns
  • hspace: the height between rows

Legends in multi-plot figures can be a headache, sometimes covering up your data. When you call ax.legend() on a specific subplot, you can control its placement with the loc parameter. While loc='best' tries to find an empty spot, you can force its position with values like 'upper left' or 'lower right'.

For a cleaner look, especially when plots share labels, you can create a single legend for the entire figure. You can achieve this by gathering the legend elements from each axis and then using fig.legend() to place it in a central, uncluttered location, such as just below all the subplots.

Fixing incorrect subplot indexing errors in plt.subplot()

It's easy to lose track of your plot index, especially in a larger grid. When using plt.subplot(), if you provide an index greater than the total number of subplots—like asking for the fifth plot in a 2x2 grid—Python will stop.

Take a look at the following code, which triggers this exact IndexError.

import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0, 10, 100)
plt.subplot(2, 2, 1)
plt.plot(x, np.sin(x))
plt.title('Sine')
plt.subplot(2, 2, 2)
plt.plot(x, np.cos(x))
plt.title('Cosine')
plt.subplot(2, 2, 3)
plt.plot(x, np.sin(2*x))
plt.title('Double Sine')
plt.subplot(2, 2, 5)  # Error: index 5 is out of range for a 2x2 grid
plt.plot(x, np.cos(2*x))
plt.title('Double Cosine')
plt.tight_layout()

The call to plt.subplot(2, 2, 5) triggers the error. A 2x2 grid only contains four plots, so index 5 is invalid. The corrected code below shows how to resolve this issue.

import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0, 10, 100)
plt.subplot(2, 2, 1)
plt.plot(x, np.sin(x))
plt.title('Sine')
plt.subplot(2, 2, 2)
plt.plot(x, np.cos(x))
plt.title('Cosine')
plt.subplot(2, 2, 3)
plt.plot(x, np.sin(2*x))
plt.title('Double Sine')
plt.subplot(2, 2, 4)  # Fixed: using correct index 4
plt.plot(x, np.cos(2*x))
plt.title('Double Cosine')
plt.tight_layout()

The fix is simple: the last subplot call is changed from plt.subplot(2, 2, 5) to plt.subplot(2, 2, 4). A 2x2 grid has four available slots, indexed 1 through 4, so requesting the fifth plot caused the error. It's an easy mistake to make in complex layouts. Always double-check that your index doesn't exceed the total number of plots, which is just the number of rows multiplied by the columns.

Resolving overlapping titles and labels

It’s a common problem: you’ve arranged your plots perfectly, but the titles and axis labels end up overlapping, creating a cluttered mess. This often happens with long titles or when the figure space is tight. The code below shows this issue in action.

import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0, 10, 100)
fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(8, 6))
axes[0, 0].plot(x, np.sin(x))
axes[0, 0].set_title('Sine Wave with Very Long Title That Might Overlap')
axes[0, 1].plot(x, np.cos(x))
axes[0, 1].set_title('Cosine Wave with Very Long Title That Might Overlap')
axes[1, 0].plot(x, np.sin(2*x))
axes[1, 0].set_title('Double Sine Wave with Very Long Title That Might Overlap')
axes[1, 1].plot(x, np.cos(2*x))
axes[1, 1].set_title('Double Cosine Wave with Very Long Title That Might Overlap')
# Missing tight_layout() causes overlapping titles

The long titles are too large for the default spacing between plots. Without an explicit command to adjust the layout, the text elements overlap and make the figure messy. The corrected code below demonstrates the fix.

import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0, 10, 100)
fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(10, 8))  # Larger figure
axes[0, 0].plot(x, np.sin(x))
axes[0, 0].set_title('Sine Wave')
axes[0, 1].plot(x, np.cos(x))
axes[0, 1].set_title('Cosine Wave')
axes[1, 0].plot(x, np.sin(2*x))
axes[1, 0].set_title('Double Sine Wave')
axes[1, 1].plot(x, np.cos(2*x))
axes[1, 1].set_title('Double Cosine Wave')
plt.tight_layout()  # Added to prevent overlapping

The fix is simple: adding plt.tight_layout() before you display the plot automatically adjusts spacing to prevent titles and labels from overlapping. The corrected code also gives the elements more room by increasing the figure size with figsize and shortening the titles. You'll want to watch for this issue whenever you're working with dense grids or long text elements, as it's a common pitfall that can make your visualizations look unprofessional.

Handling legend positioning in multi-plots

When you have multiple subplots, a simple call to plt.legend() doesn't always behave as expected. It often adds a legend to only the last active subplot, leaving the others unlabeled and making your figure incomplete. The following code demonstrates this common pitfall.

import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0, 10, 100)
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 4))
ax1.plot(x, np.sin(x), 'b-', label='sin(x)')
ax1.plot(x, np.sin(2*x), 'r-', label='sin(2x)')
ax2.plot(x, np.cos(x), 'g-', label='cos(x)')
ax2.plot(x, np.cos(2*x), 'k-', label='cos(2x)')
plt.legend()  # Error: only creates legend for the last plot
plt.tight_layout()

The call to plt.legend() is made without a target, so it only attaches to the last active axis, ax2. This leaves the first plot unlabeled. The corrected code below shows how to properly create legends for each subplot.

import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0, 10, 100)
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 4))
ax1.plot(x, np.sin(x), 'b-', label='sin(x)')
ax1.plot(x, np.sin(2*x), 'r-', label='sin(2x)')
ax1.legend()  # Correct: add legend to first subplot
ax2.plot(x, np.cos(x), 'g-', label='cos(x)')
ax2.plot(x, np.cos(2*x), 'k-', label='cos(2x)')
ax2.legend()  # Correct: add legend to second subplot
plt.tight_layout()

The fix is to call the legend() method on each subplot's axis object. Instead of one generic plt.legend() call, you target each plot individually with ax1.legend() and ax2.legend(). This explicitly tells Matplotlib where to place each legend. It's a common pitfall when using the object-oriented plt.subplots() approach, so remember to give each subplot its own legend() command if it needs one.

Real-world applications

These multi-plot techniques are the foundation for building practical tools that analyze complex, real-world data.

Comparing multiple stock prices with subplot()

Financial analysis often involves comparing the performance of different assets. The subplot() function is perfect for creating a straightforward grid to track multiple stock prices side-by-side. Each subplot can represent a different stock, plotting its price over the same time frame.

This direct comparison makes it easy to spot key insights visually. You can quickly identify which stocks are more volatile, which ones move in tandem, or how different assets react to the same market event. It’s a simple yet powerful way to get a snapshot of your portfolio's behavior.

Creating a data dashboard with GridSpec for weather analysis

When you need to present different types of related data, a simple grid won't always cut it. For a weather analysis dashboard, GridSpec gives you the flexibility to create a more intuitive layout. You can design a figure where the most important information gets the most visual real estate.

Imagine a large, central plot showing a map of temperature contours. Surrounding it, you could place smaller plots for related metrics like wind speed, humidity, and precipitation. This hierarchical approach guides the viewer's eye, creating a cohesive and information-dense dashboard that tells a much clearer story than a uniform grid ever could.

Comparing multiple stock prices with subplot()

The subplot() function lets you build a simple dashboard, placing a chart of individual stocks next to a plot of the broader market index for context.

import matplotlib.pyplot as plt
import numpy as np

x = np.arange(100)
plt.figure(figsize=(10, 4))
plt.subplot(1, 2, 1)
plt.plot(x, 100+np.cumsum(np.random.randn(100)), 'b-', label='Company A')
plt.plot(x, 120+np.cumsum(np.random.randn(100)), 'r-', label='Company B')
plt.subplot(1, 2, 2)
plt.plot(x, np.random.normal(0, 1, 100).cumsum(), 'g-', label='Market Index')
plt.legend()

This code uses plt.subplot() to create a figure with two side-by-side plots, perfect for comparing financial data. It’s a great example of how to structure a simple visual analysis.

  • The first subplot displays two lines representing simulated stock prices. The data is generated using np.cumsum() on random numbers to create a "random walk" effect.
  • The second subplot shows a single line for a market index.
  • Notice how plt.legend() only applies to the last active plot. You'd need to call it for each subplot to label them all.

Creating a data dashboard with GridSpec for weather analysis

Using GridSpec, you can create a simple dashboard to visualize different weather metrics, placing a temperature trend line next to a scatter plot that explores its relationship with rainfall.

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.gridspec import GridSpec

days = np.arange(30)
temp = 20 + 5*np.sin(days/3) + np.random.randn(30)*2
rain = np.random.exponential(2, 30)

fig = plt.figure(figsize=(10, 5))
gs = GridSpec(1, 2, figure=fig)
fig.add_subplot(gs[0, 0]).plot(days, temp, 'r-o')
fig.add_subplot(gs[0, 1]).scatter(temp, rain, c=rain, cmap='Blues')

This code creates a side-by-side visualization using GridSpec to manage the layout. After generating simulated weather data, it defines a 1-row, 2-column grid.

  • The first subplot, placed at gs[0, 0], uses plot() to create a line chart of temperature over time.
  • The second subplot, at gs[0, 1], is a scatter() plot mapping temperature to rainfall. The c=rain argument cleverly colors each point by its rainfall value, using the Blues colormap to show intensity.

Get started with Replit

Turn these techniques into a real application. Describe your idea to Replit Agent, like: "a financial dashboard comparing two stocks" or "a weather tool with a main map and side plots".

The agent writes the code, tests for errors, and deploys your app. Start building with Replit.

Get started free

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.

Get started for free

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.