Shared axis with gridspec subplots - python

I'm using nested GridSpecFromSubplotSpec to create a nested grid of axes. I have two independent set of axes, a top one and a bottom one. Each set has four axes, arranged in a 2x2 grid.
Here is the code I'm using and the result I obtain:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gsp
fig = plt.figure()
global_gsp = gsp.GridSpec(2, 1)
for i in range(2):
axes = np.empty(shape=(2, 2), dtype=object)
local_gsp = gsp.GridSpecFromSubplotSpec(2, 2, subplot_spec=global_gsp[i])
for j in range(2):
for k in range(2):
ax = plt.Subplot(fig, local_gsp[j, k],
sharex=axes[0, 0], sharey=axes[0, 0])
fig.add_subplot(ax)
axes[j, k] = ax
for j in range(2):
for k in range(2):
ax = axes[j, k]
x = i + np.r_[0:1:11j]
y = 10*i + np.random.random(11)
ax.plot(x, y, color=f'C{i}')
ax.set_xlabel('x')
ax.set_ylabel('y')
plt.show()
As you can see, the top set has blue lines, the bottom set has orange lines, and the blue lines are well represented with the limits [0, 1]x[0, 1], while the orange lines are represented with the limits [1, 2]x[10, 11]. When I create the subplots with plt.Subplot, I use the sharex and sharey arguments to have exactly the same scale on all four axes in each set (but different scale across different sets).
I would like to aviod the repetition of the label and the ticks of each axis. How can I achieve that?

Subplot axes have functions is_{first,last}_{col,row}() (although I could not find the documentation anywhere) as shown in this matplotlib tutorial. These functions are useful to only print the label(s) and/or ticks in the right spot. To hide the tick labels, shared_axis_demo.py recommends using setp(ax.get_{x,y}ticklabels(), visible=False)
fig = plt.figure()
global_gsp = gs.GridSpec(2, 1)
for i in range(2):
axes = np.empty(shape=(2, 2), dtype=object)
local_gsp = gs.GridSpecFromSubplotSpec(2, 2, subplot_spec=global_gsp[i])
for j in range(2):
for k in range(2):
ax = plt.Subplot(fig, local_gsp[j, k],
sharex=axes[0, 0], sharey=axes[0, 0])
fig.add_subplot(ax)
axes[j, k] = ax
for j in range(2):
for k in range(2):
ax = axes[j, k]
x = i + np.r_[0:1:11j]
y = 10*i + np.random.random(11)
ax.plot(x, y, color=f'C{i}')
#
# adjust axes and tick labels here
#
if ax.is_last_row():
ax.set_xlabel('x')
else:
plt.setp(ax.get_xticklabels(), visible=False)
if ax.is_first_col():
ax.set_ylabel('y')
else:
plt.setp(ax.get_yticklabels(), visible=False)
fig.tight_layout()
plt.show()

Related

How to group subplots by adjusting spaces in between

I have a subplots that look as follows:
import matplotlib.pyplot as plt
x = [1, 2, 3]
y = [4, 5, 6]
fig_shape, axs_shape = plt.subplots(2, 6, figsize=(6, 6))
for i in range(2):
for j in range(6):
axs_shape[i, j].xaxis.set_major_locator(plt.NullLocator())
axs_shape[i, j].yaxis.set_major_locator(plt.NullLocator())
for i in range(6):
axs_shape[int(i / 3), 2 * (i % 3)].plot(x, y)
axs_shape[int(i / 3), 2 * (i % 3) + 1].plot(x, y)
What I want is, that the subplots are grouped in pairs of two. That means, in each row, I want plot 0 and 1 to be right next to each other (no space in between). Then a small space and followed by plot 2 and 3 right next to each other. Then a space and plot 4 and 5 right next to each other. I read, that you can adjust sizes with .tight_layout() and subplots_adjust, but I couldn't figure out a solution for this particular behavior. Thanks a lot for your help!
You can use nested gridspecs:
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
x = [1, 2, 3]
y = [4, 5, 6]
fig = plt.figure(figsize=(12, 5))
outer = gridspec.GridSpec(nrows=2, ncols=3)
axs = []
for row in range(2):
for col in range(3):
inner = gridspec.GridSpecFromSubplotSpec(nrows=1, ncols=2, subplot_spec=outer[row, col], wspace=0)
axs += [plt.subplot(cell) for cell in inner]
for ax in axs:
ax.plot(x, y)
ax.set_yticks([])
ax.set_xticks([])
plt.tight_layout()
plt.show()
PS: As mentioned in the other answer, matplotlib has implemented subfigures as a new feature. If I understand correctly, the above example would be more or less as follows:
import matplotlib.pyplot as plt
x = [1, 2, 3]
y = [4, 5, 6]
fig = plt.figure(figsize=(12, 5), constrained_layout=True)
subfigs = fig.subfigures(nrows=2, ncols=3, wspace=0.07)
axs = [subfig.subplots(nrows=1, ncols=2, gridspec_kw={'wspace': 0}) for subfig in subfigs.ravel()]
for subax in axs:
for ax in subax:
ax.plot(x, y)
ax.set_yticks([])
ax.set_xticks([])
plt.show()
With the current matplotlib 3.4.1, I don't seem to be able to have the inner plots without a gap. Setting constrained_layout=False even makes that the 4 rightmost subplots disappear. Now it looks like:
This is the goal of the new subfigure functionality: https://matplotlib.org/stable/gallery/subplots_axes_and_figures/subfigures.html?highlight=subfigure

Putting one color bar for several subplots from different dataframes

I looked everywhere and nothing really helped.
Here is my code:
fig = plt.figure(figsize=(12, 6))
marker_colors = pca_data2['Frame']
fig.suptitle('PCA')
plt.subplot(1, 2, 1)
x = pca_data2.PC_1
y = pca_data2.PC_2
plt.scatter(x, y, c = marker_colors, cmap = "inferno")
plt.colorbar()
plt.subplot(1, 2, 2)
x1 = pca_data.PC_1
y1 = pca_data.PC_2
plt.scatter(x1, y1, c = marker_colors, cmap = "inferno")
plt.colorbar()
plt.show()
pca_data and pca_data2 are two completely different dataframes from to completele different things. But I need them side by side with the 1 color bar being on the right side for all.
Thats how the figure looks like
When I try to remove the first plt.colorbar() then the two subplots look uneven.
I would really appreciate the help.
... since none of the answers seems to mention the fact that you can tell the colorbar the axes on which it should be drawn... here's a simple example how I would do it:
The benefits of this are:
it's much clearer to read
you have complete control over the size of the colorbar
you can extend this easily to any grid of subplots and any position of the colorbar
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
# generate some data
data, data1 = np.random.rand(10,10), np.random.rand(10,10)
x, y = np.meshgrid(np.linspace(0,1,10), np.linspace(0,1,10))
# initialize a plot-grid with 3 axes (2 plots and 1 colorbar)
gs = GridSpec(1, 3, width_ratios=[.48,.48,.04])
# set vmin and vmax explicitly to ensure that both colorbars have the same range!
vmin = np.min([np.min(data), np.min(data1)])
vmax = np.max([np.max(data), np.max(data1)])
plot_kwargs = dict(cmap = "inferno", vmin=vmin, vmax=vmax)
fig = plt.figure(figsize=(12, 6))
ax_0 = fig.add_subplot(gs[0], aspect='equal')
ax_1 = fig.add_subplot(gs[1], aspect='equal')
ax_cb = fig.add_subplot(gs[2])
s1 = ax_0.scatter(x, y, c = data, **plot_kwargs)
s2 = ax_1.scatter(x, y, c = data1, **plot_kwargs)
plt.colorbar(s1, cax=ax_cb)
You can use aspect to set a fixed aspect ratio on the subplots. Then append the colorbars to the right side of each axis and discard the first colorbar, to get an even layout:
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.axes_grid1 import make_axes_locatable
fig = plt.figure(figsize=(12, 6))
marker_colors = range(0,10)
x = x1 = np.random.randint(0,10,10)
y = y1 = np.random.randint(0,10,10)
ax1 = fig.add_subplot(1, 2, 1, aspect="equal") # or e.g. aspect=0.9
g1 = ax1.scatter(x, y, c = marker_colors, cmap = "inferno", )
ax2 = fig.add_subplot(1, 2, 2, aspect="equal") # or e.g. aspect=0.9
g2 = ax2.scatter(x1, y1, c = marker_colors, cmap = "inferno")
# put colorbars right next to axes
divider1 = make_axes_locatable(ax1)
cax1 = divider1.append_axes("right", size="5%", pad=0.05)
divider2 = make_axes_locatable(ax2)
cax2 = divider2.append_axes("right", size="5%", pad=0.05)
# reserve space for 1st colorbar, then remove
cbar1 = fig.colorbar(g1, cax=cax1)
fig.delaxes(fig.axes[2])
# 2nd colorbar
cbar2 = fig.colorbar(g2, cax=cax2)
plt.tight_layout()
plt.show()
If you want a different aspect ratio, you can modify aspect, e.g. to aspect=0.9. The result will have locked aspect ratios for the subplots, even if you resize the figure box:
use following code:
Hope it will match your problem statment.
fig = plt.figure(figsize=(12, 6))
marker_colors = range(0,10)
x=x1=np.random.randint(0,10,10)
y=y1=np.random.randint(0,10,10)
plt.subplot(1, 2, 1)
g1=plt.scatter(x, y, c = marker_colors, cmap = "inferno")
plt.subplot(1, 2, 2)
g2=plt.scatter(x1, y1, c = marker_colors, cmap = "inferno")
g11=plt.colorbar(g1)
g12=plt.colorbar(g2)
g11.ax.set_title('g1')
g12.ax.set_title('g2')

Placing multiple histograms in a stack with matplotlib

I am trying to place multiple histograms in a vertical stack. I am able to get the plots in a stack but then all the histograms are in the same graph.
fig, ax = plt.subplots(2, 1, sharex=True, figsize=(20, 18))
n = 2
axes = ax.flatten()
for i, j in zip(range(n), axes):
plt.hist(np.array(dates[i]), bins=20, alpha=0.3)
plt.show()
You have a 2x1 grid of axis objects. By directly looping over axes.flatten(), you are accessing one subplot at a time. You then need to use the corresponding axis instance for plotting the histogram.
fig, axes = plt.subplots(2, 1, sharex=True, figsize=(20, 18)) # axes here
n = 2
for i, ax in zip(range(n), axes.flatten()): # <--- flatten directly in the zip
ax.hist(np.array(dates[i]), bins=20, alpha=0.3)
Your original version was also correct except the fact that instead of plt, you just should have used the correct variable i.e. j instead of plt
for i, j in zip(range(n), axes):
j.hist(np.array(dates[i]), bins=20, alpha=0.3)

Adding 'unorthodox' axes labels to a pyplot

I found an excellent tutorial on drawing a heatmap for a confusion matrix, but I want to add some errors of commission and omission on the sides.
I'll try to explain using this image:
This means:
I need to insert a number beside each of the boxes containing 0, 6, and 9 just right of the right edge of the image, and to the left of the legend
I need to insert a number above the each of boxes containing 13, 0 and 0 just above the top edge of the image, just below the title.
(so 6 numbers in total)
Is this even possible? I know nothing about the plotting functions in Python, as I'm new to the language. It just seems like a very difficult task from where I'm standing.
Use the following modified function. The idea is following:
Add two twin axes - one to the right and other to the top.
Set the limits of the twin axes equal to that of the original axes
Set the positions of the ticks on the twin axes to be the same as that of the original axes
Hide the tick marks and assign the tick-labels
Shift the title a bit upward using y=1.1
def plot_confusion_matrix(y_true, y_pred, classes, normalize=False,
title=None, cmap=plt.cm.Blues):
if not title:
if normalize:
title = 'Normalized confusion matrix'
else:
title = 'Confusion matrix, without normalization'
cm = confusion_matrix(y_true, y_pred)
classes = classes[unique_labels(y_true, y_pred)]
if normalize:
cm = cm.astype('float') / cm.sum(axis=1)[:, np.newaxis]
print("Normalized confusion matrix")
else:
print('Confusion matrix, without normalization')
fig, ax = plt.subplots(figsize=(6.5,6))
im = ax.imshow(cm, interpolation='nearest', cmap=cmap)
ax.figure.colorbar(im, ax=ax)
ax.set(xticks=np.arange(cm.shape[1]),
yticks=np.arange(cm.shape[0]),
xticklabels=classes, yticklabels=classes,
ylabel='True label',
xlabel='Predicted label')
ax.set_title(title, y=1.1)
plt.setp(ax.get_xticklabels(), rotation=45, ha="right",
rotation_mode="anchor")
# Adding data to the right
ax2 = ax.twinx()
ax2.set_ylim(ax.get_ylim())
ax2.set_yticks(np.arange(cm.shape[0]))
ax2.set_yticklabels(cm[:, -1])
ax2.tick_params(axis="y", right=False)
# Adding data to the top
ax3 = ax.twiny()
ax3.set_xlim(ax.get_xlim())
ax3.set_xticks(np.arange(cm.shape[0]))
ax3.set_xticklabels(cm[:, 0])
ax3.tick_params(axis="x", top=False)
ax.set_aspect('auto')
fmt = '.2f' if normalize else 'd'
thresh = cm.max() / 2.
for i in range(cm.shape[0]):
for j in range(cm.shape[1]):
ax.text(j, i, format(cm[i, j], fmt),
ha="center", va="center",
color="white" if cm[i, j] > thresh else "black")
fig.tight_layout()
return ax
You could do this using ticks.
Let me present this approach with the following easy plot:
from matplotlib import pyplot as plt
ax = plt.axes()
ax.set_xlim(0, 3)
ax.set_ylim(0, 3)
for i in range(3):
for j in range(3):
ax.fill_between((i, i+1), j, j+1)
ax.fill_between((i, i+1), j, j+1)
ax.fill_between((i, i+1), j, j+1)
plt.show()
I will not focus on the colors neither on the tick style, but know that you can change these very easily.
You can create an Axes object that will share ax's Y axis, with ax.twiny(). Then, you can add X ticks on this new Axes, which will appear on top of the plot:
from matplotlib import pyplot as plt
ax = plt.axes()
ax.set_xlim(0, 3)
ax.set_ylim(0, 3)
for i in range(3):
for j in range(3):
ax.fill_between((i, i+1), j, j+1)
ax.fill_between((i, i+1), j, j+1)
ax.fill_between((i, i+1), j, j+1)
ax2 = ax.twiny()
ax2.set_xlim(ax.get_xlim())
ax2.set_xticks([0.5, 1.5, 2.5])
ax2.set_xticklabels([13, 0, 0])
plt.show()
In order to display ticks for the X axis, you have to create an Axes object that shares ax's Y axis, with ax.twiny(). This might seem counter-intuitive, but if you used ax.twinx() instead, then modifying ax2's X ticks would modify ax's as well, because they're actually the same.
Then, you want to set the X window of ax2, so that it has three squares.
After that, you can set the ticks: one in every square, at the horizontal center, so at [0.5, 1.5, 2.5].
Finally, you can set the tick labels to display the desired value.
Then, you just do the same with the Y ticks:
from matplotlib import pyplot as plt
ax = plt.axes()
ax.set_xlim(0, 3)
ax.set_ylim(0, 3)
for i in range(3):
for j in range(3):
ax.fill_between((i, i+1), j, j+1)
ax.fill_between((i, i+1), j, j+1)
ax.fill_between((i, i+1), j, j+1)
ax2 = ax.twiny()
ax2.set_xlim(ax.get_xlim())
ax2.set_xticks([0.5, 1.5, 2.5])
ax2.set_xticklabels([13, 0, 0])
ax3 = ax.twinx()
ax3.set_ylim(ax.get_ylim())
ax3.set_yticks([0.5, 1.5, 2.5])
ax3.set_yticklabels([0, 6, 9])
plt.show()
A rather manual approach would consist of combining the following items until the result is satisfactory:
using twinx and twiny to get new axes on the top and on the right: twinax = ax.twinx().twiny()
using twinax.set(xlim=ax.get_xlim(), ylim=ax.get_ylim()) to match their range with the range of the original axes, then...
using twinax.set(xticks=ax.get_xticks(), yticks=ax.get_yticks, xticklabels=('0','1','2'), yticklabels = ('0','1','2')) to set the labels on the new axes as was done in your example (these two calls can be combined if you like).
(If you don't want the actual ticks (only the labels) you can give them 0 length through tick_params.)
You can reposition the axes with set_position.
See this question for info on how to move the colorbar.

Matplotlib Gridspec "Cell" labels

Using GridSpec, I have a regular lattice of plots. Assume 3 x 3. All plot axis are turned off as I am interested in the shape of the plot, not the individual axis values.
What I would like to do, is label the x and y axis of the larger box. For example, in the 3 x 3 case above, the x-axis could be ['A', 'B', 'C'] and the y-axis could be [1,2,3].
Is it possible to get this labeling? How can I access the grid spec axis?
Not much in the GridSpec documentation unless I am missing an obvious method name.
Code example. Data is in a pandas dataframe - ignore the brute force extraction with nested loops...
fig = plt.figure(figsize=(12,12))
gs = gridspec.GridSpec(40, 19, wspace=0.0, hspace=0.0)
for j in nseasons:
t = tt[j]
nlats = t.columns.levels[0]
for idx, k in enumerate(nlats):
diurnal = t[k].iloc[0]
ax = plt.subplot(gs[j, idx])
ax.plot(y, diurnal.values, 'b-')
ax.set_xticks([])
ax.set_yticks([])
fig.add_subplot(ax)
sys.stdout.write("Processed plot {}/{}\r".format(cplots, nplots))
sys.stdout.flush()
cplots += 1
#Here the figures axis labels need to be set.
As mentioned in the comments, you can do this with xlabel and ylabel by only labeling axes on the left and bottom of the figure. Example below.
from matplotlib import pyplot as plt
import matplotlib.gridspec as gridspec
fig = plt.figure(figsize=(12,12))
rows = 40
cols = 19
gs = gridspec.GridSpec(rows, cols, wspace=0.0, hspace=0.0)
for i in range(rows):
for j in range(cols):
ax = plt.subplot(gs[i, j])
ax.set_xticks([])
ax.set_yticks([])
# label y
if ax.is_first_col():
ax.set_ylabel(i, fontsize = 9)
# label x
if ax.is_last_row():
ax.set_xlabel(j, fontsize = 9)
plt.show()

Categories