Introduction
In matplotlib, each plot lives in a Figure object. To create multiple plots and switch between them, you create separate figure references with plt.figure() and use plt.figure(fig_number) to switch back to an earlier figure. The object-oriented API (fig, ax = plt.subplots()) is the cleaner approach — you work directly with figure and axes objects instead of relying on matplotlib's implicit "current figure" state.
The Problem
1import matplotlib.pyplot as plt
2
3plt.plot([1, 2, 3], [1, 4, 9]) # Plot on figure 1
4plt.plot([1, 2, 3], [1, 2, 3]) # This adds to figure 1, not a new figure
5
6plt.show()
7# Both lines appear on the same plot — how do you put them on separate plots?
1import matplotlib.pyplot as plt
2
3# Create figure 1
4plt.figure(1)
5plt.plot([1, 2, 3], [1, 4, 9], 'r-')
6plt.title("Quadratic")
7
8# Create figure 2
9plt.figure(2)
10plt.plot([1, 2, 3], [1, 2, 3], 'b-')
11plt.title("Linear")
12
13# Go back to figure 1 and add more data
14plt.figure(1)
15plt.plot([1, 2, 3], [1, 8, 27], 'g--')
16plt.legend(["x²", "x³"])
17
18plt.show()
19# Two separate windows: figure 1 has two lines, figure 2 has one
plt.figure(n) creates a new figure if number n does not exist, or switches to it if it does.
Method 2: Object-Oriented API (Recommended)
1import matplotlib.pyplot as plt
2
3# Create two separate figures with their axes
4fig1, ax1 = plt.subplots()
5fig2, ax2 = plt.subplots()
6
7# Plot on figure 1
8ax1.plot([1, 2, 3], [1, 4, 9], 'r-')
9ax1.set_title("Quadratic")
10
11# Plot on figure 2
12ax2.plot([1, 2, 3], [1, 2, 3], 'b-')
13ax2.set_title("Linear")
14
15# Go back to figure 1 and add more data — just use ax1
16ax1.plot([1, 2, 3], [1, 8, 27], 'g--')
17ax1.legend(["x²", "x³"])
18
19plt.show()
With the OO API, you hold references to axes objects and plot on them directly. No need to track figure numbers or worry about which figure is "current."
If you want multiple plots in the same window:
1import matplotlib.pyplot as plt
2import numpy as np
3
4x = np.linspace(0, 2 * np.pi, 100)
5
6fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
7
8ax1.plot(x, np.sin(x), 'r-')
9ax1.set_title("Sine")
10
11ax2.plot(x, np.cos(x), 'b-')
12ax2.set_title("Cosine")
13
14# Later, add to the first subplot
15ax1.plot(x, np.sin(2 * x), 'r--', alpha=0.5)
16ax1.legend(["sin(x)", "sin(2x)"])
17
18plt.tight_layout()
19plt.show()
Method 4: Grid of Subplots
1import matplotlib.pyplot as plt
2import numpy as np
3
4x = np.linspace(0, 10, 100)
5
6fig, axes = plt.subplots(2, 2, figsize=(10, 8))
7
8axes[0, 0].plot(x, np.sin(x))
9axes[0, 0].set_title("sin(x)")
10
11axes[0, 1].plot(x, np.cos(x))
12axes[0, 1].set_title("cos(x)")
13
14axes[1, 0].plot(x, np.exp(-x))
15axes[1, 0].set_title("exp(-x)")
16
17axes[1, 1].plot(x, np.log(x + 1))
18axes[1, 1].set_title("log(x+1)")
19
20# Add to the first subplot later
21axes[0, 0].axhline(y=0, color='gray', linestyle='--')
22
23plt.tight_layout()
24plt.show()
1fig1, ax1 = plt.subplots()
2ax1.plot([1, 2, 3], [1, 4, 9])
3ax1.set_title("Plot A")
4
5fig2, ax2 = plt.subplots()
6ax2.plot([1, 2, 3], [3, 2, 1])
7ax2.set_title("Plot B")
8
9# Save each figure to a different file
10fig1.savefig("plot_a.png", dpi=150, bbox_inches='tight')
11fig2.savefig("plot_b.png", dpi=150, bbox_inches='tight')
12
13# Close figures to free memory
14plt.close(fig1)
15plt.close(fig2)
Using plt.gcf() and plt.gca()
1# gcf() = Get Current Figure
2# gca() = Get Current Axes
3
4plt.figure(1)
5plt.plot([1, 2], [1, 2])
6
7plt.figure(2)
8plt.plot([1, 2], [2, 1])
9
10# Switch back to figure 1
11plt.figure(1)
12current_fig = plt.gcf() # Returns figure 1
13current_ax = plt.gca() # Returns axes of figure 1
14current_ax.set_xlabel("X axis")
gcf() and gca() are useful in interactive sessions but should be avoided in scripts — use explicit figure/axes references instead.
Real-World Example: Dashboard
1import matplotlib.pyplot as plt
2import numpy as np
3
4# Create a dashboard with different plot types
5fig, axes = plt.subplots(2, 2, figsize=(12, 10))
6
7months = ['Jan', 'Feb', 'Mar', 'Apr', 'May']
8revenue = [10, 15, 13, 18, 20]
9costs = [8, 10, 11, 12, 14]
10
11# Bar chart
12axes[0, 0].bar(months, revenue, color='steelblue')
13axes[0, 0].set_title("Monthly Revenue")
14
15# Line chart
16axes[0, 1].plot(months, revenue, 'g-o', label="Revenue")
17axes[0, 1].plot(months, costs, 'r-o', label="Costs")
18axes[0, 1].legend()
19axes[0, 1].set_title("Revenue vs Costs")
20
21# Pie chart
22axes[1, 0].pie([30, 25, 20, 15, 10],
23 labels=['A', 'B', 'C', 'D', 'E'],
24 autopct='%1.0f%%')
25axes[1, 0].set_title("Market Share")
26
27# Scatter plot
28x = np.random.randn(50)
29y = x + np.random.randn(50) * 0.5
30axes[1, 1].scatter(x, y, alpha=0.6)
31axes[1, 1].set_title("Correlation")
32
33plt.suptitle("Q1 Dashboard", fontsize=14)
34plt.tight_layout()
35plt.show()
Common Pitfalls
Forgetting plt.figure(n) before plotting: Without switching figures, all plots go on the current (last created) figure. Use the OO API to avoid this entirely.
plt.show() clears state: After plt.show(), the current figure is reset. Save references before calling plt.show(), or save figures to files beforehand.
Memory leaks: Creating many figures without closing them leaks memory. Use plt.close(fig) or plt.close('all') when done.
Jupyter vs scripts: In Jupyter, each cell typically renders automatically with %matplotlib inline. In scripts, you must call plt.show() explicitly. Multiple plt.show() calls block execution until each window is closed.
plt.subplot() vs plt.subplots(): plt.subplot(2,2,1) is the old state-based API. fig, axes = plt.subplots(2,2) is the modern OO approach — prefer it.
Summary
Use fig, ax = plt.subplots() to create figures and hold references (OO API)
Plot on any axes at any time with ax.plot() — no need to switch figures
Use plt.figure(n) to switch between figures in the state-based API
Use fig, axes = plt.subplots(rows, cols) for multiple plots in one window
Save figures with fig.savefig() and close with plt.close(fig) to free memory
Prefer the object-oriented API over the state-based plt.plot() approach