0

I'm trying to build a sns.FacetGrid plot using a custom function twin_lineplot(); but unable to figure out how to "join" the legend labels label1 and label2.

The below is just a simplified example to demonstrate my problem. I hope to achieve a solution that is scalable (multiple labels in both legends to be combined).

Given the dataset:

import pandas as pd
import numpy as np
from datetime import datetime, timedelta

date_today= datetime.now()
tips = sns.load_dataset("tips")
days = pd.date_range(date_today, date_today + timedelta(tips.shape[0]-1), freq='D')
tips['date'] = days

I would like to plot a sns.FacetGrid() plot with a legend. However, my attempt below splits the legend to inner plot marked as label2 and outer plot label1

def twin_lineplot(x,y,color,**kwargs):
    ax = plt.twinx()
    sns.lineplot(x=x,y=y,color=color,**kwargs, ax=ax)

g = sns.FacetGrid(tips, row='smoker', col='time')
g.map(sns.lineplot, 'date', 'tip', color='b', label='label1')
g.map(twin_lineplot, 'date', 'total_bill', color='g', label='label2')
g.add_legend()
g.fig.autofmt_xdate()
plt.show()

plot

How do I combine the two legend labels to be displayed in the outer legend (where label1 is currently shown)?

I would like to scale the solution to solve the below:

scaled solution

Carl Chang
  • 59
  • 1
  • 6

2 Answers2

0

You can basically use matplotlib child property.

Below code might help you.

g = sns.FacetGrid(tips,row='sex',hue='smoking_status',height=3,aspect=3, legend_out=True)
g = g.map(plt.scatter,'total_bill','tip').add_legend()
plt.show()

enter image description here

import seaborn as sns
import matplotlib.pyplot as plt

tips = sns.load_dataset('tips')

# more informative values
condition = tips['smoker'] == 'Yes'
tips['smoking_status'] = ''
tips.loc[condition,'smoking_status'] = 'Smoker'
tips.loc[~condition,'smoking_status'] = 'Non-Smoker'

g = sns.FacetGrid(tips,row='sex',hue='smoking_status',height=3,aspect=3)
g = g.map(plt.scatter,'total_bill','tip')
for ax in g.axes.flat:
    box = ax.get_position()
    ax.set_position([box.x0,box.y0,box.width*0.85,box.height])

plt.legend(loc='upper left',bbox_to_anchor=(1,0.5))
plt.show()

enter image description here

Jay Patel
  • 1,374
  • 1
  • 8
  • 17
  • This solution doesn't actually combine `label1` and `label2` together from the sample code in the question. It instead; moves `label2` to `label1` but they are not 'combined' – Carl Chang Jan 21 '21 at 01:06
0

The g.add_legend() can accept a legend_data dictionary containing the labels and matplotlib artist handles. By default, the FacetGrid object contains a ._legend_data that is used for add_legend().

I had a similar situation where I had two custom functions - one for the "main" plot and one for a "twinx" plot. In my case, the ._legend_data object only contained the label and handle for the main plot. I solved this issue by retrieving both the main and twinx handle/label pairs from the g.figure.axes object (not the g.axes object, which only contained the main plot handle/label pairs):

main_ax = g.figure.axes[0]
twin_ax = g.figure.axes[-1]
h1,l1 = main_ax.get_legend_handles_labels()
h2,l2 = twin_ax.get_legend_handles_labels()
legend_dict = dict(zip(l1+l2,h1+h2))
g.add_legend(legend_data=legend_dict)

Basically, I retrieved the first and last axes in g.figure.axes which correspond to the first main plot and last twinx plot. Finally, I built the dictionary using both label/handle pairs as shown above.

To move the legend to a different position see this answer.

Oliver Lopez
  • 220
  • 1
  • 8
  • I am not sure about whether this is already something that seaborn developers are improving or not.. see related issue here: https://github.com/mwaskom/seaborn/issues/2231 – Oliver Lopez Feb 13 '23 at 13:37