szczor1
szczor1

Reputation: 191

How to adapt too large dot sizes in a seaborn scatterplot legend?

I have created a series of bubble plots with seaborn, it looks fine except for legend: enter image description here

I used following command to create this plot:

ax = sns.scatterplot(x='year', y='population', hue='Country', data=temp,
                     size="density", sizes=(1500, 5000), alpha=0.25, palette="muted")

How can I delete Country from legend and leave just density? There is no such option in documentation. Also, how can I approximate values in bubbles and make them more visible?

Runnable code:

import seaborn as sns
import matplotlib.pyplot as plt
import pandas as pd
temp = pd.DataFrame({'year': [1980, 1980, 1980, 1980, 1980], 'population': [2.87,2.95,3.45,4.18,4.67], 'Country': ['ALB','ARM'
,'URY','PAN','CAF'], 'density': [1.0,1.03,1.2,1.46,1.63] })

ax=sns.scatterplot(x='year', y='population', hue='Country', data=temp,size="density", sizes=(500, 1500), alpha=0.25, palette="muted" )    
ax.set_xlim(1950,2030)
for line in temp.index:
    ax.text(temp.year[line], temp.population[line], temp.Country[line], horizontalalignment='center', size='medium', color='black', weight='semibold')
plt.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.)
ax.set_ylabel("")
ax.set_xlabel("")
ax.set_title("Population density in 5 closely populated countries")

Upvotes: 3

Views: 2973

Answers (1)

JohanC
JohanC

Reputation: 80459

ax.get_legend_handles_labels() gets the current handles and labels for the legend. These are just lists, which can be shortened via slicing. The handles to show the sizes are too large to fit into the legend. Just retrieve the sizes, scale them and set them back. (Note that handles[i].get_sizes() returns a list, normally with just one entry.)

The labels are just strings. If needed, they can be created again, e.g. with different formatting. To change the number of displayed digits, the label can be converted first to float and then back to string f{float(label):.2f}' (making use of f-strings for the formatting). Note that the very first label is the title which should be handled differently.

Optionally, the ylimits can be extended, because the default padding doesn't take the large scatter dots into account. Also, the text could be centered vertically to better fit into the circles.

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

temp = pd.DataFrame({'year': [1980, 1980, 1980, 1980, 1980],
                     'population': [2.87, 2.95, 3.45, 4.18, 4.67],
                     'Country': ['ALB', 'ARM', 'URY', 'PAN', 'CAF'],
                     'density': [1.0, 1.03, 1.2, 1.46, 1.63]})

ax = sns.scatterplot(x='year', y='population', hue='Country', data=temp,
                     size="density", sizes=(500, 1500), alpha=0.25, palette="muted")
ax.set_xlim(1950, 2030)
ylim0, ylim1 = ax.get_ylim()
ydelta = (ylim1 - ylim0) * 0.05
ax.set_ylim(ylim0 - ydelta, ylim1 + ydelta)
for line in temp.index:
    ax.text(temp.year[line], temp.population[line], temp.Country[line], ha='center', va='center', size='medium',
            color='black', weight='semibold')
handles, labels = ax.get_legend_handles_labels()
entries_to_skip = len(temp) + 1
handles = handles[entries_to_skip:]
labels = labels[entries_to_skip:]
for h in handles[1:]:
    sizes = [s / 10 for s in h.get_sizes()]
    h.set_sizes(sizes)
labels = labels[:1] + [f'{float(lab):.2f}' for lab in labels[1:]]
plt.legend(handles, labels, bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.)
ax.set_ylabel("")
ax.set_xlabel("")

plt.tight_layout()
plt.show()

resulting plot

Upvotes: 4

Related Questions