Adjusting space between gridspec subplots for better alignment

Question:

I’m using matplotlib.gridspec to create a grid for 3 axes.

My current code looks like:

from matplotlib import pyplot as plt
from matplotlib.gridspec import GridSpec
import pandas as pd

df = pd.DataFrame({'d1': [20,30,40], 'd2': [10,20,30]}, index=['a', 'b', 'c']) #  Dataset to emulate real data structure
df = df.apply(lambda l: l / l.sum() * 100).T
df.index = ['Some dataset', 'Some dataset 2']

fig = plt.figure(figsize=(6.25, 4.0))
gs = GridSpec(3, 2, figure=fig)

ax1 = fig.add_subplot(gs[:2, 0])
ax2 = fig.add_subplot(gs[:2, 1])
hbar_ax = fig.add_subplot(gs[2, :])

df.plot(ax=hbar_ax, kind='barh', stacked=True, width=0.5, color=['#FF3B3B', '#F4B083', '#9CD5A4'], legend=None)
hbar_ax.invert_yaxis()
hbar_ax.set_xlim(-1, 101)

for n in df:
    for i, (cs, ab, pc) in enumerate(zip(df.iloc[:, 0:].cumsum(1)[n], 
                                         df[n], df[n])):
        hbar_ax.text(cs - ab / 2, i, str(np.round(pc, 1)) + '%', 
                 va = 'center', ha = 'center', fontsize='medium')


hbar_ax.tick_params(axis='both', which='both', length=0)
hbar_ax.tick_params(
    axis='x',
    which='both',
    bottom=False,
    top=False,
    labelbottom=False)

plt.tight_layout()

Which produces this figure:

enter image description here

But i cant figure out how to adjust the top plots to space out to look like this:

enter image description here

Asked By: Loxx

||

Answers:

You can use subfigures:

import numpy as np
from matplotlib import pyplot as plt
from matplotlib.gridspec import GridSpec
import pandas as pd

df = pd.DataFrame({'d1': [20,30,40], 'd2': [10,20,30]}, index=['a', 'b', 'c']) #  Dataset to emulate real data structure
df = df.apply(lambda l: l / l.sum() * 100).T
df.index = ['Some dataset', 'Some dataset 2']

# create subplot grid (using wpace to set space between top two plots)
fig, axs = plt.subplots(3, 2, layout="constrained", figsize=(6.25, 4.0), gridspec_kw={"wspace": 0.2})
gridspec = axs[0, 0].get_subplotspec().get_gridspec()

# add in top two plots (first removing existing axes)
for ax in axs[:2, 0]:
    ax.remove()
for ax in axs[:2, 1]:
    ax.remove()
ax1 = fig.add_subplot(gridspec[:2, 0])
ax2 = fig.add_subplot(gridspec[:2, 1])

# create subfigure for bottom plot
for ax in axs[2, :]:
    ax.remove()

subfig = fig.add_subfigure(gridspec[2, :])

# bottom axis
hbar_ax = subfig.subplots(1)

df.plot(ax=hbar_ax, kind='barh', stacked=True, width=0.5, color=['#FF3B3B', '#F4B083', '#9CD5A4'], legend=None)
hbar_ax.invert_yaxis()
hbar_ax.set_xlim(-1, 101)

for n in df:
    for i, (cs, ab, pc) in enumerate(zip(df.iloc[:, 0:].cumsum(1)[n],
                                         df[n], df[n])):
        hbar_ax.text(cs - ab / 2, i, str(np.round(pc, 1)) + '%',
                 va = 'center', ha = 'center', fontsize='medium')

hbar_ax.tick_params(axis='both', which='both', length=0)
hbar_ax.tick_params(
    axis='x',
    which='both',
    bottom=False,
    top=False,
    labelbottom=False)

fig.show()

This gives:

enter image description here

Answered By: Matt Pitkin

This answer is correct, and subfigures is the best approach. However, the following implementation is more idiomatic:

import matplotlib.pyplot as plt
import numpy as np
import matplotlib as mpl

import pandas as pd

df = pd.DataFrame({'d1': [20,30,40], 'd2': [10,20,30]}, index=['a', 'b', 'c'])
df = df.apply(lambda l: l / l.sum() * 100).T
df.index = ['Some dataset', 'Some dataset 2']


fig = plt.figure(layout='constrained', figsize=(6.25, 4.0))
subfigs = fig.subfigures(nrows=2, ncols=1, height_ratios=[1, 0.5])

axs = subfigs[0].subplots(1, 2, gridspec_kw={"wspace": 0.2})

hbar_ax = subfigs[1].subplots()

df.plot(ax=hbar_ax, kind='barh', stacked=True, width=0.5, color=['#FF3B3B', '#F4B083', '#9CD5A4'], legend=None)
hbar_ax.invert_yaxis()
hbar_ax.set_xlim(-1, 101)

enter image description here

Answered By: Jody Klymak