agold
agold

Reputation: 6276

How to put the legend on first subplot of seaborn.FacetGrid?

I have a pandas DataFrame df which I visualize with subplots of a seaborn.barplot. My problem is that I want to move my legend inside one of the subplots.

To create subplots based on a condition (in my case Area), I use seaborn.FacetGrid. This is the code I use:

import matplotlib.pyplot as plt
import matplotlib
import seaborn as sns
# .. load data
grid = sns.FacetGrid(df, col="Area", col_order=['F1','F2','F3'])
bp = grid.map(sns.barplot,'Param','Time','Method')
bp.add_legend()
bp.set_titles("{col_name}")
bp.set_ylabels("Time (s)")
bp.set_xlabels("Number")
sns.plt.show()

Which generates this plot:

barplot-all

You see that the legend here is totally at the right, but I would like to have it inside one of the plots (for example the left one) since my original data labels are quite long and the legend occupies too much space. This is the example for only 1 plot where the legend is inside the plot:

barplot1

and the code:

mask = df['Area']=='F3'
ax=sns.barplot(x='Param',y='Time',hue='Method',data=df[mask])
sns.plt.show()

Test 1: I tried the example of an answer where they have the legend in one of the subplots:

grid = sns.FacetGrid(df, col="Area", col_order=['F1','F2','F3'])
bp = grid.map(sns.barplot,'Param','Time','Method')
Ax = bp.axes[0]
Boxes = [item for item in Ax.get_children()
     if isinstance(item, matplotlib.patches.Rectangle)][:-1]
legend_labels  = ['So1', 'So2', 'So3', 'So4', 'So5']
# Create the legend patches
legend_patches = [matplotlib.patches.Patch(color=C, label=L) for
              C, L in zip([item.get_facecolor() for item in Boxes],
                          legend_labels)]

# Plot the legend
plt.legend(legend_patches)    
sns.plt.show()

Note that I changed plt.legend(handles=legend_patches) did not work for me therefore I use plt.legend(legend_patches) as commented in this answer. The result however is:

bar test with legend in 3rd

As you see the legend is in the third subplot and neither the colors nor labels match.


Test 2:

Finally I tried to create a subplot with a column wrap of 2 (col_wrap=2) with the idea of having the legend in the right-bottom square:

grid = sns.FacetGrid(df, col="MapPubName", col_order=['F1','F2','F3'],col_wrap=2)

but this also results in the legend being at the right:

barplot 2cols


Question: How can I get the legend inside the first subplot? Or how can I move the legend to anywhere in the grid?

Upvotes: 6

Views: 19390

Answers (2)

mwaskom
mwaskom

Reputation: 49002

  1. Use the legend_out=False option.

  2. If you are making a faceted bar plot, you should use factorplot with kind=bar. Otherwise, if you don't explicitly specify the order for each facet, it is possible that your plot will end up being wrong.

    import seaborn as sns
    tips = sns.load_dataset("tips")
    sns.factorplot(x="sex", y="total_bill", hue="smoker", col="day",
                   data=tips, kind="bar", aspect=.7, legend_out=False)
    

enter image description here

Upvotes: 5

tmdavison
tmdavison

Reputation: 69116

You can set the legend on the specific axes you want, by using grid.axes[i][j].legend()

For your case of a 1 row, 3 column grid, you want to set grid.axes[0][0].legend() to plot on the left hand side.

Here's a simple example derived from your code, but changed to account for the sample dataset.

import matplotlib.pyplot as plt
import matplotlib
import seaborn as sns

df = sns.load_dataset("tips")

grid = sns.FacetGrid(df, col="day")
bp = grid.map(sns.barplot,"time",'total_bill','sex')

grid.axes[0][0].legend()

bp.set_titles("{col_name}")
bp.set_ylabels("Time (s)")
bp.set_xlabels("Number")
sns.plt.show()

enter image description here

Upvotes: 8

Related Questions