I'm hoping to adjust the space between subplots horizontally. Specifically between every second row. I can adjust every row using fig.subplots_adjust(hspace=n). But is it possible to apply this to every 2nd row?
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize = (10,10))
plt.style.use('ggplot')
ax.grid(False)
ax1 = plt.subplot2grid((5,2), (0, 0))
ax2 = plt.subplot2grid((5,2), (0, 1))
ax3 = plt.subplot2grid((5,2), (1, 0))
ax4 = plt.subplot2grid((5,2), (1, 1))
ax5 = plt.subplot2grid((5,2), (2, 0))
ax6 = plt.subplot2grid((5,2), (2, 1))
ax7 = plt.subplot2grid((5,2), (3, 0))
ax8 = plt.subplot2grid((5,2), (3, 1))
fig.subplots_adjust(hspace=0.9)
Using the subplots below I'm hoping to add a space between rows 2 and 3 and keep the rest as is.
You may interlace two grids such that there is a larger spacing between every second subplot.
To illustrate the concept:
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
n = 3 # number of double-rows
m = 2 # number of columns
t = 0.9 # 1-t == top space
b = 0.1 # bottom space (both in figure coordinates)
msp = 0.1 # minor spacing
sp = 0.5 # major spacing
offs=(1+msp)*(t-b)/(2*n+n*msp+(n-1)*sp) # grid offset
hspace = sp+msp+1 #height space per grid
gso = GridSpec(n,m, bottom=b+offs, top=t, hspace=hspace)
gse = GridSpec(n,m, bottom=b, top=t-offs, hspace=hspace)
fig = plt.figure()
axes = []
for i in range(n*m):
axes.append(fig.add_subplot(gso[i]))
axes.append(fig.add_subplot(gse[i]))
plt.show()
Without going to tedious low-level hacks like adjusting the position of the axes manually, I would suggest using a grid but just leaving some of the rows blank.
I tried this:
import matplotlib.pyplot as plt
plt.figure(figsize=(10., 10.))
num_rows = 6
num_cols = 2
row_height = 3
space_height = 2
num_sep_rows = lambda x: int((x-1)/2)
grid = (row_height*num_rows + space_height*num_sep_rows(num_rows), num_cols)
ax_list = []
for ind_row in range(num_rows):
for ind_col in range(num_cols):
grid_row = row_height*ind_row + space_height*num_sep_rows(ind_row+1)
grid_col = ind_col
ax_list += [plt.subplot2grid(grid, (grid_row, grid_col), rowspan=row_height)]
plt.subplots_adjust(bottom=.05, top=.95, hspace=.1)
# plot stuff
ax_list[0].plot([0, 1])
ax_list[1].plot([1, 0])
# ...
ax_list[11].plot([0, 1, 4], c='C2')
which gives this result:
Note that you can change the number of rows; also, you can adjust the size of the blank space compared to the subplots by tweaking the row_height/space_height ratio (both must be integers).
Here's a solution with getting into tedious low-level hacks:
import matplotlib.pyplot as plt
def tight_pairs(n_cols, fig=None):
"""
Stitch vertical pairs together.
Input:
- n_cols: number of columns in the figure
- fig: figure to be modified. If None, the current figure is used.
Assumptions:
- fig.axes should be ordered top to bottom (ascending row number).
So make sure the subplots have been added in this order.
- The upper-half's first subplot (column 0) should always be present
Effect:
- The spacing between vertical pairs is reduced to zero by moving all lower-half subplots up.
Returns:
- Modified fig
"""
if fig is None:
fig = plt.gcf()
for ax in fig.axes:
if hasattr(ax, 'get_subplotspec'):
ss = ax.get_subplotspec()
row, col = ss.num1 // n_cols, ss.num1 % n_cols
if (row % 2 == 0) and (col == 0): # upper-half row (first subplot)
y0_upper = ss.get_position(fig).y0
elif (row % 2 == 1): # lower-half row (all subplots)
x0_low, _ , width_low, height_low = ss.get_position(fig).bounds
ax.set_position(pos=[x0_low, y0_upper - height_low, width_low, height_low])
return fig
Here's a test for above function:
def test_tight_pairs():
def make_template(title):
fig = plt.figure(figsize=(8, 6))
for i in range(12):
plt.subplot(6, 2, i+1)
plt.plot([0,1], [0,1][::-1 if i%2==1 else 1])
fig.suptitle(title)
return fig
make_template("The vertical spacing should have increased (disappeared) between (within) pairs.")
tight_pairs(2)
make_template("Default spacing.")
plt.show()
test_tight_pairs()
Extra notes:
This will also work if some subplot pairs in the grid are missing, e.g. for a "lower triangle" arrangement of subplots.
To keep some distance between the pairs, you can add some padding via
y0_upper - height_low - padding, or
y0_upper - height_low - p * height_low
The labels and ticks on the y axis might need some fixing if they overlap.
Related
I am plotting separate figures for each attribute and label for each data sample. Here is the illustration:
As illustrated in the the last subplot (Label), my data contains seven classes (numerically) (0 to 6). I'd like to visualize these classes using a different fancy colors and a legend. Please note that I just want colors for last subplot. How should I do that?
Here is the code of above plot:
x, y = test_data["x"], test_data["y"]
# determine the total number of plots
n, off = x.shape[1] + 1, 0
plt.rcParams["figure.figsize"] = (40, 15)
# plot all the attributes
for i in range(6):
plt.subplot(n, 1, off + 1)
plt.plot(x[:, off])
plt.title('Attribute:' + str(i), y=0, loc='left')
off += 1
# plot Labels
plt.subplot(n, 1, n)
plt.plot(y)
plt.title('Label', y=0, loc='left')
plt.savefig(save_file_name, bbox_inches="tight")
plt.close()
First, just to set up a similar dataset:
import matplotlib.pyplot as plt
import numpy as np
x = np.random.random((100,6))
y = np.random.randint(0, 6, (100))
fig, axs = plt.subplots(6, figsize=(40,15))
We could use plt.scatter() to give individual points different marker styles:
for i in range(x.shape[-1]):
axs[i].scatter(range(x.shape[0]), x[:,i], c=y)
Or we could mask the arrays we're plotting:
for i in range(x.shape[-1]):
for j in np.unique(y):
axs[i].plot(np.ma.masked_where(y!=j, x[:,i]), 'o')
Either way we get the same results:
Edit: Ah you've edited your question! You can do exactly the same thing for your last plot only, just modify my code above to take it out of the loop of subplots :)
As suggested, we imitate the matplotlib step function by creating a LineCollection to color the different line segments:
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.collections import LineCollection
from matplotlib.patches import Patch
#random data generation
np.random.seed(12345)
number_of_categories=4
y = np.concatenate([np.repeat(np.random.randint(0, number_of_categories), np.random.randint(1, 30)) for _ in range(20)])
#check the results with less points
#y = y[:10]
x = y[None] * np.linspace(1, 5, 3)[:, None]
x += 2 * np.random.random(x.shape) - 1
#your initial plot
num_plots = x.shape[0] + 1
fig, axes = plt.subplots(num_plots, 1, sharex=True, figsize=(10, 8))
for i, ax in enumerate(axes.flat[:-1]):
ax.plot(x[i,:])
#first we create the matplotlib step function with x-values as their midpoint
axes.flat[-1].step(np.arange(y.size), y, where="mid", color="lightgrey", zorder=-1)
#then we plot colored segments with shifted index simulating the step function
shifted_x = np.arange(y.size+1)-0.5
#and identify the step indexes
idx_steps, = np.nonzero(np.diff(y, prepend=np.inf, append=np.inf))
#create collection of plateau segments
colored_segments = np.zeros((idx_steps.size-1, 2, 2))
colored_segments[:, :, 0] = np.vstack((shifted_x[idx_steps[:-1]], shifted_x[idx_steps[1:]])).T
colored_segments[:, :, 1] = np.repeat(y[idx_steps[:-1]], 2).reshape(-1, 2)
#generate discrete color list
n_levels, idx_levels = np.unique(y[idx_steps[:-1]], return_inverse=True)
colorarr = np.asarray(plt.cm.tab10.colors[:n_levels.size])
#and plot the colored segments
lc_cs = LineCollection(colored_segments, colors=colorarr[idx_levels, :], lw=10)
lines_cs = axes.flat[-1].add_collection(lc_cs)
#scaling and legend generation
axes.flat[-1].set_ylim(n_levels.min()-0.5, n_levels.max()+0.5)
axes.flat[-1].legend([Patch(color=colorarr[i, :]) for i, _ in enumerate(n_levels)],
[f"cat {i}" for i in n_levels],
loc="upper center", bbox_to_anchor=(0.5, -0.15),
ncol=n_levels.size)
plt.show()
Sample output:
Alternatively, you can use broken barh plots or color this axis or even all axes using axvspan.
I have configured subplots of (5 x 1) format shown in Fig. 1 as given by Figure block A in the MWE. I am trying to repeat them n times such that they appear in a grid format with number of rows and columns given by the function fitPlots as mentioned here; to give output as shown in Fig. 2.
Fig. 1 Initial plot
Fig. 2 Repeated plot (desired output)
What would be the best way to repeat the code block to create a grid plot with inner subplots? The MWE creates multiple pages, I want all of them on a single page.
MWE
from matplotlib.backends.backend_pdf import PdfPages
import matplotlib.pyplot as plt
import numpy as np
import math
x = np.arange(1, 100, 0.2)
y_a = np.sqrt(x)
y_b = np.sin(x)
y_c = np.sin(x)
y_d = np.cos(x) * np.cos(x)
y_e = 1/x
########## Figure block A #####################
with PdfPages('./plot_grid.pdf') as plot_grid_loop:
fig, (a, b, c, d, e) = plt.subplots(5, 1, sharex=True, gridspec_kw={'height_ratios': [5, 1, 1, 1, 1]})
a.plot(x, y_a)
b.plot(x, y_b)
c.plot(x, y_c)
d.plot(x, y_d)
e.plot(x, y_e)
plot_grid_loop.savefig()
plt.close
########## Figure block A #####################
# from https://stackoverflow.com/a/43366784/4576447
def fitPlots(N, aspect=(16,9)):
width = aspect[0]
height = aspect[1]
area = width*height*1.0
factor = (N/area)**(1/2.0)
cols = math.floor(width*factor)
rows = math.floor(height*factor)
rowFirst = width < height
while rows*cols < N:
if rowFirst:
rows += 1
else:
cols += 1
rowFirst = not(rowFirst)
return rows, cols
n_plots = 15
n_rows, n_cols = fitPlots(n_plots)
with PdfPages('./plot_grid.pdf') as plot_grid_loop:
for m in range(1, n_plots+1):
fig, (a, b, c, d, e) = plt.subplots(5, 1, sharex=True, gridspec_kw={'height_ratios': [5, 1, 1, 1, 1]})
a.plot(x, y_a)
b.plot(x, y_b)
c.plot(x, y_c)
d.plot(x, y_d)
e.plot(x, y_e)
plot_grid_loop.savefig()
plt.close
This can be done by generating a GridSpec object with gs_fig = fig.add_gridspec() that contains enough rows and columns to fit the five figure blocks (note that when you use plt.subplots a GridSpec is also generated and can be accessed with ax.get_gridspec()). Each empty slot in the GridSpec can then be filled with a sub-GridSpec with gs_sub = gs_fig[i].subgridspec() to hold the five subplots. The trickier part is sharing the x-axis. This can be done by generating an empty first Axes with which the x-axis of all the subplots can be shared.
The following example illustrates this with only three figure blocks, based on the code sample you have shared but with some differences regarding the figure design: the number of rows is computed based on the chosen number of columns, and the figure dimensions are set based on a chosen figure width and aspect ratio. The code for saving the figure to a pdf file is not included.
import numpy as np # v 1.19.2
import matplotlib.pyplot as plt # v 3.3.4
# Create variables to plot
x = np.arange(1, 100, 0.2)
y_a = np.sqrt(x)
y_b = np.sin(x)
y_c = np.sin(x)
y_d = np.cos(x)*np.cos(x)
y_e = 1/x
# Set parameters for figure dimensions
nplots = 3 # random number of plots for this example
ncols = 2
nrows = int(np.ceil(nplots/ncols))
subp_w = 10/ncols # 10 is the total figure width in inches
subp_h = 1*subp_w # set subplot aspect ratio
# Create figure containing GridSpec object with appropriate dimensions
fig = plt.figure(figsize=(ncols*subp_w, nrows*subp_h))
gs_fig = fig.add_gridspec(nrows, ncols)
# Loop through GridSpec to add sub-GridSpec for each figure block
heights = [5, 1, 1, 1, 1]
for i in range(nplots):
gs_sub = gs_fig[i].subgridspec(len(heights), 1, height_ratios=heights, hspace=0.2)
ax = fig.add_subplot(gs_sub[0, 0]) # generate first empty Axes to enable sharex
ax.axis('off') # remove x and y axes because it is overwritten in the loop below
# Loop through y variables to plot all the subplots with shared x-axis
for j, y in enumerate([y_a, y_b, y_c, y_d, y_e]):
ax = fig.add_subplot(gs_sub[j, 0], sharex=ax)
ax.plot(x, y)
if not ax.is_last_row():
ax.tick_params(labelbottom=False)
Reference: matplotlib tutorial GridSpec using SubplotSpec
images = wcs_request.get_data() # get image data
fig, axs = plt.subplots((len(images) + (6 - 1)) // 6, 6, figsize=(20, 20),
gridspec_kw={'hspace': 0.0, 'wspace': 0.0})
total = ((len(images) + (6 - 1)) // 6) * 6
for idx, (image, time) in enumerate(zip(images, wcs_request.get_dates())):
# Plot bbox
axs.flat[idx].imshow(image)
# Set title
axs.flat[idx].set_title(time.date().strftime("%d %B %Y"), fontsize=10, fontweight='bold')
# delete plots which have no data
for idx in range(len(images), total):
fig.delaxes(axs.flat[idx])
plt.suptitle(id, fontsize=12, fontweight='bold')
# fig.tight_layout(pad=0, h_pad=.1, w_pad=.1)
# fig.subplots_adjust(wspace=0, hspace=0)
plt.savefig(dir_out / f'{id}_map.png', dpi=300)
plt.close()
When I run the code above, I get a subplot with much larger vertical blank space than I want. How can I fix it? I already set wspace and hspace to 0.0
Well, there are many ways to generate a "nice" array of subplots; but assuming that your goal is to, e.g. create two rows of images where len(images)=10:
import matplotlib.pyplot as plt
images=range(10)
## assuming you want e.g. axes on your first row:
ncols = 6
# figure out how many plots will fall into the last row using modulo
ncols_last = (len(images) % ncols)
# and (if mod > 0 !) add one to the floor operation here:
nrows = (len(images) // ncols ) + (ncols_last > 0)
fig = plt.figure()
axes={}
for i in range(len(images)):
# note that for some reason, add_subplot() counts from 1, hence we use i+1 here
axes[i] = fig.add_subplot(nrows,ncols,i+1)
# add some content
for i,ax in axes.items():
ax.text(0,0,i)
ax.set_xlim(-1,1)
ax.set_ylim(-1,1)
plt.show()
Which should give you 6 plots on the first row and 4 on the second. You should be able to add your plot content like this:
for idx, (image, time) in enumerate(zip(images, wcs_request.get_dates())):
# Plot bbox
axes[idx].imshow(image)
# Set title
axes[idx].set_title(time.date().strftime("%d %B %Y"), fontsize=10, fontweight='bold')
Or alternatively, using gridspec in order to get access to further layout options:
import matplotlib.pyplot as plt
from matplotlib import gridspec
images=range(10)
ncols = 6
ncols_last = (len(images) % ncols)
nrows = (len(images) // ncols ) + (ncols_last > 0)
fig = plt.figure()
axes = {}
gs = gridspec.GridSpec(nrows, ncols,
left=0.1,right=.9,
bottom=0.1,top=.9,
wspace=0.25,hspace=0.3,
)
for i,(r,c) in enumerate([(r,c) for r in range(nrows) for c in range(ncols)]):
if i < len(images):
print(f"axes[{i}]: relates to the gridspec at index ({r},{c})")
axes[i] = fig.add_subplot(gs[r,c])
for i,ax in axes.items():
ax.text(0,0,i)
ax.set_xlim(-1,1)
ax.set_ylim(-1,1)
plt.show()
You may want to check out subplots_adjust, which let you specify:
The height of the padding between subplots, as a fraction of the average axes height.
fig, axs = plt.subplots(2,1)
fig.subplots_adjust(hspace=0.0)
So with hspace=0 there is no spacing at all:
I want to create a before-and-after plot of the three axis of my measurement system. This is close to what I want. However:
How do I have the "before" and "after" titles span subplot 1+2 and 4+5, respectively? (the tabs dont work as expected)
Like "before" and "after" should be above a column, i would like to have the "x-Axis", "y-Axis" etc infront of the row of graphs. How do I do that?
How do I join subplot 1+2 and 4+5 together, so that they touch? wspace=.0 doesnt work as expected.
How do I reduce the width in the middle, where subplot 3 would be, so that the other sides can take up more space?
How do I add some hspace between the fig.suptitle and the graphs?
How can I make my code more elegant? There is a lot of repetition.
from matplotlib.pyplot import figure
def plot_before_and_after(data_before, data_after, title):
shape = data_before.shape
sensor_num = shape[0]
n_start = 20 # number picked at random
N = 2 ** 12 # power of two is good
n_stop = n_start + N
p_stop = n_start + 40 # one periode #50Hz at the sampling rate
x_long = range(n_start, n_stop)
x_short = range(n_start, p_stop)
cmap = plt.get_cmap('jet_r')
axis_list = ['x', 'y', 'z']
fig = figure(num=None, figsize=(10, 10), dpi=80, facecolor='w', edgecolor='k')
fig.suptitle(title + "\nbefore \t\t\t\t\tafter")
plots = []
for axis_cnt in range(0, 3):
ax0 = plt.subplot(3, 5, axis_cnt * 5 + 1,
title="before, {}-Axis".format(axis_list[axis_cnt]))
for sensor_cnt in range(0, sensor_num):
color = cmap(float(sensor_cnt) / sensor_num)
plt.plot(x_long, data_before[sensor_cnt, n_start:n_stop, axis_cnt], color=color,
label="sensor" + str(sensor_cnt))
ax1 = plt.subplot(3, 5, axis_cnt * 5 + 2,
title="before, {}-Axis".format(axis_list[axis_cnt]),
sharey=ax0)
for sensor_cnt in range(0, sensor_num):
color = cmap(float(sensor_cnt) / sensor_num)
plt.plot(x_short, data_before[sensor_cnt, n_start:p_stop, axis_cnt], color=color,
label="sensor" + str(sensor_cnt))
plt.setp(ax1.get_yticklabels(), visible=False)
ax3 = plt.subplot(3, 5, axis_cnt * 5 + 4,
title="after, {}-Axis".format(axis_list[axis_cnt]))
for sensor_cnt in range(0, sensor_num):
color = cmap(float(sensor_cnt) / sensor_num)
plt.plot(x_long, data_after[sensor_cnt, n_start:n_stop, axis_cnt], color=color,
label="sensor" + str(sensor_cnt))
ax4 = plt.subplot(3, 5, axis_cnt * 5 + 5,
title="after, {}-Axis".format(axis_list[axis_cnt]),sharey=ax3)
for sensor_cnt in range(0, sensor_num):
color = cmap(float(sensor_cnt) / sensor_num)
plt.plot(x_short, data_after[sensor_cnt, n_start:p_stop, axis_cnt], color=color,
label="sensor" + str(sensor_cnt))
plt.setp(ax4.get_yticklabels(), visible=False)
plt.subplots_adjust(wspace=.0)
plt.legend()
plt.show()
Here's a preliminary answer that may help you. I used Matplotlib's GridSpec (see here for useful information) and the add_subplot method, both of which seem to be convenient in these cases. The gridspec is what allows us to create the two groups of subplots independently formatted; the add_subplot generates the individual axes.
Code
import matplotlib.pyplot as plt
nrow, ncol = 3, 2 # Number of rows and cols of gridspecs
lborder = [0.1, 0.6] # Left border coordinates of gridspecs
rborder = [0.45, 0.95] # Right border coordinates of gridspecs
tborder = 0.92 # Top border coordinate of gridspecs
gtitles = ['Before', 'After']
txt_axis = ['X-axis', 'Y-axis', 'Z-axis']
fig = plt.figure(figsize=(10, 10), dpi=80, facecolor='w', edgecolor='k')
for i in range(2):
gs = fig.add_gridspec(nrows=nrow, ncols=ncol, left=lborder[i],
right=rborder[i], top=tborder, hspace=0.45, wspace=0)
for j in range(nrow):
ax0 = fig.add_subplot(gs[j,0])
ax0.plot([1,2,3])
plt.text(-0.4, 0.5, txt_axis[j],
horizontalalignment='center',verticalalignment='center',
transform = ax0.transAxes, fontsize = 12)
if j == 0:
fig.text(1, 1.1, gtitles[i], fontsize=12, horizontalalignment =
'center', transform = ax0.transAxes)
for k in range(1, ncol):
ax1 = fig.add_subplot(gs[j,k], sharey = ax0)
plt.setp(ax1.get_yticklabels(), visible=False)
ax1.plot([1,2,3])
fig.suptitle('Figure title', fontsize = 14)
As for your questions:
I created the 'Before' and 'After' titles using text, based on this answer).
Same thing for the "-axis" text. Note that it will probably overlap with any axes label you write on the vertical axis. Also note that now we have to shift the left gridspec slightly to the right (via the leftargument of add_gridspec).
wspace can be introduced in add_gridspec too. I don't know why it doesn't work in your code.
For the space in between the 2 gridspecs, use the left and right arguments in the add_gridspec function.
The space between the main title and the subplots can be achieved via the top argument in add_gridspec.
Your inner loops seem very similar, perhaps you could define a function and save some lines of code. In my case, I tried to encase everything in a loop.
Hope it helps.
This question already has an answer here:
Set subplot(or gridspec) with same size
(1 answer)
Closed 3 years ago.
I am trying to plot 3 graphs in a single row, but all plots should be the same size (at least the same height).
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.gridspec as gridspec
x = np.random.uniform(size=2000) - 0.5
y = np.random.uniform(size=2000) - 0.5
z = np.random.uniform(size=2000) - 0.5
DPI = 106
fig = plt.figure(figsize=(900 / DPI, 350 / DPI))
gs = gridspec.GridSpec(1, 3, width_ratios=[1,1,1])
# 1D
r = np.abs(x)
# plot
ax1 = fig.add_subplot(gs[0])
plot = ax1.scatter(x, r, s = 1, c=r, cmap='jet', marker='.', alpha = 1, vmax = 0.5)
ax1.set_xlabel('$x$')
ax1.set_ylabel('$y$')
ax1.set_aspect('equal')
# 2D
r = np.sqrt(x * x + y * y)
# plot
ax2 = fig.add_subplot(gs[1])
plot = ax2.scatter(x, y, s = 1, c=r, cmap='jet', marker='.', alpha = 1, vmax = 0.5)
ax2.set_xlabel('$x$')
ax2.set_ylabel('$y$')
ax2.set_aspect('equal')
fig.colorbar(plot, shrink = 1, ax = ax2)
# 3D
r = np.sqrt(x * x + y * y + z * z)
ax3 = fig.add_subplot(gs[2], projection='3d')
plot = ax3.scatter(x, y, z, s = 10, c=r, cmap='jet', marker='.', alpha = 1, vmax = 0.5)
ax3.set_xlabel('$x$')
ax3.set_ylabel('$y$')
ax3.set_zlabel('$z$')
ax3.view_init(30, 240)
ax3.set_aspect('equal', 'box')
fig.colorbar(plot, shrink = 1,ax = ax3)
fig.tight_layout()
The code above does produce three columns of plots, first 1D, than 2D and lastly a 3D plot. However, as you can see from attached image, the plots are not of the same size eventhough I tried using gridspecas suggested here.
Any ideas on how to change the size of subplots?
Matplotlib.pyplot's auto-layout algorithm does not care if you are plotting a 3D object, 2D object or 1D (points). Of course the syntax for defining the object will change and a 3D object will take 3 parameters. But how the objects are placed in a row is not changed. I see a few possible reasons why your specific data may be causing you trouble.
The first image is 2D and has a smaller y-axis scale than the other 2 images. Also the 1st image x-axis scale is twice as wide as y-axis scale is tall. The 2nd and 3rd images include vertical colormaps, which makes these images taller in total.
1) You can change the 1st plot's y-axis to be taller than it currently is.
ax1.set_aspect('equal')
This code in your 1st plot is preventing you from changing the y-axis scale only. You can remove this line and manually set the scale of y-axis scale to be larger.
2) Make your entire row taller, so the vertical colormaps in 2nd and 3rd plots will not determine the overall height of the figure space. Set the figsize's x and y attribute to (12, 12) and see if that fixes the issue. The 2nd number in figsize sets height.
fig, axes = plt.subplots(nrows=1, ncols=3, figsize=(12, 12))
3) Alternatively, you can plot 1st graph in it's own 1st row, and 2nd and 3rd graphs in a separate 2nd row. Set nrows to 2 and ncols to 2, then add 1st plot to row 1 and col 1, and 2nd plot to row 2, col 1, and 3rd plot to row 2, col 2.
fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(12, 18))
You can refer to Matplotlib documentation for getting details setting layout parameters. Hope one of these will work. :-)
https://matplotlib.org/3.1.0/api/_as_gen/matplotlib.axes.Axes.set_aspect.html