Pylab - Adjust hspace for some of the subplots - python

I have a plot in which I want to have one panel separate from other four panels. I want the rest of the four panels to share the x axis. The figure is shown below. I want the bottom four panels to have shared x-axis. I tried
f = plt.figure()
ax6=f.add_subplot(511)
ax4=f.add_subplot(515)
ax1=f.add_subplot(512,sharex=ax4)
ax2=f.add_subplot(513,sharex=ax4)
ax3=f.add_subplot(514,sharex=ax4)
However, that does not work for me. The attached figure is made with
f = plt.figure()
ax6=f.add_subplot(511)
ax4=f.add_subplot(515)
ax1=f.add_subplot(512)
ax2=f.add_subplot(513)
ax3=f.add_subplot(514)
and then setting the xticks to none by
ax1.get_xaxis().set_ticklabels([])
ax2.get_xaxis().set_ticklabels([])
ax3.get_xaxis().set_ticklabels([])
using f.subplots_adjust(hspace=0) joins all the subplots. Is there a way to join only the bottom four panels?
Thanks!

It's easiest to use two separate gridspec objects for this. That way you can have independent margins, padding, etc for different groups of subplots.
As a quick example:
import numpy as np
import matplotlib.pyplot as plt
# We'll use two separate gridspecs to have different margins, hspace, etc
gs_top = plt.GridSpec(5, 1, top=0.95)
gs_base = plt.GridSpec(5, 1, hspace=0)
fig = plt.figure()
# Top (unshared) axes
topax = fig.add_subplot(gs_top[0,:])
topax.plot(np.random.normal(0, 1, 1000).cumsum())
# The four shared axes
ax = fig.add_subplot(gs_base[1,:]) # Need to create the first one to share...
other_axes = [fig.add_subplot(gs_base[i,:], sharex=ax) for i in range(2, 5)]
bottom_axes = [ax] + other_axes
# Hide shared x-tick labels
for ax in bottom_axes[:-1]:
plt.setp(ax.get_xticklabels(), visible=False)
# Plot variable amounts of data to demonstrate shared axes
for ax in bottom_axes:
data = np.random.normal(0, 1, np.random.randint(10, 500)).cumsum()
ax.plot(data)
ax.margins(0.05)
plt.show()

Related

How would I create multiple subplots in the same subplot in a while loop?: Python [duplicate]

I would like to have three plots in a single figure. The figure should have a subplot layout of two by two, where the first plot should occupy the first two subplot cells (i.e. the whole first row of plot cells) and the other plots should be positioned underneath the first one in cells 3 and 4.
I know that MATLAB allows this by using the subplot command like so:
subplot(2,2,[1,2]) % the plot will span subplots 1 and 2
Is it also possible in pyplot to have a single axes occupy more than one subplot?
The docstring of pyplot.subplot doesn't talk about it.
Anyone got an easy solution?
You can simply do:
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(0, 7, 0.01)
plt.subplot(2, 1, 1)
plt.plot(x, np.sin(x))
plt.subplot(2, 2, 3)
plt.plot(x, np.cos(x))
plt.subplot(2, 2, 4)
plt.plot(x, np.sin(x)*np.cos(x))
i.e., the first plot is really a plot in the upper half (the figure is only divided into 2x1 = 2 cells), and the following two smaller plots are done in a 2x2=4 cell grid.
The third argument to subplot() is the position of the plot inside the grid (in the direction of reading in English, with cell 1 being in the top-left corner):
for example in the second subplot (subplot(2, 2, 3)), the axes will go to the third section of the 2x2 matrix i.e, to the bottom-left corner.
The Using Gridspec to make multi-column/row subplot layouts shows a way to do this with GridSpec. A simplified version of the example with 3 subplots would look like
import matplotlib.pyplot as plt
fig = plt.figure()
gs = fig.add_gridspec(2,2)
ax1 = fig.add_subplot(gs[0, 0])
ax2 = fig.add_subplot(gs[0, 1])
ax3 = fig.add_subplot(gs[1, :])
plt.show()
To have multiple subplots with an axis occupy, you can simply do:
from matplotlib import pyplot as plt
import numpy as np
b=np.linspace(-np.pi, np.pi, 100)
a1=np.sin(b)
a2=np.cos(b)
a3=a1*a2
plt.subplot(221)
plt.plot(b, a1)
plt.title('sin(x)')
plt.subplot(222)
plt.plot(b, a2)
plt.title('cos(x)')
plt.subplot(212)
plt.plot(b, a3)
plt.title('sin(x)*cos(x)')
plt.show()
Another way is
plt.subplot(222)
plt.plot(b, a1)
plt.title('sin(x)')
plt.subplot(224)
plt.plot(b, a2)
plt.title('cos(x)')
plt.subplot(121)
plt.plot(b, a3)
plt.title('sin(x)*cos(x)')
plt.show()
For finer-grained control you might want to use the subplot2grid module of matplotlib.pyplot.
http://matplotlib.org/users/gridspec.html
A more modern answer would be: Simplest is probably to use subplots_mosaic:
https://matplotlib.org/stable/tutorials/provisional/mosaic.html
import matplotlib.pyplot as plt
import numpy as np
# Some example data to display
x = np.linspace(0, 2 * np.pi, 400)
y = np.sin(x ** 2)
fig, axd = plt.subplot_mosaic([['left', 'right'],['bottom', 'bottom']],
constrained_layout=True)
axd['left'].plot(x, y, 'C0')
axd['right'].plot(x, y, 'C1')
axd['bottom'].plot(x, y, 'C2')
plt.show()
There are three main options in matplotlib to make separate plots within a figure:
subplot: access the axes array and add subplots
gridspec: control the geometric properties of the underlying figure (demo)
subplots: wraps the first two in a convenient api (demo)
The posts so far have addressed the first two options, but they have not mentioned the third, which is the more modern approach and is based on the first two options. See the specific docs Combining two subplots using subplots and GridSpec.
Update
A much nicer improvement may be the provisional subplot_mosaic method mentioned in #Jody Klymak's post. It uses a structural, visual approach to mapping out subplots instead of confusing array indices. However it is still based on the latter options mentioned above.
I can think of 2 more flexible solutions.
The most flexible way: using subplot_mosaic.
f, axes = plt.subplot_mosaic('AAB;CDD;EEE')
# axes = {'A': ..., 'B': ..., ...}
Effect:
Using gridspec_kw of subplots. Although it is also inconvenient when different rows need different width ratios.
f, (a0, a1) = plt.subplots(1, 2, gridspec_kw={'width_ratios': [2, 1]})
Effect:
The subplot method of other answers is kind of rigid, IMO. For example, you cannot create two rows with width ratios being 1:2 and 2:1 easily. However, it can help when you need to overwrite some layout of subplots, for example.

Is there a way in python using matplotlib to create a figure with subplots of subplots?

I'm trying to display a figure that contains 3 plots, and each of the plots is a plot of (8,1)-shaped subplots.
Essentially, I want one big figure with three sections each containing (8,1)-shaped subplots.
I'm looking for a way to do this without having to manually set all the proportions and spacings. The reason I'm doing this is to visualize an 8-channel neural signal compared to three other pre-defined signals, each signal being 8 channels.
If it makes any sense this way, I'm trying for something like this (ficticious code):
fig, ax = plt.subplots(n_figures = 3, n_rows = 8, n_cols = 1)
ax[figure_i, row_j, col_k].imshow(image)
Is there a way to do this?
Here is an example of what I am talking about. Ideally it would three subplots, and in each of the subplots there is a set of subplots of shape 8x1. I understand how to plot this all out by going through all the margins and setting the proportions, but I'm wondering if there's a simpler way to do this without having to go through all the additional code and settings as described in the above example code I've written.
You can create this kind of figure by first creating a subplot grid with the appropriate layout using the plt.subplots() function and then looping through the array of axes to plot the data, like in this example:
import numpy as np # v 1.19.2
import matplotlib.pyplot as plt # v 3.3.2
# Create sample signal data as a 1-D list of arrays representing 3x8 channels
signal_names = ['X1', 'X2', 'X3']
nsignals = len(signal_names) # ncols of the subplot grid
nchannels = 8 # nrows of the subplot grid
nsubplots = nsignals*nchannels
x = np.linspace(0, 14*np.pi, 100)
y_signals = nsubplots*[np.cos(x)]
# Set subplots width and height
subp_w = 10/nsignals # 10 corresponds the figure width in inches
subp_h = 0.25*subp_w
# Create figure and subplot grid with the appropriate layout and dimensions
fig, axs = plt.subplots(nchannels, nsignals, sharex=True, sharey=True,
figsize=(nsignals*subp_w, nchannels*subp_h))
# Optionally adjust the space between the subplots: this can also be done by
# adding 'gridspec_kw=dict(wspace=0.1, hspace=0.3)' to the above function
# fig.subplots_adjust(wspace=0.1, hspace=0.3)
# Loop through axes to create plots: note that the list of axes is transposed
# in this example to plot the signals one after the other column-wise, as
# indicated by the colors representing the channels
colors = nsignals*plt.get_cmap('tab10').colors[:nchannels]
for idx, ax in enumerate(axs.T.flat):
ax.plot(x, y_signals[idx], c=colors[idx])
if ax.is_first_row():
ax.set_title(signal_names[idx//nchannels], pad=15, fontsize=14)
plt.show()

Synchronising dual x axes in a dual (sub)plot in interactive Matplotlib?

Basically, I want to achieve the same as in https://stackoverflow.com/a/58413766/6197439 - except with a two plots.
The example code for this is pasted below, and here is an animated gif of how it behaves:
The thing is:
I have to make the dual axis a twiny of ax2 (the bottom subplot) so it is drawn below the shared x axis at the bottom - else it gets drawn below the top plot (and overlapping the top of the bottom plot)
After start, I first drag in the bottom subplot - both axes follow as they should
If I zoom in the bottom subplot, both x-axes scale properly - but the twin axes does not have all the labels (that is why I have on_xlims_change, which helped fix that in the linked post, where there was only one plot - but here I cannot get it to work)
If then I drag in the top subplot - only the original x-axis moves, the dual/twinned cloned x-axis does not (the gif doesn't show that, but the same goes for zooming in top subplot as well)
I have tried using the callback on either and both ax and ax2, and I couldn't get an improved behavior - however, note that the gif shows the behavior as in the code posted here (where the callback is not used).
So, how can I make the dual/twinned x-axis follow the original shared x-axis - across both zoom and pan, in both the top and the bottom subplot?
The code:
#!/usr/bin/env python3
import matplotlib
print("matplotlib.__version__ {}".format(matplotlib.__version__))
import matplotlib.pyplot as plt
#
# Some toy data
x_seq = [x / 100.0 for x in range(1, 100)]
y_seq = [x**2 for x in x_seq]
y2_seq = [0.3*x**2 for x in x_seq]
#
# Scatter plot
fig, (ax, ax2) = plt.subplots(2, 1, sharex=True, figsize=(9, 6), dpi=120, gridspec_kw={'height_ratios': [2, 1]}) # two rows, one column
# Remove horizontal space between axes
fig.subplots_adjust(hspace=0)
ax.plot(x_seq, y_seq)
ax2.plot(x_seq, y2_seq)
# https://stackoverflow.com/questions/31803817/how-to-add-second-x-axis-at-the-bottom-of-the-first-one-in-matplotlib
ax22 = ax2.twiny() # instantiate a second axes that shares the same y-axis
# Move twinned axis ticks and label from top to bottom
ax22.xaxis.set_ticks_position("bottom")
ax22.xaxis.set_label_position("bottom")
# Offset the twin axis below the host
ax22.spines["bottom"].set_position(("axes", -0.1))
factor = 655
old_xlims = ax2.get_xlim()
new_xlims = (factor*old_xlims[0], factor*old_xlims[1])
old_tlocs = ax2.get_xticks()
new_tlocs = [i*factor for i in old_tlocs]
print("old_xlims {} new_xlims {} old_tlocs {} new_tlocs {}".format(old_xlims, new_xlims, old_tlocs, new_tlocs))
ax22.set_xticks(new_tlocs)
ax22.set_xlim(*new_xlims)
def on_xlims_change(axes):
old_tlocs = axes.get_xticks()
new_tlocs = [i*factor for i in old_tlocs]
ax22.set_xticks(new_tlocs)
# ax.callbacks.connect('xlim_changed', on_xlims_change)
# ax2.callbacks.connect('xlim_changed', on_xlims_change)
#
# Show
plt.show()
I think I have a solution (code below):
.... thanks to the comment by #ImportanceOfBeingErnest :
axes.get_xticks() gets you the ticks before the change.
Well, now at least it makes sense, why it was so difficult to set it up :) Wish I found this info earlier ... People seem to have had a problem with it:
matplotlib interactive subplots with sharex and twiny
How to to enable sharing for the secondary axis (twiny) in python
I think I would try to share all three axes
The only info I found on this is:
Aplpy multiplot dynamic axis sharing
How share x axis of two subplots after they are created?
Apparently, one can use ax1.get_shared_x_axes().join(ax1, ax2) -> however, this join is not in the sense of "join"ing an array to string in Python, nor in the sense of appending to an array, it is in the sense of a (dis)join(t) set, apparently - so you can join three items, which is what I tried (and it seems to work):
ax.get_shared_x_axes().join(ax, ax2, ax22)
Is this correct?
and just use a different formatter on the last one.
There is decent info on that here:
https://matplotlib.org/3.1.1/gallery/ticks_and_spines/tick-formatters.html
So, finally, my code is:
#!/usr/bin/env python3
import matplotlib
print("matplotlib.__version__ {}".format(matplotlib.__version__))
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
#
# Some toy data
x_seq = [x / 100.0 for x in range(1, 100)]
y_seq = [x**2 for x in x_seq]
y2_seq = [0.3*x**2 for x in x_seq]
#
# Scatter plot
fig, (ax, ax2) = plt.subplots(2, 1, sharex=True, figsize=(9, 6), dpi=100, gridspec_kw={'height_ratios': [2, 1]}) # two rows, one column
# Remove horizontal space between axes
fig.subplots_adjust(hspace=0)
# https://stackoverflow.com/questions/31803817/how-to-add-second-x-axis-at-the-bottom-of-the-first-one-in-matplotlib
ax22 = ax2.twiny() # instantiate a second axes that shares the same y-axis
#~ ax.get_shared_x_axes().join(ax, ax22) # SO:42718823
ax.get_shared_x_axes().join(ax, ax2, ax22)
#~ ax.autoscale() # <-- needed if no axes limits are explicitely set. SO:42718823
# Move twinned axis ticks and label from top to bottom
ax22.xaxis.set_ticks_position("bottom")
ax22.xaxis.set_label_position("bottom")
# Offset the twin axis below the host
ax22.spines["bottom"].set_position(("axes", -0.1))
ax.plot(x_seq, y_seq)
ax2.plot(x_seq, y2_seq)
factor = 655
# FuncFormatter can be used as a decorator
#ticker.FuncFormatter
def major_formatter(x, pos):
#return "[%.2f]" % x
return int(factor*x)
ax22.xaxis.set_major_formatter(major_formatter)
#
# Show
plt.show()

Re-combine different axes to make new figure in matplotlib

I am curious that whether this is possible in matplotlib:
I first create some figures, with subplots.
import matplotlib.pyplot as plt
fig1, axs1 = plt.subplots(2, 2)
fig2, axs2 = plt.subplots(2, 2)
And then, could I recombine them, so fig3 is composed of the first row in fig1 (i.e., axs1[0, 0] and axs1[0, 1]) and second row in fig2 (i.e., axs2[1, 0] and axs2[1, 1])?
Currently, all I could do is to re-plot them. I am curious about whether there is a way that I can just move axes around and re-combine them to make new figures. Thanks!
-Shawn

Python: Parallel coordinates subplots in subplot

I saw this example on how to create a parallel coordinate plot: Parallel Coordinates:
This creates a nice Parallel Coordinates figure, but I would like to add this plot to an already existing figure in a subplot (there should be another plot next to it in the same plot).
For the already existing figure, the figure and axes are defined as:
fig = plt.figure(figsize=plt.figaspect(2.))
ax = fig.add_subplot(1,2,1)
For the Parallel Coordinates, they suggest:
fig, axes = plt.subplots(1, dims-1, sharey=False)
How can I reconcile both initializations of the figure and the ax(es)?
One option is to create all the axes using subplots then just shift the location of the one that you don't want to have wspace=0 as is done for the Parallel Coordinate plots:
import matplotlib.pylab as plt
dims = 4
fig, axes = plt.subplots(1, dims-1 + 1, sharey=False)
plt.subplots_adjust(wspace=0)
ax1 = axes[0]
pos = ax1.get_position()
ax1.set_position(pos.translated(tx = -0.1,ty=0))
I have added 1 to the number of columns creates (leaving it explicitly -1+1) and set wspace=0 which draws all the plots adjacent to one another with no space inbetween. Take the left most axes and get the position which is a Bbox. This is nice as it gives you the ability to translate it by tx=-0.1 separating your existing figure.

Categories