Separate seaborn legend into two distinct boxes

2019-08-20 13:39发布

I'm using Seaborn to generate many types of graphs, but will use just a simple example here for illustration purposes based on an included dataset:

import seaborn
tips = seaborn.load_dataset("tips")
axes = seaborn.scatterplot(x="day", y="tip", size="sex", hue="time", data=tips)

enter image description here

In this result, the single legend box contains two titles "time" and "sex", each with sub-elements.

How could I easily separate the legend into two boxes, each with a single title? I.e. one for legend box indicating color codes (that could be placed at the left), and one legend box indicating size codes (that would be placed at the right).

3条回答
聊天终结者
2楼-- · 2019-08-20 13:44

If you want to use matplotlib instead of seaborn,

import matplotlib.pyplot as plt
import seaborn
tips = seaborn.load_dataset("tips")

tips["time_int"] = tips["time"].cat.codes
tips["sex_int"] = (tips["sex"].cat.codes*5+5)**2

sc = plt.scatter(x="day", y="tip", s="sex_int", c="time_int", data = tips, cmap="bwr")

leg1 = plt.legend(sc.legend_elements("colors")[0], tips["time"].cat.categories,
                  title="Time", loc="upper right")
leg2 = plt.legend(sc.legend_elements("sizes")[0], tips["sex"].cat.categories,
                  title="Sex", loc="upper left")
plt.gca().add_artist(leg1)
plt.show()

enter image description here

查看更多
倾城 Initia
3楼-- · 2019-08-20 13:51

I took Diziet's answer and expanded on it. He produced the necessary syntax I was needing, but as he pointed out, was missing a way to calculate how many lines of legend are required for splitting the legend. I have added this, and wrote a complete script:

# Modules #
import seaborn
from matplotlib import pyplot

# Plot #
tips = seaborn.load_dataset("tips")
axes = seaborn.scatterplot(x="day", y="tip", size="sex", hue="time", data=tips)

# Legend split and place outside #
num_of_colors   = len(tips['time'].unique()) + 1
handles, labels = axes.get_legend_handles_labels()
color_hl = handles[:num_of_colors], labels[:num_of_colors]
sizes_hl = handles[num_of_colors:], labels[num_of_colors:]

# Call legend twice #
color_leg = axes.legend(*color_hl,
                        bbox_to_anchor = (1.05, 1),
                        loc            = 'upper left',
                        borderaxespad  = 0.)
sizes_leg = axes.legend(*sizes_hl,
                        bbox_to_anchor = (1.05, 0),
                        loc            = 'lower left',
                        borderaxespad  = 0.)

# We need this because the 2nd call to legend() erases the first #
axes.add_artist(color_leg)

# Adjust #
pyplot.subplots_adjust(right=0.75)

# Display #
pyplot.ion()
pyplot.show()

enter image description here

查看更多
Anthone
4楼-- · 2019-08-20 14:03

The following code works well because there is the same number of time categories as sex categories. If it is not necessarily the case, you would have to calculate a priori how many lines of legend are required by each "label"

fig = plt.figure()
tips = seaborn.load_dataset("tips")
axes = seaborn.scatterplot(x="day", y="tip", size="sex", hue="time", data=tips)
h,l = axes.get_legend_handles_labels()
l1 = axes.legend(h[:int(len(h)/2)],l[:int(len(l)/2)], loc='upper left')
l2 = axes.legend(h[int(len(h)/2):],l[int(len(l)/2):], loc='upper right')
axes.add_artist(l1) # we need this because the 2nd call to legend() erases the first

output graph

查看更多
登录 后发表回答