Search code examples
pythonmatplotliblegendlegend-properties

How to put legend outside the plot for a for 2 column plot definition?


enter image description hereI am trying to plot the legend outside a 2 columns 5 rows, but in this MWE 2 rows example in matplotlib. Turns out the legend to "lower center" puts the space between the two plots as well, which is undesirable. Would anyone have any suggestions to correct this? Ideally default space between the two columns is desired. (have looked at various examples which are a single plot exmamples, moving around bbox_to_anchor, none exists for 2 column ones, atleast to the best of my knowledge)

import numpy as np
import matplotlib.pyplot as plt
[![enter image description here][2]][2]
r2x =np.arange(1,10,1)
rmx =np.arange(1,10,1)

a0_r2_gp = np.random.rand(9)
a0_r2_mf = np.random.rand(9)
a0_r2_mo = np.random.rand(9)

a0_rmse_gp = np.random.randint(0, 60, 9)
a0_rmse_mf = np.random.randint(0, 50, 9)
a0_rmse_mo = np.random.randint(0, 40, 9)

a1_r2_gp = np.random.rand(9)
a1_r2_mf = np.random.rand(9)
a1_r2_mo = np.random.rand(9)

a1_rmse_gp = np.random.randint(0, 30, 9)
a1_rmse_mf =np.random.randint(0, 20, 9)
a1_rmse_mo =np.random.randint(0, 15, 9)

fig, axs = plt.subplots(2, 2, figsize=(8, 12) )
axs[0,0].plot(r2x, np.asarray(a0_r2_gp), marker='o', c = gp_color, label='GP')
axs[0,0].plot(r2x, np.asarray(a0_r2_mf), marker='o', c = mf_color, label='MF')
axs[0,0].plot(r2x, np.asarray(a0_r2_mo), marker='o', c = mo_color, label='MO')
axs[0,0].set_xticks(r2x)
axs[0,0].set_yticks(np.linspace(0,1,11))
axs[0,0].set_ylim(0, 1)
axs[0,0].set_ylabel("r\u00b2, 1st case", fontsize=textfs)

axs[0,1].plot(rmx, np.asarray(a0_rmse_gp), marker='o', c = gp_color, label='GP')
axs[0,1].plot(rmx, np.asarray(a0_rmse_mf), marker='o', c = mf_color, label='MF')
axs[0,1].plot(rmx, np.asarray(a0_rmse_mo), marker='o', c = mo_color, label='MO')
axs[0,1].set_yticks(np.arange(10, 65, 5))
axs[0,1].set_xticks(rmx)
axs[0,1].set_ylim(10, 65)
axs[0,1].set_ylabel("%rmse", fontsize=textfs)
fig.suptitle("r\u00b2 and %rmse averages for Tubes", fontsize=textfs)

axs[1,0].plot(r2x, np.asarray(a1_r2_gp), marker='o', c = gp_color, label='GP')
axs[1,0].plot(r2x, np.asarray(a1_r2_mf), marker='o', c = mf_color, label='MF')
axs[1,0].plot(r2x, np.asarray(a1_r2_mo), marker='o', c = mo_color, label='MO')
axs[1,0].set_xticks(r2x)
axs[1,0].set_yticks(np.linspace(0,1,11))
axs[1,0].set_ylim(0, 1)
axs[1,0].set_ylabel("r\u00b2, 2nd case", fontsize=textfs)

axs[1,1].plot(rmx, np.asarray(a1_rmse_gp), marker='o', c = gp_color, label='GP')
axs[1,1].plot(rmx, np.asarray(a1_rmse_mf), marker='o', c = mf_color, label='MF')
axs[1,1].plot(rmx, np.asarray(a1_rmse_mo), marker='o', c = mo_color, label='MO')
axs[1,1].set_yticks(np.arange(6, 30, 2))
axs[1,1].set_xticks(rmx)
axs[1,1].set_ylim(6, 30)
axs[1,1].set_ylabel("%rmse", fontsize=textfs)

plt.legend(bbox_to_anchor=(0.5, 0), loc="lower center", bbox_transform=fig.transFigure, ncol=3)
plt.tight_layout()
plt.show()

Solution

    1. Use constrained layout rather than tight_layout.
    2. Use a figure legend rather than plt.legend (which is a wrapper to ax.legend) and set the loc to "outside lower center".
    3. Only set the labels within one of the axes, otherwise the legend entries will be repeated.

    See also https://matplotlib.org/stable/tutorials/intermediate/legend_guide.html#figure-legends

    import numpy as np
    import matplotlib.pyplot as plt
    
    r2x =np.arange(1,10,1)
    rmx =np.arange(1,10,1)
    
    a0_r2_gp = np.random.rand(9)
    a0_r2_mf = np.random.rand(9)
    a0_r2_mo = np.random.rand(9)
    
    a0_rmse_gp = np.random.randint(0, 60, 9)
    a0_rmse_mf = np.random.randint(0, 50, 9)
    a0_rmse_mo = np.random.randint(0, 40, 9)
    
    a1_r2_gp = np.random.rand(9)
    a1_r2_mf = np.random.rand(9)
    a1_r2_mo = np.random.rand(9)
    
    a1_rmse_gp = np.random.randint(0, 30, 9)
    a1_rmse_mf =np.random.randint(0, 20, 9)
    a1_rmse_mo =np.random.randint(0, 15, 9)
    
    fig, axs = plt.subplots(2, 2, figsize=(8, 12), layout='constrained' )
    axs[0,0].plot(r2x, np.asarray(a0_r2_gp), marker='o', label='GP')
    axs[0,0].plot(r2x, np.asarray(a0_r2_mf), marker='o', label='MF')
    axs[0,0].plot(r2x, np.asarray(a0_r2_mo), marker='o', label='MO')
    axs[0,0].set_xticks(r2x)
    axs[0,0].set_yticks(np.linspace(0,1,11))
    axs[0,0].set_ylim(0, 1)
    axs[0,0].set_ylabel("r\u00b2, 1st case")
    
    axs[0,1].plot(rmx, np.asarray(a0_rmse_gp), marker='o')
    axs[0,1].plot(rmx, np.asarray(a0_rmse_mf), marker='o')
    axs[0,1].plot(rmx, np.asarray(a0_rmse_mo), marker='o')
    axs[0,1].set_yticks(np.arange(10, 65, 5))
    axs[0,1].set_xticks(rmx)
    axs[0,1].set_ylim(10, 65)
    axs[0,1].set_ylabel("%rmse")
    fig.suptitle("r\u00b2 and %rmse averages for Tubes")
    
    axs[1,0].plot(r2x, np.asarray(a1_r2_gp), marker='o')
    axs[1,0].plot(r2x, np.asarray(a1_r2_mf), marker='o')
    axs[1,0].plot(r2x, np.asarray(a1_r2_mo), marker='o')
    axs[1,0].set_xticks(r2x)
    axs[1,0].set_yticks(np.linspace(0,1,11))
    axs[1,0].set_ylim(0, 1)
    axs[1,0].set_ylabel("r\u00b2, 2nd case")
    
    axs[1,1].plot(rmx, np.asarray(a1_rmse_gp), marker='o')
    axs[1,1].plot(rmx, np.asarray(a1_rmse_mf), marker='o')
    axs[1,1].plot(rmx, np.asarray(a1_rmse_mo), marker='o')
    axs[1,1].set_yticks(np.arange(6, 30, 2))
    axs[1,1].set_xticks(rmx)
    axs[1,1].set_ylim(6, 30)
    axs[1,1].set_ylabel("%rmse")
    
    fig.legend(loc="outside lower center", ncol=3)
    plt.show()
    

    enter image description here