Python Seaborn placing legend outside with a title [duplicate] - python

I'm using factorplot(kind="bar") with seaborn.
The plot is fine except the legend is misplaced: too much to the right, text goes out of the plot's shaded area.
How do I make seaborn place the legend somewhere else, such as in top-left instead of middle-right?

Building on #user308827's answer: you can use legend=False in factorplot and specify the legend through matplotlib:
import seaborn as sns
import matplotlib.pyplot as plt
sns.set(style="whitegrid")
titanic = sns.load_dataset("titanic")
g = sns.factorplot("class", "survived", "sex",
data=titanic, kind="bar",
size=6, palette="muted",
legend=False)
g.despine(left=True)
plt.legend(loc='upper left')
g.set_ylabels("survival probability")
plt acts on the current axes. To get axes from a FacetGrid use fig.
g.fig.get_axes()[0].legend(loc='lower left')

For seaborn >= 0.11.2 use seaborn.move_legend, which applies to Axes and Figure level plots, and it accepts kwargs, like title
See matplotlib.axes.Axes.legend and How to put the legend out of the plot for parameters and their usage.
The original question asked about sns.factorplot, which has been renamed to seaborn.catplot, a figure-level plot.
For g = sns.jointplot or g = sns.JointGrid, the legend is in g.ax_joint, not g.
sns.move_legend(g.ax_joint)
See How to move or remove the legend from a seaborn JointGrid or jointplot.
Tested in python 3.10, pandas 1.5.0, matplotlib 3.5.2, seaborn 0.12.0
There isn't a current solution to relocate legends using the new seaborn.object interface, which debuted in seaborn 0.12.0.
import matplotlib.pyplot as plt
import seaborn as sns
# load the data
penguins = sns.load_dataset('penguins', cache=False)
Figure Level Plot
g = sns.displot(penguins, x="bill_length_mm", hue="species", col="island", col_wrap=2, height=3)
sns.move_legend(g, "upper left", bbox_to_anchor=(.55, .45), title='Species')
plt.show()
Axes Level Plot
ax = sns.histplot(penguins, x="bill_length_mm", hue="species")
sns.move_legend(ax, "lower center", bbox_to_anchor=(.5, 1), ncol=3, title=None, frameon=False)
plt.show()

Check out the docs here: https://matplotlib.org/users/legend_guide.html#legend-location
adding this simply worked to bring legend out of the plot:
plt.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.)

Modifying the example here:
You can use legend_out = False
import seaborn as sns
sns.set(style="whitegrid")
titanic = sns.load_dataset("titanic")
g = sns.factorplot("class", "survived", "sex",
data=titanic, kind="bar",
size=6, palette="muted",
legend_out=False)
g.despine(left=True)
g.set_ylabels("survival probability")

This is how I was able to move the legend to a particular place inside the plot and change the aspect and size of the plot:
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
matplotlib.style.use('ggplot')
import seaborn as sns
sns.set(style="ticks")
figure_name = 'rater_violinplot.png'
figure_output_path = output_path + figure_name
viol_plot = sns.factorplot(x="Rater",
y="Confidence",
hue="Event Type",
data=combo_df,
palette="colorblind",
kind='violin',
size = 10,
aspect = 1.5,
legend=False)
viol_plot.ax.legend(loc=2)
viol_plot.fig.savefig(figure_output_path)
This worked for me to change the size and aspect of the plot as well as move the legend outside the plot area.
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
matplotlib.style.use('ggplot')
import seaborn as sns
sns.set(style="ticks")
figure_name = 'rater_violinplot.png'
figure_output_path = output_path + figure_name
viol_plot = sns.factorplot(x="Rater",
y="Confidence",
hue="Event Type",
data=combo_df,
palette="colorblind",
kind='violin',
size = 10,
aspect = 1.5,
legend_out=True)
viol_plot.fig.savefig(figure_output_path)
I figured this out from mwaskom's answer here and Fernando Hernandez's answer here.

it seems you can directly call:
g = sns.factorplot("class", "survived", "sex",
data=titanic, kind="bar",
size=6, palette="muted",
legend_out=False)
g._legend.set_bbox_to_anchor((.7, 1.1))

If you wish to customize your legend, just use the add_legend method. It takes the same parameters as matplotlib plt.legend.
import seaborn as sns
sns.set(style="whitegrid")
titanic = sns.load_dataset("titanic")
g = sns.factorplot("class", "survived", "sex",
data=titanic, kind="bar",
size=6, palette="muted",
legend_out=False)
g.despine(left=True)
g.set_ylabels("survival probability")
g.add_legend(bbox_to_anchor=(1.05, 0), loc=2, borderaxespad=0.)

Using object oriented API:
fig,ax = plt.subplots(1,1)
sns.someplot(...,ax=ax)
handles, labels = ax.get_legend_handles_labels()
ax.legend(handles, labels,loc="upper left")
source: https://matplotlib.org/stable/tutorials/intermediate/legend_guide.html

Related

How to set ticklabel rotation and add bar annotations

I would like to draw the following bar plot with annotation and I want to keep the x-label 45 degree so that it is easily readable. I am not sure why my code is not working. I have added the sample data and desired bar plots as a attachment. I appreciate your suggestions! Thanks!
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns
#sns.set(rc={"figure.dpi":300, 'savefig.dpi':300})
sns.set_context('notebook')
sns.set_style("ticks")
#sns.set_style('white')
sns.set_context("paper", font_scale = 2)
colors = ['b', 'g', 'r', 'c', 'm']
#sns.set(style="whitegrid")
#sns.set_palette(sns.color_palette(colors))
#fig, (ax1,ax2) = plt.subplots(1, 2, figsize=(16, 8))
#fig.subplots_adjust(wspace=0.3)
plots1 = sns.barplot(x="Model", y="G-mean", data=df_Aussel2014_5features, ax=ax1,palette='Spectral')
# Iterrating over the bars one-by-one
for bar in plots1.patches:
# Using Matplotlib's annotate function and
# passing the coordinates where the annotation shall be done
plots1.annotate(format(bar.get_height(), '.2f'),
(bar.get_x() + bar.get_width() / 2,
bar.get_height()), ha='center', va='center',
size=10, xytext=(0, 5),
textcoords='offset points')
plt.show()
# Save figure
#plt.savefig('Aussel2014_5features.png', dpi=300, transparent=False, bbox_inches='tight')
I got the following image.
You are using the object oriented interface (e.g. axes) so don't mix plt. and axes. methods
seaborn.barplot is an axes-level plot, which returns a matplotlib axes, p1 in this case.
Use the matplotlib.axes.Axes.tick_params to set the rotation of the axis, or a number of other parameters, as shown in the documentation.
Use matplotlib.pyplot.bar_label to add bar annotations.
See this answer with additional details and examples for using the method.
Adjust the nrow, ncols and figsize as needed, and set sharex=False and sharey=False.
Tested in python 3.8.12, pandas 1.3.4, matplotlib 3.4.3, seaborn 0.11.2
import seaborn as sns
import matplotlib.pyplot as plot
import pandas as pd
# data
data = {'Model': ['QDA', 'LDA', 'DT', 'Bagging', 'NB'],
'G-mean': [0.703780, 0.527855, 0.330928, 0.294414, 0.278713]}
df = pd.DataFrame(data)
# create figure and axes
fig, ax1 = plt.subplots(nrows=1, ncols=1, figsize=(8, 8), sharex=False, sharey=False)
# plot
p1 = sns.barplot(x="Model", y="G-mean", data=df, palette='Spectral', ax=ax1)
p1.set(title='Performance Comparison based on G-mean')
# add annotation
p1.bar_label(p1.containers[0], fmt='%0.2f')
# add a space on y for the annotations
p1.margins(y=0.1)
# rotate the axis ticklabels
p1.tick_params(axis='x', rotation=45)
import matplotlib.pyplot as plt. plt.xticks(rotation=‌​45)
Example :
import matplotlib.pyplot as plt
plt.xticks(rotation=‌​45)

How to invert the axis of a seaborn figure-level plot (FacetGrid)

I would like to invert the y-axis in each plot of a Facetgrid.
Below you find a reduced example of the code:
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
df_test = pd.DataFrame({'a':['yes', 'no']*5, 'b':np.arange(10), 'c':np.arange(10)*2})
plt.figure()
g = sns.FacetGrid(data=df_test, row='a')
g.map_dataframe(sns.scatterplot, y='c', x='b')
plt.show()
As far as I know, this is normally done with ax.invert_yaxis() when using matplotlib, so I tried to access it through g.axes but had no luck.
I am aware that I can manually set a ylim=(max_val, min_val), however, this results in unsightly tick spacing.
Extract and set each axes, by iterating through g.axes
Tested in python 3.8.11, matplotlib 3.4.3, seaborn 0.11.2
g = sns.FacetGrid(data=df_test, row='a')
g.map_dataframe(sns.scatterplot, y='c', x='b')
for ax in g.axes[0]:
ax.invert_yaxis()
However, directly using seaborn.FacetGrid is not recommended. Use the figure-level plot seaborn.relplot with kind='scatter'
g = sns.relplot(data=df_test, row='a', x='b', y='c', kind='scatter', height=3)
for ax in g.axes[0]:
ax.invert_yaxis()
Use .ravel() to flatten n x n arrays or axes, where both n > 1
df_test = pd.DataFrame({'a':['yes', 'no', 'maybe']*4, 'b':np.arange(12), 'c':np.arange(12)*2, 'd': np.arange(12)*3})
g = sns.relplot(data=df_test, col='a', col_wrap=2, x='b', y='c', kind='scatter', height=3)
for ax in g.axes.ravel():
ax.invert_yaxis()

How to add x and y axis line in seaborn scatter plot

I used the following code to create scatterplot (data is imported as an example). However, the plot was created without x and y axis, which looks weird. I would like to keep facecolor='white' as well.
import seaborn as sns
tips = sns.load_dataset("tips")
fig, ax = plt.subplots(figsize=(10, 8))
sns.scatterplot(
x='total_bill',
y='tip',
data=tips,
hue='total_bill',
edgecolor='black',
palette='rocket_r',
linewidth=0.5,
ax=ax
)
ax.set(
title='title',
xlabel='total_bill',
ylabel='tip',
facecolor='white'
);
Any suggestions? Thanks a lot.
You seem to have explicitly set the default seaborn theme. That has no border (so also no line for x and y axis), a grey facecolor and white grid lines. You can use sns.set_style("whitegrid") to have a white facecolor. You can also use sns.despine() to only show the x and y-axis but no "spines" at the top and right. See Controlling figure aesthetics for more information about fine-tuning how the plot looks like.
Here is a comparison. Note that the style should be set before the axes are created, so for demo-purposes plt.subplot creates the axes one at a time.
import matplotlib.pyplot as plt
import seaborn as sns
sns.set() # set the default style
# sns.set_style('white')
tips = sns.load_dataset("tips")
fig = plt.figure(figsize=(18, 6))
for subplot_ind in (1, 2, 3):
if subplot_ind >= 2:
sns.set_style('white')
ax = plt.subplot(1, 3, subplot_ind)
sns.scatterplot(
x='total_bill',
y='tip',
data=tips,
hue='total_bill',
edgecolor='black',
palette='rocket_r',
linewidth=0.5,
ax=ax
)
ax.set(
title={1: 'Default theme', 2: 'White style', 3: 'White style with despine'}[subplot_ind],
xlabel='total_bill',
ylabel='tip'
)
if subplot_ind == 3:
sns.despine(ax=ax)
plt.tight_layout()
plt.show()

Make seaborn show a colorbar instead of a legend when using hue in a bar plot?

Let's say I want to make a bar plot where the hue of the bars represents some continuous quantity. e.g.
import seaborn as sns
titanic = sns.load_dataset("titanic")
g = titanic.groupby('pclass')
survival_rates = g['survived'].mean()
n = g.size()
ax = sns.barplot(x=n.index, y=n,
hue=survival_rates, palette='Reds',
dodge=False,
)
ax.set_ylabel('n passengers')
The legend here is kind of silly, and gets even worse the more bars I plot. What would make most sense is a colorbar (such as are used when calling sns.heatmap). Is there a way to make seaborn do this?
The other answer is a bit hacky. So a more stringent solution, without producing plots that are deleted afterwards, would involve the manual creation of a ScalarMappable as input for the colorbar.
import matplotlib.pyplot as plt
import seaborn as sns
titanic = sns.load_dataset("titanic")
g = titanic.groupby('pclass')
survival_rates = g['survived'].mean()
n = g.size()
norm = plt.Normalize(survival_rates.min(), survival_rates.max())
sm = plt.cm.ScalarMappable(cmap="Reds", norm=norm)
sm.set_array([])
ax = sns.barplot(x=n.index, y=n, hue=survival_rates, palette='Reds',
dodge=False)
ax.set_ylabel('n passengers')
ax.get_legend().remove()
ax.figure.colorbar(sm)
plt.show()
You can try this:
import matplotlib.pyplot as plt
import seaborn as sns
titanic = sns.load_dataset("titanic")
g = titanic.groupby('pclass')
survival_rates = g['survived'].mean()
n = g.size()
plot = plt.scatter(n.index, n, c=survival_rates, cmap='Reds')
plt.clf()
plt.colorbar(plot)
ax = sns.barplot(x=n.index, y=n, hue=survival_rates, palette='Reds', dodge=False)
ax.set_ylabel('n passengers')
ax.legend_.remove()
Output:

How to scale Seaborn's y-axis with a bar plot

I'm using factorplot(kind="bar").
How do I scale the y-axis, for example with log-scale?
I tried tinkering with the plots' axes, but that always messed up the bar plot in one way or another, so please try your solution first to make sure it really works.
Considering your question mentions barplot I thought I would add in a solution for that type of plot also as it differs from the factorplot in #Jules solution.
import matplotlib.pyplot as plt
import seaborn as sns
sns.set(style="whitegrid")
xs = ["First", "First", "Second", "Second", "Third", "Third"]
hue = ["Female", "Male"] * 3
ys = [1988, 301, 860, 77, 13, 1]
g = sns.barplot(x=xs, y=ys, hue=hue)
g.set_yscale("log")
_ = g.set(xlabel="Class", ylabel="Survived")
And if you want to label the y-axis with non-logarithmic labels you can do the following.
import matplotlib.pyplot as plt
import seaborn as sns
sns.set(style="whitegrid")
xs = ["First", "First", "Second", "Second", "Third", "Third"]
hue = ["Female", "Male"] * 3
ys = [1988, 301, 860, 77, 13, 1]
g = sns.barplot(x=xs, y=ys, hue=hue)
g.set_yscale("log")
# the non-logarithmic labels you want
ticks = [1, 10, 100, 1000]
g.set_yticks(ticks)
g.set_yticklabels(ticks)
_ = g.set(xlabel="Class", ylabel="Survived")
Note that seaborn.factorplot was renamed to seaborn.catplot
import seaborn as sns
import matplotlib.pyplot as plt
titanic = sns.load_dataset("titanic")
g = sns.catplot(x="class", y="survived", hue="sex",
data=titanic, kind="bar",
height=5, palette="muted", legend=False, log=True)
plt.show()
You can use Matplotlib commands after calling factorplot.
For example:
g = sns.factorplot(x="class", y="survived", hue="sex",
data=titanic, kind="bar",
height=5, palette="muted", legend=False)
g.fig.get_axes()[0].set_yscale('log')
plt.show()
If you are facing the problem of vanishing bars upon setting log-scale using the previous solutions, try adding log=True to the seaborn function call instead. (I'm lacking reputation to comment on the other answers).
Using sns.factorplot:
import seaborn as sns
import matplotlib.pyplot as plt
sns.set(style="whitegrid")
titanic = sns.load_dataset("titanic")
g = sns.factorplot(x="class", y="survived", hue="sex", kind='bar',
data=titanic, palette="muted", log=True)
g.ax.set_ylim(0.05, 1)
Using sns.barplot:
import seaborn as sns
import matplotlib.pyplot as plt
sns.set(style="whitegrid")
titanic = sns.load_dataset("titanic")
g = sns.barplot(x="class", y="survived", hue="sex",
data=titanic, palette="muted", log=True)
g.set_ylim(0.05, 1)
Seaborn's catplot does not have anymore the log parameter.
For those looking for an updated answer, here's the quickest fix I've used: you have to use matplotlib's built-in support by accessing the axes object.
g = sns.catplot(data=df, <YOUR PARAMETERS>)
for ax in g.fig.axes:
ax.set_yscale('log')

Categories

Resources