48

I want to create a plot consisting of several subplots with shared x/y axes. It should look something like this from the documentation (though my subplots will be scatterblots): (code here)

3 subplots sharing x and y axis

But I want to create the subplots dynamically!

So the number of subplots depends on the output of a previous function. (It will probably be around 3 to 15 subplots per diagram, each from a distinct dataset, depending on the input of my script.)

Can anyone tell me how to accomplish that?

Richard
  • 56,349
  • 34
  • 180
  • 251
CodingCat
  • 4,999
  • 10
  • 37
  • 59
  • 2
    Can't you use `plt.subplots(numplots, sharex=True, sharey=True)` with `numplots` a variable? – Tim Sep 07 '12 at 14:26
  • @Tim - You should post that as an answer. :) (A lot of people aren't aware of `subplots`. It's relatively new.) – Joe Kington Sep 07 '12 at 14:52
  • 3
    Well, it's in the source source code linked above, so I was guessing there was another problem. – Tim Sep 07 '12 at 15:21

4 Answers4

42

Suppose you know total subplots and total columns you want to use:

import matplotlib.pyplot as plt

# Subplots are organized in a Rows x Cols Grid
# Tot and Cols are known

Tot = number_of_subplots
Cols = number_of_columns

# Compute Rows required

Rows = Tot // Cols 

#     EDIT for correct number of rows:
#     If one additional row is necessary -> add one:

if Tot % Cols != 0:
    Rows += 1

# Create a Position index

Position = range(1,Tot + 1)

First instance of Rows accounts only for rows completely filled by subplots, then is added one more Row if 1 or 2 or ... Cols - 1 subplots still need location.

Then create figure and add subplots with a for loop.

# Create main figure

fig = plt.figure(1)
for k in range(Tot):

  # add every single subplot to the figure with a for loop

  ax = fig.add_subplot(Rows,Cols,Position[k])
  ax.plot(x,y)      # Or whatever you want in the subplot

plt.show()

Please note that you need the range Position to move the subplots into the right place.

Matteo Scarponi
  • 521
  • 4
  • 5
27
import matplotlib.pyplot as plt
from pylab import *
import numpy as np

x = np.linspace(0, 2*np.pi, 400)
y = np.sin(x**2)

subplots_adjust(hspace=0.000)
number_of_subplots=3

for i,v in enumerate(xrange(number_of_subplots)):
    v = v+1
    ax1 = subplot(number_of_subplots,1,v)
    ax1.plot(x,y)

plt.show()

This code works but you will need to correct the axes. I used to subplot to plot 3 graphs all in the same column. All you need to do is assign an integer to number_of_plots variable. If the X and Y values are different for each plot you will need to assign them for each plot.

subplot works as follows, if for example I had a subplot values of 3,1,1. This creates a 3x1 grid and places the plot in the 1st position. In the next interation if my subplot values were 3,1,2 it again creates a 3x1 grid but places the plot in the 2nd position and so forth.

Harpal
  • 12,057
  • 18
  • 61
  • 74
  • Thanks for explaining! I didn't understand the way the subplot values worked from the documentation, but it's clear now. Thank you! One question, though: what's the benefit of using `for i,v in enumerate(xrange(number_of_subplots))` instead of `for i in range(number_of_subplots)`? Even if you needed the same value twice (which your code doesn't seem to), couldn't you just use i twice? Or am I missing something? (I'm still a beginner, hence curious.) – CodingCat Sep 10 '12 at 07:47
  • @Lastalda You can indeed just use `(xrange(number_if_plots))` for your case. In fact the way you suggested is a lot simpler. I did `for i,v in enumerate(xrange(number_of_subplots))` for future cases, say if i needed to go through a list at the same time, all I would have to do is take out the `xrange()`. Then `i` would become the values in the list and `v` would remain as an integer. So I could position the plot using the `v` variable and assign the x and y values using `i`. Hope this makes sense – Harpal Sep 10 '12 at 09:56
  • 8
    meh why not just tidy the code rather than defending it. `for i in range(3): ...` – P i Nov 08 '15 at 11:57
  • 1
    I got error when trying this code: NameError: name 'xrange' is not defined. Sorry, it should be some stupid error, but I am an absolute beginner in Python. Any ideea? – Marko Buršič Feb 28 '17 at 16:15
  • 1
    You must be using `Python 3` change `xrange` to `range` – Harpal Feb 28 '17 at 18:10
  • 2
    @Harpal, How can I add title for each subplot and legend for the overall plot? – Fracedo Feb 02 '18 at 13:11
26

Based on this post, what you want to do is something like this:

import matplotlib.pyplot as plt

# Start with one
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot([1,2,3])

# Now later you get a new subplot; change the geometry of the existing
n = len(fig.axes)
for i in range(n):
    fig.axes[i].change_geometry(n+1, 1, i+1)

# Add the new
ax = fig.add_subplot(n+1, 1, n+1)
ax.plot([4,5,6])

plt.show() 

However, Paul H's answer points to the submodule called gridspec which might make the above easier. I am leaving that as an exercise for the reader ^_~.

Community
  • 1
  • 1
Sardathrion - against SE abuse
  • 17,269
  • 27
  • 101
  • 156
  • 1
    How do you do this if you have x number of columns, not just one? If you change the '1' argument in change_geometry to something else the first plot is across all columns instead of being reasigned to a single column. – Frikster Jul 22 '15 at 22:25
  • 2
    http://stackoverflow.com/questions/31575399/dynamically-add-subplots-in-matplotlib-with-more-than-one-column – Frikster Jul 22 '15 at 22:54
  • 1
    @DirkHaupt: I do not know the answer to that but I upvoted your question! Thank you. – Sardathrion - against SE abuse Jul 23 '15 at 06:53
  • 1
    `change_geometry` is [deprecated](https://matplotlib.org/stable/api/prev_api_changes/api_changes_3.4.0.html?highlight=set_subplotspec#subplot-related-attributes-and-methods). I came up with a new solution [here](https://gist.github.com/LeoHuckvale/89683dc242f871c8e69b?permalink_comment_id=4067498#gistcomment-4067498), based on the `set_subplotspec` function. – Splines Feb 16 '22 at 14:04
  • This seems to be the only answer taking into account that the OP wants "to create the subplots dynamically". – Splines Feb 16 '22 at 14:06
2

Instead of counting your own number of rows and columns, I found it easier to create the subplots using plt.subplots first, then iterate through the axes object to add plots.

import matplotlib.pyplot as plt
import numpy as np

fig, axes = plt.subplots(nrows=3, ncols=2, figsize=(12, 8))
x_array = np.random.randn(6, 10)
y_array = np.random.randn(6, 10)

i = 0
for row in axes:
    for ax in row:
        x = x_array[i] 
        y = y_array[i]
        ax.scatter(x, y)
        ax.set_title("Plot " + str(i))
        i += 1
plt.tight_layout()
plt.show()

Here I use i to iterate through elements of x_array and y_array, but you can likewise easily iterate through functions, or columns of dataframes to dynamically generate graphs.

charleslow
  • 21
  • 1
  • This solution can be simplified by looping through the axes like this instead: `for i, ax in enumerate(axes.flat): ax.scatter(x_array[i], y_array[i]); ax.set_title(f'Plot {i}')` – Patrick FitzGerald Jan 24 '21 at 14:40