Python – Seaborn FacetGrid – Places a single color bar after the last subgraph

Seaborn FacetGrid – Places a single color bar after the last subgraph… here is a solution to the problem.

Seaborn FacetGrid – Places a single color bar after the last subgraph

I’m trying to add color bars to the grid of 3 seaborn plots. I can add color bars to 3 separate plots or squeeze a color bar next to a third plot. I want to have a color bar after the third plot without changing the size of the last plot.

I got a lot of good ideas from this answer but couldn’t solve my exact problem: SO Question/Answer

Here is my current code :

import seaborn as sns

def masked_vs_unmasked_facets(output_dir, merged_df, target_col, thresholds):
    # defining the maximal values, to make the plot square
    z_min = merged_df[['z_full', 'z_masked']].min(axis=0, skipna=True).min(skipna=True)
    z_max = merged_df[['z_full', 'z_masked']].max(axis=0, skipna=True).max(skipna=True)
    z_range_value = max(abs(z_min), abs(z_max))

# Setting the column values to create the facet grid
    for i, val in enumerate(thresholds):
        merged_df.loc[merged_df.info_score_masked > val, 'PlotSet'] = i

# Start the actual plots
    g = sns. FacetGrid(merged_df, col='PlotSet', size=8)

def facet_scatter(x, y, c, **kwargs):
        kwargs.pop("color")
        plt.scatter(x, y, c=c, **kwargs)
        # plt.colorbar() for multiple colourbars

vmin, vmax = 0, 1
    norm=plt. Normalize(vmin=vmin, vmax=vmax)

g = (g.map(facet_scatter, 'z_full', 'z_masked', 'info_score_masked', norm=norm, cmap='viridis'))

ax = g.axes[0]
    for ax in ax:
        ax.set_xlim([-z_range_value * 1.1, z_range_value * 1.1])
        ax.set_ylim([-z_range_value * 1.1, z_range_value * 1.1])
        ax.plot(ax.get_xlim(), ax.get_ylim(), ls="--", c=".3")

plt.colorbar() # Single squashed colorbar
    plt.show()

masked_vs_unmasked_facets(output_dir, masking_results, 'info_score_masked', [0, 0.7, 0.9])

Single color bar, but the 3rd figure is flattened
Single colorbar
Multiple color bars, but crowded
Multi colorbar

Solution

Following @ImportanceOfBeingEarnest’s suggestion, I found that I needed to add another set of axes to the polygon grid and then assign those axes to the color bar. To save this extra element to the drawing, I used bbox_extra_artist kwarg as a tight bounding box. Another small addition is a small clause for capturing edge cases where one aspect of me has no data. In this case, I append a blank row with one category instance, so each category always has at least 1 row.

import seaborn as sns

def masked_vs_unmasked_facets(output_dir, merged_df, target_col, thresholds):
    z_min = merged_df[['z_full', 'z_masked']].min(axis=0, skipna=True).min(skipna=True)
    z_max = merged_df[['z_full', 'z_masked']].max(axis=0, skipna=True).max(skipna=True)
    z_range_value = max(abs(z_min), abs(z_max))

for i, val in enumerate(thresholds):
        merged_df.loc[merged_df.info_score_masked > val, 'PlotSet'] = i
        # Catch instances where there are no values in category, to ensure all facets are drawn each time
        if i not in merged_df['PlotSet'].unique():
            dummy_row = pd. DataFrame(columns=merged_df.columns, data={'PlotSet': [i]})
            merged_df = merged_df.append(dummy_row)

g = sns. FacetGrid(merged_df, col='PlotSet', size=8)

def facet_scatter(x, y, c, **kwargs):
        kwargs.pop("color")
        plt.scatter(x, y, c=c, **kwargs)

vmin, vmax = 0, 1
    norm=plt. Normalize(vmin=vmin, vmax=vmax)

g = (g.map(facet_scatter, 'z_full', 'z_masked', 'info_score_masked', norm=norm, cmap='viridis'))

titles = ["Correlation for all masked / unmasked z-score with {} above {}".format(target_col, threshold) for threshold in thresholds]

axs = g.axes.flatten()
    for i, ax in enumerate(axs):
        ax.set_title(titles[i])
        ax.set_xlim([-z_range_value * 1.1, z_range_value * 1.1])
        ax.set_ylim([-z_range_value * 1.1, z_range_value * 1.1])
        ax.plot(ax.get_xlim(), ax.get_ylim(), ls="--", c=".3")

cbar_ax = g.fig.add_axes([1.015,0.13, 0.015, 0.8])
    plt.colorbar(cax=cbar_ax)
    # extra_artists used here
    plt.savefig(os.path.join(output_dir, 'masked_vs_unmasked_scatter_final.png'), bbox_extra_artists=(cbar_ax,),  bbox_inches='tight')

masked_vs_unmasked_facets(output_dir, masking_results, 'info_score_masked', [0, 0.7, 0.9])

This gave me:

Final_plot

Related Problems and Solutions