9

First off, I am a newbie with both Python and seaborn, but after spending some time with this matter I believe my question has not been addressed elsewhere. If so, I sincerely apologize.

I want to use the seaborn relplot() function with kind='line' to plot data from a dataframe as several lines of different colors. The data corresponding to the lines are separated categorically by values in a column (cat, say) and I use hue (in this case, hue='cat'). Here is a simple example:

import pandas as pd
import seaborn as sns
df = pd.DataFrame({'x': [0, 1, 3, 4, 7, 1, 2, 5, 6],
                   'y': [2, 4, 3, 6, 5, 7, 10, 9, 7],
                   'cat': [0, 0, 0, 0, 0, 1, 1, 1, 1]})
g = sns.relplot(x='x', y='y', hue='cat', kind='line', data=df);

It produces the following graph:

Example plot

How do I modify the legend title without changing the name of the column cat? I believe my problem is that the legend title is actually implemented as a legend item, so what I want to do is remove that item and then add a proper legend title (which I can do using g._legend.set_title('New title')), but I do not know how to accomplish this.

This question differs from Remove seaborn lineplot legend title in that relplot() with kind='line' produces a FacetGrid figure and attaches the legend to that one. This means I cannot access the legend content as g.ax.legend().texts but must use g._legend, in which case I am lost.

Mr. T
  • 11,960
  • 10
  • 32
  • 54
Robert
  • 161
  • 1
  • 8

2 Answers2

14

One option would be to set the first legend entry to an empty string using g._legend.texts[0].set_text(""), then use g._legend.set_title('New title')

import seaborn as sns
df = pd.DataFrame({'x': [0, 1, 3, 4, 7, 1, 2, 5, 6],
                   'y': [2, 4, 3, 6, 5, 7, 10, 9, 7],
                   'cat': [0, 0, 0, 0, 0, 1, 1, 1, 1]})
g = sns.relplot(x='x', y='y', hue='cat', kind='line', data=df)

g._legend.texts[0].set_text("")
g._legend.set_title("New title")
g._legend._legend_box.sep = -5  # move title down slightly

plt.show()

enter image description here

You can also change the horizontal alignment using:

g._legend._legend_box.align = "right"  # or left, or center

Another option would be to draw the legend yourself:

g = sns.relplot(x='x', y='y', hue='cat', kind='line', data=df, legend=False)
ax =g.axes[0][0]

for i, line in enumerate(ax.lines):
    line.set_label(i)

leg = ax.legend()
leg.set_title("New title")

An alternative to setting the first legend entry to an empty string is shown below (suggested by ImportanceOfBeingErnest in the comments):

c = g._legend.get_children()[0].get_children()[1].get_children()[0]
c._children = c._children[1:]
DavidG
  • 24,279
  • 14
  • 89
  • 82
  • Thanks! This works. I would also like to know if it's possible to remove the first legend item, but if not I'll use this! – Robert Nov 02 '18 at 10:38
  • @RobertNyqvist Not entirely sure you can remove it using just seaborn. I think another option would be to create the legend yourself. I've updated my answer with that option. – DavidG Nov 02 '18 at 10:55
  • Your new option is also useful, thanks! But outside the scope of this toy example, I want a row of such plots using `FacetGrid`, in which case the `legend_out` type legend is nice to have because it naturally applies to all plots. – Robert Nov 02 '18 at 11:07
  • 1
    @RobertNyqvist Then I would stick to the first way in my answer for now, and wait to see if anybody else knows of a better way – DavidG Nov 02 '18 at 11:20
  • I'd be happy to help, but I think I don't understand in how far this answer does not already solve the issue. I guess it should either be accepted, or the question should be updated with a clearer explanation of the issue. – ImportanceOfBeingErnest Nov 02 '18 at 20:51
  • @ImportanceOfBeingErnest I think OP wanted the first legend item to actually be removed rather than set to an empty string, but I don't think its possible? – DavidG Nov 04 '18 at 20:31
  • 1
    Well, there is no remove method, but you can empty the packer, like `g._legend.get_children()[0].get_children()[1].get_children()[0].get_children()[0]._children = []`. – ImportanceOfBeingErnest Nov 04 '18 at 21:03
  • 1
    Or maybe better `c = g._legend.get_children()[0].get_children()[1].get_children()[0]; c._children = c._children[1:]` – ImportanceOfBeingErnest Nov 04 '18 at 21:05
  • @DavidG You're correct, that is indeed what I wanted. The solution by ImportanceOfBeingErnest is just what I was looking for. I will now accept your modified answer. Thanks for your contributions! – Robert Nov 05 '18 at 07:41
2

For me, g._legend.texts[0].set_text("") removed the label of the first element in the legend, not the title. Maybe seaborn 0.11.1 defines g._legend.texts[0] differently.

Therefore I simply used

g._legend.set_title("")
MERose
  • 4,048
  • 7
  • 53
  • 79