How to set ticklabel rotation and add bar annotations - python

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)

Related

Python Seaborn placing legend outside with a title [duplicate]

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

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()

Set xlim in heatmap with subplots and annotation

I would like to plot several heatmaps side by side, with annotations.
For this, I use subplots and I can plot each heatmap in its axes using the ax kwarg.
The issue is when I use xlim : it's applied to the heatmap, but not the annotation :
Here is the code :
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
values = np.random.random((7,24)) # create (7,24) shape array
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(30,10)) # create 2 columns for subplots
ax1 = sns.heatmap(values, annot=True, ax=ax1) # heatmap with annotation
ax1.set(xlim=(12,22)) # works fine with this line commented
# ax1.set_xlim(12,22)
# ax2 = sns.heatmap(values, annot=True, ax=ax2) # second heatmap
plt.show()
And it gets worse with a second heatmap, because the annotation from the second heatmap are ploted on the first heatmap.
How can I limit x axis to (12,22) while using annotation ?
matplotlib 2.2.2
seaborn 0.9.0
python 3.6.5
Why not providing the slice of interest in the first place and relabel the x-axis?
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
np.random.seed(1234)
values = np.random.random((7,24)) # create (7,24) shape array # create (7,24) shape array ) # create (7,24) shape array
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(21,7)) # create 2 columns for subplots
#full heatmap
sns.heatmap(values, annot=True, ax=ax1)
#slice of interest
start=12
stop=22
sns.heatmap(values[:, start:stop+1], annot=True, ax=ax2, xticklabels = np.arange(start, stop+1)) # second heatmap
plt.show()
Sample output
After posting this issue on seaborn github, here is the official answer :
matplotlib text objects are not automatically clipped when they are
placed outside of the axes limits; you can turn that on by passing
annot_kws=dict(clip_on=True) to heatmap, though.
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
values = np.random.random((7,24)) # create (7,24) shape array
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(30,10)) # create 2 columns for subplots
ax1 = sns.heatmap(values, annot=True, ax=ax1, annot_kws=dict(clip_on=True)) # heatmap with annotation
ax1.set(xlim=(12,22)) # works fine with this line commented
# ax1.set_xlim(12,22)
ax2 = sns.heatmap(values, annot=True, ax=ax2, annot_kws=dict(clip_on=True)) # second heatmap
ax2.set(xlim=(12,22))
plt.show()
clip_on=True will remove everithing that is outside the axe

How do you plot a vertical line on a time series plot in Pandas?

How do you plot a vertical line (vlines) in a Pandas series plot?
I am using Pandas to plot rolling means, etc., and would like to mark important positions with a vertical line.
Is it possible to use vlines, or something similar, to accomplish this?
In this case, the x axis is datetime.
plt.axvline(x_position)
It takes the standard plot formatting options (linestlye, color, ect)
(doc)
If you have a reference to your axes object:
ax.axvline(x, color='k', linestyle='--')
If you have a time-axis, and you have Pandas imported as pd, you can use:
ax.axvline(pd.to_datetime('2015-11-01'), color='r', linestyle='--', lw=2)
For multiple lines:
xposition = [pd.to_datetime('2010-01-01'), pd.to_datetime('2015-12-31')]
for xc in xposition:
ax.axvline(x=xc, color='k', linestyle='-')
DataFrame plot function returns AxesSubplot object and on it, you can add as many lines as you want. Take a look at the code sample below:
%matplotlib inline
import pandas as pd
import numpy as np
df = pd.DataFrame(index=pd.date_range("2019-07-01", "2019-07-31")) # for sample data only
df["y"] = np.logspace(0, 1, num=len(df)) # for sample data only
ax = df.plot()
# you can add here as many lines as you want
ax.axhline(6, color="red", linestyle="--")
ax.axvline("2019-07-24", color="red", linestyle="--")
matplotlib.pyplot.vlines
For a time series, the dates for the axis must be proper datetime objects, not strings.
Use pandas.to_datetime to convert columns to datetime dtype.
Allows for single or multiple locations
ymin & ymax are specified as a specific y-value, not as a percent of ylim
If referencing axes with something like fig, axes = plt.subplots(), then change plt.xlines to axes.xlines
Also see How to draw vertical lines on a given plot
Tested in python 3.10, pandas 1.4.2, matplotlib 3.5.1, seaborn 0.11.2
Imports and Sample Data
from datetime import datetime
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns # if using seaborn
# configure synthetic dataframe
df = pd.DataFrame(index=pd.bdate_range(datetime(2020, 6, 8), freq='1d', periods=500).tolist())
df['v'] = np.logspace(0, 1, num=len(df))
# display(df.head())
v
2020-06-08 1.000000
2020-06-09 1.004625
2020-06-10 1.009272
2020-06-11 1.013939
2020-06-12 1.018629
Make the initial plot
Using matplotlib.pyplot.plot or matplotlib.axes.Axes.plot
fig, ax = plt.subplots(figsize=(9, 6))
ax.plot('v', data=df, label='v')
ax.set(xlabel='date', ylabel='v')
Using pandas.DataFrame.plot
ax = df.plot(ylabel='v', figsize=(9, 6))
Using seaborn.lineplot
fig, ax = plt.subplots(figsize=(9, 6))
sns.lineplot(data=df, ax=ax)
ax.set(ylabel='v')
Add the vertical lines
This should follow any of the 3 methods used to make the plot
y_min = df.v.min()
y_max = df.v.max()
# add x-positions as a list of date strings
ax.vlines(x=['2020-07-14', '2021-07-14'], ymin=y_min, ymax=y_max, colors='purple', ls='--', lw=2, label='vline_multiple')
# add x-positions as a datetime
ax.vlines(x=datetime(2020, 12, 25), ymin=4, ymax=9, colors='green', ls=':', lw=2, label='vline_single')
ax.legend(bbox_to_anchor=(1.04, 0.5), loc="center left")
plt.show()

Title rows and columns of matplotlib subplot layout (replacing subplots with titles)

Suppose I have a 3x3 array of 9 subplots of identical sizes. Is it possible to make a 4x4 figure and replace the top and left-most subplots with large titles?
I know it's theoretically possible to use some sort of text box, but those don't scale very well and require a lot of tweaking. Suggestions would be much appreciated.
EDIT: I was thinking of something similar to this except with proper graphs inside the array:
Sounds to me a job for GridSpec or subplot2grid.
Besides the above link, you can find some example and code here
You could use large, rotated x and y labels to achieve something similar:
Pandas
from pandas.tools.plotting import scatter_matrix
from pandas import DataFrame
from numpy.random import randn
import matplotlib.pyplot as plt
df = DataFrame(randn(1000, 8), columns=['Label1', 'Label2', 'Label3', 'Label4', 'Label5', 'Label6', 'Label7', 'Label8'])
fig = scatter_matrix(df, alpha=0.2, figsize=(6, 6), diagonal='kde')
for axes in fig:
for ax in axes:
ax.set_ylabel(ax.get_ylabel(), rotation='horizontal', ha='right', fontsize=16)
ax.set_xlabel(ax.get_xlabel(), rotation='vertical', fontsize=16)
ax.set_yticklabels('')
ax.set_xticklabels('')
plt.gcf().set_facecolor('w')
Seaborn
import seaborn as sns
import matplotlib.pyplot as plt
sns.set(style="white")
df = DataFrame(randn(50, 6), columns=['Label1', 'Label2', 'Label3', 'Label4', 'Label5', 'Label6'])
g = sns.PairGrid(df, diag_sharey=False, size=1.4)
g.map_lower(sns.kdeplot, cmap="Blues_d")
g.map_upper(plt.scatter)
g.map_diag(sns.kdeplot, lw=2)
for axes in g.axes:
for ax in axes:
ax.set_ylabel(ax.get_ylabel(), rotation='horizontal', ha='right', fontsize=20)
ax.set_xlabel(ax.get_xlabel(), rotation='vertical', fontsize=20)
ax.set_yticklabels('')
ax.set_xticklabels('')
ax.set_frame_on(False)
plt.gcf().set_facecolor('w')
Both examples are from their respective tutorials and then tweaked a little.

Categories

Resources