Embedding several inset axes in another axis using matplotlib - python

Is it possible to embed a changing number of plots in a matplotlib axis? For example, the inset_axes method is used to place inset axes inside parent axes:
However, I have several rows of plots and I want to include some inset axes inside the last axis object of each row.
fig, ax = plt.subplots(2,4, figsize=(15,15))
for i in range(2):
ax[i][0].plot(np.random.random(40))
ax[i][2].plot(np.random.random(40))
ax[i][3].plot(np.random.random(40))
# number of inset axes
number_inset = 5
for j in range(number_inset):
ax[i][4].plot(np.random.random(40))
Here instead of the 5 plots drawn in the last column, I want several inset axes containing a plot. Something like this:
The reason for this is that every row refers to a different item to be plotted and the last column is supposed to contain the components of such item. Is there a way to do this in matplotlib or maybe an alternative way to visualize this?
Thanks

As #hitzg mentioned, the most common way to accomplish something like this is to use GridSpec. GridSpec creates an imaginary grid object that you can slice to produce subplots. It's an easy way to align fairly complex layouts that you want to follow a regular grid.
However, it may not be immediately obvious how to use it in this case. You'll need to create a GridSpec with numrows * numinsets rows by numcols columns and then create the "main" axes by slicing it with intervals of numinsets.
In the example below (2 rows, 4 columns, 3 insets), we'd slice by gs[:3, 0] to get the upper left "main" axes, gs[3:, 0] to get the lower left "main" axes, gs[:3, 1] to get the next upper axes, etc. For the insets, each one is gs[i, -1].
As a complete example:
import numpy as np
import matplotlib.pyplot as plt
def build_axes_with_insets(numrows, numcols, numinsets, **kwargs):
"""
Makes a *numrows* x *numcols* grid of subplots with *numinsets* subplots
embedded as "sub-rows" in the last column of each row.
Returns a figure object and a *numrows* x *numcols* object ndarray where
all but the last column consists of axes objects, and the last column is a
*numinsets* length object ndarray of axes objects.
"""
fig = plt.figure(**kwargs)
gs = plt.GridSpec(numrows*numinsets, numcols)
axes = np.empty([numrows, numcols], dtype=object)
for i in range(numrows):
# Add "main" axes...
for j in range(numcols - 1):
axes[i, j] = fig.add_subplot(gs[i*numinsets:(i+1)*numinsets, j])
# Add inset axes...
for k in range(numinsets):
m = k + i * numinsets
axes[i, -1][k] = fig.add_subplot(gs[m, -1])
return fig, axes
def plot(axes):
"""Recursive plotting function just to put something on each axes."""
for ax in axes.flat:
data = np.random.normal(0, 1, 100).cumsum()
try:
ax.plot(data)
ax.set(xticklabels=[], yticklabels=[])
except AttributeError:
plot(ax)
fig, axes = build_axes_with_insets(2, 4, 3, figsize=(12, 6))
plot(axes)
fig.tight_layout()
plt.show()

This is what I did to obtain the same result without setting the number of inset plots in advance.
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import numpy as np
fig = plt.figure(figsize=(12,6))
nrows = 2
ncols = 4
# changing the shape of GridSpec's output
outer_grid = gridspec.GridSpec(nrows, ncols)
grid = []
for i in range(nrows*ncols):
grid.append(outer_grid[i])
outer_grid = np.array(grid).reshape(nrows,ncols)
for i in range(nrows):
inner_grid_1 = gridspec.GridSpecFromSubplotSpec(1, 1,
subplot_spec=outer_grid[i][0])
ax = plt.Subplot(fig, inner_grid_1[0])
ax.plot(np.random.normal(0,1,50).cumsum())
fig.add_subplot(ax)
inner_grid_2 = gridspec.GridSpecFromSubplotSpec(1, 1,
subplot_spec=outer_grid[i][1])
ax2 = plt.Subplot(fig, inner_grid_2[0])
ax2.plot(np.random.normal(0,1,50).cumsum())
fig.add_subplot(ax2)
inner_grid_3 = gridspec.GridSpecFromSubplotSpec(1, 1,
subplot_spec=outer_grid[i][2])
ax3 = plt.Subplot(fig, inner_grid_3[0])
ax3.plot(np.random.normal(0,1,50).cumsum())
fig.add_subplot(ax3)
# this value can be set based on some other calculation depending
# on each row
numinsets = 3
inner_grid_4 = gridspec.GridSpecFromSubplotSpec(numinsets, 1,
subplot_spec=outer_grid[i][3])
# Adding subplots to the last inner grid
for j in range(inner_grid_4.get_geometry()[0]):
ax4 = plt.Subplot(fig, inner_grid_4[j])
ax4.plot(np.random.normal(0,1,50).cumsum())
fig.add_subplot(ax4)
# Removing labels
for ax in fig.axes:
ax.set(xticklabels=[], yticklabels=[])
fig.tight_layout()

Related

Creating a matrix of plots with sns distplot

I am plotting 20+ features like so:
for col in dsd_mod["ae_analysis"].columns[:len(dsd_mod["ae_analysis"].columns)]:
if col != "sae_flag":
sns.distplot(dsd_mod["ae_analysis"].loc[(dsd_mod["ae_analysis"]['sae_flag'] == 1),col],
color='r',
kde=True,
hist=False,
label='sae_ae = 1')
sns.distplot(dsd_mod["ae_analysis"].loc[(dsd_mod["ae_analysis"]['sae_flag'] == 0),col],
color='y',
kde=True,
hist=False,
label='sae_ae = 0')
Which creates a separate graph for each feature. How can I put these all on a matrix? Or like how pair plots outputs?
Right now I get 30 graphs like this all in one column:
How can I modify this so that I can get 6 rows and 5 columns ?
Thanks in advance!
displot can use whatever axes object you want to draw the plot. So you just need to create your axes with the desired geometry, and pass the relevant axes to your functions.
fig, axs = plt.subplots(6,5)
# axs is a 2D array with shape (6,5)
# you can keep track of counters in your for-loop to place the resulting graphs
# using ax=axs[i,j]
# or an alternative is to use a generator that you can use to get the next axes
# instance at every step of the loop
ax_iter = iter(axs.flat)
for _ in range(30):
ax = next(ax_iter)
sns.distplot(np.random.normal(loc=0, size=(1000,)), ax=ax)
sns.distplot(np.random.normal(loc=1, size=(1000,)), ax=ax)

Matplotlib title spanning two (or any number of) subplot columns

Because of the nature of what I am plotting, I want subplots akin to nested tables.
I'm not sure how to ask the question clearly so I'll added some pictures instead which I hope illustrate the problem.
What I have:
What I want:
Current (shortened) code looks something like this:
fig, axes = plt.subplots(nrows=5, ncols=4)
fig.suptitle(title, fontsize='x-large')
data0.plot(x=data0.x, y=data0.y, ax=axes[0,0],kind='scatter')
data1.plot(x=data1.x, y=data1.y, ax=axes[0,1],kind='scatter')
axes[0,0].set_title('title 0')
axes[0,1].set_title('title 1')
I can't figure out how to set a title for axes[0,0] and [0,1] together. I can't find anything in the documentation either. I am not fond of fussing around with tables in latex to achieve this. Any pointers?
Setting the figure title using fig.suptitle() and the axes (subplot) titles using ax.set_title() is rather straightforward. For setting an intermediate, column spanning title there is indeed no build in option.
One way to solve this issue can be to use a plt.figtext() at the appropriate positions. One needs to account some additional space for that title, e.g. by using fig.subplots_adjust and find appropriate positions of this figtext.
In the example below, we use the bounding boxes of the axes the title shall span over to find a centralized horizontal position. The vertical position is a best guess.
import matplotlib.pyplot as plt
import numpy as np
x = np.arange(10)
y = np.random.rand(10,8)
colors=["b", "g", "r", "violet"]
fig, axes = plt.subplots(nrows=2, ncols=4, sharex=True, sharey=True, figsize=(8,5))
#set a figure title on top
fig.suptitle("Very long figure title over the whole figure extent", fontsize='x-large')
# adjust the subplots, i.e. leave more space at the top to accomodate the additional titles
fig.subplots_adjust(top=0.78)
ext = []
#loop over the columns (j) and rows(i) to populate subplots
for j in range(4):
for i in range(2):
axes[i,j].scatter(x, y[:,4*i+j], c=colors[j], s=25)
# each axes in the top row gets its own axes title
axes[0,j].set_title('title {}'.format(j+1))
# save the axes bounding boxes for later use
ext.append([axes[0,j].get_window_extent().x0, axes[0,j].get_window_extent().width ])
# this is optional
# from the axes bounding boxes calculate the optimal position of the column spanning title
inv = fig.transFigure.inverted()
width_left = ext[0][0]+(ext[1][0]+ext[1][1]-ext[0][0])/2.
left_center = inv.transform( (width_left, 1) )
width_right = ext[2][0]+(ext[3][0]+ext[3][1]-ext[2][0])/2.
right_center = inv.transform( (width_right, 1) )
# set column spanning title
# the first two arguments to figtext are x and y coordinates in the figure system (0 to 1)
plt.figtext(left_center[0],0.88,"Left column spanning title", va="center", ha="center", size=15)
plt.figtext(right_center[0],0.88,"Right column spanning title", va="center", ha="center", size=15)
axes[0,0].set_ylim([0,1])
axes[0,0].set_xlim([0,10])
plt.show()
New in matplotlib 3.4.0
You can use subfigures if you have matplotlib version >= 3.4.0 (as mentioned in a comment by #ra0).
Once the subfigures are created, you can treat them exactly as you would a normal figure and create subplots and add suptitles.
Documentation and examples on subfigures.
import matplotlib.pyplot as plt
import numpy as np
x = np.arange(10)
y = np.random.rand(10, 8)
colors = ["b", "g", "r", "violet"]
fig = plt.figure(figsize=(8, 5), constrained_layout=True)
subfigs = fig.subfigures(1, 2)
titles = ["Left spanning title", "Right spanning title"]
for i, subfig in enumerate(subfigs):
axes = subfig.subplots(2, 2)
for j, row in enumerate(axes):
for k, ax in enumerate(row):
ax.scatter(x, y[:, i*4 + j*2 + k], color=colors[i*2 + k], s=25)
ax.set_xlim([0, 10])
ax.set_ylim([0, 1])
if j == 0:
ax.set_title(f"fig{i}, row{j}, col{k}")
subfig.suptitle(titles[i])
fig.suptitle("Very long figure title over the whole figure extent", fontsize='x-large')
plt.show()

Python, Matplotlib custom axes share Y axis

I have simple code to create a figure with 7 axes/ custom subplots (my understanding is that subplots are equal-sized and equal-spaced and in my particular situation I need one to be larger than the rest).
fig = plt.figure(figsize = (16,12))
# row 1
ax1 = plt.axes([0.1,0.7,0.2,0.2])
ax2 = plt.axes([0.4,0.7,0.2,0.2])
ax3 = plt.axes([0.7,0.7,0.2,0.2])
# big row 2
ax4 = plt.axes([0.1, 0.4, 0.5, 0.2])
#row 3
ax5 = plt.axes([0.1,0.1,0.2,0.2])
ax6 = plt.axes([0.4,0.1,0.2,0.2])
ax7 = plt.axes([0.7,0.1,0.2,0.2])
my question is, how do i get all of these axes to share the same y-axis. All i can find on google/stack is for subplots, eg:
ax = plt.subplot(blah, sharey=True)
but calling the same thing for axes creation does not work:
ax = plt.axes([blah], sharey=True) # throws error
is there anyway to accomplish this? What I'm working with is:
This is quite simple using matplotlib.gridspec.GridSpec
gs=GridSpec(3,3) creates a 3x3 grid to place subplots on
For your top and bottom rows, we just need to index one cell on that 3x3 grid (e.g. gs[0,0] is on the top left).
For the middle row, you need to span two columns, so we use gs[1,0:2]
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
fig=plt.figure(figsize=(16,12))
gs = GridSpec(3,3)
# Top row
ax1=fig.add_subplot(gs[0,0])
ax2=fig.add_subplot(gs[0,1],sharey=ax1)
ax3=fig.add_subplot(gs[0,2],sharey=ax1)
# Middle row
ax4=fig.add_subplot(gs[1,0:2],sharey=ax1)
# Bottom row
ax5=fig.add_subplot(gs[2,0],sharey=ax1)
ax6=fig.add_subplot(gs[2,1],sharey=ax1)
ax7=fig.add_subplot(gs[2,2],sharey=ax1)
ax1.set_ylim(-15,10)
plt.show()

Dynamically add/create subplots in matplotlib

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)
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?
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.
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.
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 ^_~.
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.

Python Subplot function parameters

I am having a hard time with putting in the parameters for the python subplot function.
What I want is to plot 4 graphs on a same image file with the following criteria
left
space
right
space
left
space
right
I have tried different ways of the 3 numbers but the output doesnt show up correctly.
Do you mean something like this?
import matplotlib.pyplot as plt
fig = plt.figure()
ax1 = fig.add_subplot(4,2,1)
ax2 = fig.add_subplot(4,2,4)
ax3 = fig.add_subplot(4,2,5)
ax4 = fig.add_subplot(4,2,8)
fig.subplots_adjust(hspace=1)
plt.show()
Well, the not-so-easily-found documentation regarding the sublot function template is as follows:
subplot (number_of_graphs_horizontal, number of graphs_vertical, index)
Let us investigate the code from Joe Kington above:
import matplotlib.pyplot as plt
fig = plt.figure()
ax1 = fig.add_subplot(4,2,1)
ax2 = fig.add_subplot(4,2,4)
ax3 = fig.add_subplot(4,2,5)
ax4 = fig.add_subplot(4,2,8)
fig.subplots_adjust(hspace=1)
plt.show()
You told matplotlib that you want a grid with 4 rows and 2 columns of graphs. ax1, ax2 and so on are the graphs that you add at the index positions which you can read as the third parameter. You count from left to right in a row-wise manner.
I hope that helped :)
Matplotlib provides several ways deal with the deliberate placement of plots on a single page; i think the best is gridspec, which i believe first appeared in the 1.0 release. The other two, by the way, are (i) directly indexing subplot and (ii) the new ImageGrid toolkit).
GridSpec works like grid-based packers in GUI toolkits used to placed widgets in a parent frame, so for that reason at least, it seems the easiest to use and the most configurable of the three placement techniques.
import numpy as NP
import matplotlib.pyplot as PLT
import matplotlib.gridspec as gridspec
import matplotlib.cm as CM
V = 10 * NP.random.rand(10, 10) # some data to plot
fig = PLT.figure(1, (5., 5.)) # create the top-level container
gs = gridspec.GridSpec(4, 4) # create a GridSpec object
# for the arguments to subplot that are identical across all four subplots,
# to avoid keying them in four times, put them in a dict
# and let subplot unpack them
kx = dict(frameon = False, xticks = [], yticks = [])
ax1 = PLT.subplot(gs[0, 0], **kx)
ax3 = PLT.subplot(gs[2, 0], **kx)
ax2 = PLT.subplot(gs[1, 1], **kx)
ax4 = PLT.subplot(gs[3, 1], **kx)
for itm in [ax1, ax2, ax3, ax4] :
itm.imshow(V, cmap=CM.jet, interpolation='nearest')
PLT.show()
Beyond just arranging the four plots in a 'checkerboard' configuration (per your Question), I have not tried to tune this configuration, but that's easy to do. E.g.,
# to change the space between the cells that hold the plots:
gs1.update(left=.1, right=,1, wspace=.1, hspace=.1)
# to create a grid comprised of varying cell sizes:
gs = gridspec.GridSpec(4, 4, width_ratios=[1, 2], height_ratios=[4, 1])

Categories

Resources