pythonmatplotlibcartopy

Is there a way to individually control subplot sizes in matplotlib?


I need to combine imshow and regular line plots to create a figure. Unfortunately, the imshow plot area is smaller than the regular line plots, leading to something that looks like this:

fig = plt.figure(figsize=(25, 9))

map_subplots = [
    plt.subplot(2, 5, 1, projection=ccrs.PlateCarree()),
    plt.subplot(2, 5, 3, projection=ccrs.PlateCarree()),
    plt.subplot(2, 5, 6, projection=ccrs.PlateCarree()),
    plt.subplot(2, 5, 8, projection=ccrs.PlateCarree())
]


for i, ax in enumerate(map_subplots):
    ax.set_title(titles[i])
    ax.coastlines(linewidth=0.5)
    ax.imshow(eofs[i], extent=[0, 360, -90, 90], transform=ccrs.PlateCarree(), cmap='RdBu_r', origin='lower', vmin=-0.01, vmax=0.01)

ts_subplots = [
    plt.subplot(2, 5, 2),
    plt.subplot(2, 5, 4),
    plt.subplot(2, 5, 7),
    plt.subplot(2, 5, 9)
]


for i, ax in enumerate(ts_subplots):
    ax.plot(pcs.time, pcs.sel(pc=i))
    ax.set_ylim(-100, 100)

    if True:
        # remove yticks
        ax.set_yticks([])
    
    ax.set_xticks(pd.date_range("2013-01-01", "2016-01-01", freq='6MS'))
    ax.set_xticklabels(["2013", "Jul", "2014", "Jul", "2015", "Jul", "2016"])

Bad figure

I've got a hacky workaround with gridspec, that spreads the imshow plots over multiple subplots, so that it forces them to be bigger:

# Create a figure
fig = plt.figure(figsize=(15, 9))

# Define a gridspec with custom widths and heights for each subplot
hr = 0.6
wr = 0.7
gs = gridspec.GridSpec(6, 5, width_ratios=[1, wr, 1, wr, 1], height_ratios=[hr, 1, hr, hr, 1, hr])

# Map subplots
map_subplots = [
    plt.subplot(gs[:3, 0], projection=ccrs.PlateCarree()),
    plt.subplot(gs[:3, 2], projection=ccrs.PlateCarree()),
    plt.subplot(gs[3:, 0], projection=ccrs.PlateCarree()),
    plt.subplot(gs[3:, 2], projection=ccrs.PlateCarree())
]

# Plot the map subplots
for i, ax in enumerate(map_subplots):
    ax.set_title(titles[i])
    ax.coastlines(linewidth=0.5)
    ax.imshow(eofs[i], extent=[0, 360, -90, 90], transform=ccrs.PlateCarree(), cmap='RdBu_r', origin='lower', vmin=-0.01, vmax=0.01)

# Time series subplots
ts_subplots = [
    plt.subplot(gs[1, 1]),
    plt.subplot(gs[1, 3]),
    plt.subplot(gs[4, 1]),
    plt.subplot(gs[4, 3])
]

# Plot the time series subplots
for i, ax in enumerate(ts_subplots):
    ax.plot(pcs.time, pcs.sel(pc=i))
    ax.set_ylim(-100, 100)
    if True:
        # remove yticks
        ax.set_yticks([])
    
    ax.set_xticks(pd.date_range("2013-01-01", "2016-01-01", freq='6MS'))
    ax.set_xticklabels(["2013", "Jul", "2014", "Jul", "2015", "Jul", "2016"])

# fig.text(0.04, 0.5, 'PC value (arb. units)', va='center', rotation='vertical', fontsize=12)

plt.subplots_adjust(wspace=0.1, hspace=0)
plt.tight_layout()
plt.show()

enter image description here

This is very close to working, but there's a huge gap between the two rows, which I can't get rid of, no matter how negative a subplots_adjust parameter I supply. Any ideas on how to fix this?


Solution

  • This is what compressed layout is meant to handle (https://matplotlib.org/stable/users/explain/axes/constrainedlayout_guide.html#grids-of-fixed-aspect-ratio-axes-compressed-layout)

    Note that there is still blank space, but it is above and below the plots instead of between them. One fix for that is to either adjust the size of the figure manually, or to use bbox_inches='tight' when you save the figure.

    import matplotlib.pyplot as plt
    import numpy as np
    
    fig, axs = plt.subplots(2, 2, layout='compressed')
    
    axs[0, 0].imshow(np.random.rand(100, 400))
    axs[0, 1].plot(np.random.randn(500))
    axs[1, 0].imshow(np.random.rand(100, 400))
    axs[1, 1].plot(np.random.randn(500))
    
    plt.show()
    

    enter image description here