How to put the legend on first subplot of seaborn.FacetGrid? - python

I have a pandas DataFrame df which I visualize with subplots of a seaborn.barplot. My problem is that I want to move my legend inside one of the subplots.
To create subplots based on a condition (in my case Area), I use seaborn.FacetGrid. This is the code I use:
import matplotlib.pyplot as plt
import matplotlib
import seaborn as sns
# .. load data
grid = sns.FacetGrid(df, col="Area", col_order=['F1','F2','F3'])
bp = grid.map(sns.barplot,'Param','Time','Method')
bp.add_legend()
bp.set_titles("{col_name}")
bp.set_ylabels("Time (s)")
bp.set_xlabels("Number")
sns.plt.show()
Which generates this plot:
You see that the legend here is totally at the right, but I would like to have it inside one of the plots (for example the left one) since my original data labels are quite long and the legend occupies too much space. This is the example for only 1 plot where the legend is inside the plot:
and the code:
mask = df['Area']=='F3'
ax=sns.barplot(x='Param',y='Time',hue='Method',data=df[mask])
sns.plt.show()
Test 1:
I tried the example of an answer where they have the legend in one of the subplots:
grid = sns.FacetGrid(df, col="Area", col_order=['F1','F2','F3'])
bp = grid.map(sns.barplot,'Param','Time','Method')
Ax = bp.axes[0]
Boxes = [item for item in Ax.get_children()
if isinstance(item, matplotlib.patches.Rectangle)][:-1]
legend_labels = ['So1', 'So2', 'So3', 'So4', 'So5']
# Create the legend patches
legend_patches = [matplotlib.patches.Patch(color=C, label=L) for
C, L in zip([item.get_facecolor() for item in Boxes],
legend_labels)]
# Plot the legend
plt.legend(legend_patches)
sns.plt.show()
Note that I changed plt.legend(handles=legend_patches) did not work for me therefore I use plt.legend(legend_patches) as commented in this answer. The result however is:
As you see the legend is in the third subplot and neither the colors nor labels match.
Test 2:
Finally I tried to create a subplot with a column wrap of 2 (col_wrap=2) with the idea of having the legend in the right-bottom square:
grid = sns.FacetGrid(df, col="MapPubName", col_order=['F1','F2','F3'],col_wrap=2)
but this also results in the legend being at the right:
Question: How can I get the legend inside the first subplot? Or how can I move the legend to anywhere in the grid?

You can set the legend on the specific axes you want, by using grid.axes[i][j].legend()
For your case of a 1 row, 3 column grid, you want to set grid.axes[0][0].legend() to plot on the left hand side.
Here's a simple example derived from your code, but changed to account for the sample dataset.
import matplotlib.pyplot as plt
import matplotlib
import seaborn as sns
df = sns.load_dataset("tips")
grid = sns.FacetGrid(df, col="day")
bp = grid.map(sns.barplot,"time",'total_bill','sex')
grid.axes[0][0].legend()
bp.set_titles("{col_name}")
bp.set_ylabels("Time (s)")
bp.set_xlabels("Number")
sns.plt.show()

Use the legend_out=False option.
If you are making a faceted bar plot, you should use factorplot with kind=bar. Otherwise, if you don't explicitly specify the order for each facet, it is possible that your plot will end up being wrong.
import seaborn as sns
tips = sns.load_dataset("tips")
sns.factorplot(x="sex", y="total_bill", hue="smoker", col="day",
data=tips, kind="bar", aspect=.7, legend_out=False)

Related

How to reduce the blank area in a grouped boxplot with many missing hue categories

I have an issue when plotting a categorical grouped boxplot by seaborn in Python, especially using 'hue'.
My raw data is as shown in the figure below. And I wanted to plot values in column 8 after categorized by column 1 and 4.
I used seaborn and my code is shown below:
ax = sns.boxplot(x=output[:,1], y=output[:,8], hue=output[:,4])
ax.set_xticklabel(ax.get_xticklabels(), rotation=90)
plt.legend([],[])
However, the generated plot always contains large blank area, as shown in the upper figure below. I tried to add 'dodge=False' in sns.boxplot according to a post here (https://stackoverflow.com/questions/53641287/off-center-x-axis-in-seaborn), but it gives the lower figure below.
Actually, what I want Python to plot is a boxplot like what I generated using JMP below.
It seems that if one of the 2nd categories is empty, seaborn will still leave the space on the generated figure for each 1st category, thus causes the observed off-set/blank area.
So I wonder if there is any way to solve this issue, like using other package in python?
Seaborn reserves a spot for each individual hue value, even when some of these values are missing. When many hue values are missing, this leads to annoying open spots. (When there would be only one box per x-value, dodge=False would solve the problem.)
A workaround is to generate a separate subplot for each individual x-label.
Reproducible example for default boxplot with missing hue values
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
import numpy as np
np.random.seed(20230206)
df = pd.DataFrame({'label': np.repeat(['label1', 'label2', 'label3', 'label4'], 250),
'cat': np.repeat(np.random.choice([*'abcdefghijklmnopqrst'], 40), 25),
'value': np.random.randn(1000).cumsum()})
df['cat'] = pd.Categorical(df['cat'], [*'abcdefghijklmnopqrst'])
sns.set_style('white')
plt.figure(figsize=(15, 5))
ax = sns.boxplot(df, x='label', y='value', hue='cat', palette='turbo')
sns.move_legend(ax, loc='upper left', bbox_to_anchor=(1, 1), ncol=2)
sns.despine()
plt.tight_layout()
plt.show()
Individual subplots per x value
A FacetGrid is generated with a subplot ("facet") for each x value
The original hue will be used as x-value for each subplot. To avoid empty spots, the hue should be of string type. When the hue would be pd.Categorical, seaborn would still reserve a spot for each of the categories.
df['cat'] = df['cat'].astype(str) # the column should be of string type, not pd.Categorical
g = sns.FacetGrid(df, col='label', sharex=False)
g.map_dataframe(sns.boxplot, x='cat', y='value')
for label, ax in g.axes_dict.items():
ax.set_title('') # remove the title generated by sns.FacetGrid
ax.set_xlabel(label) # use the label from the dataframe as xlabel
plt.tight_layout()
plt.show()
Adding consistent coloring
A dictionary palette can color the boxes such that corresponding boxes in different subplots have the same color. hue= with the same column as the x= will do the coloring, and dodge=False will remove the empty spots.
df['cat'] = df['cat'].astype(str) # the column should be of string type, not pd.Categorical
cats = np.sort(df['cat'].unique())
palette_dict = {cat: color for cat, color in zip(cats, sns.color_palette('turbo', len(cats)))}
g = sns.FacetGrid(df, col='label', sharex=False)
g.map_dataframe(sns.boxplot, x='cat', y='value',
hue='cat', dodge=False, palette=palette_dict)
for label, ax in g.axes_dict.items():
ax.set_title('') # remove the title generated by sns.FacetGrid
ax.set_xlabel(label) # use the label from the dataframe as xlabel
# ax.tick_params(axis='x', labelrotation=90) # optionally rotate the tick labels
plt.tight_layout()
plt.show()

is it possible to combine 2 differents styles in Matplotlib or seaborn in one plot?

I don't know if it's possible with Matplotlib or seaborn or another tools to plot 1 line and 1 bar (candlestick style) , both in one figure . Like the image below (in excel) :
The x-axis and y-axis are the same
following the response below , I choose mplfinance : mplfinance
i have the following dataframe (daily)
and with the following function we can plot :
def ploting_chart(daily):
# Take marketcolors from 'yahoo'
mc = mpf.make_marketcolors(base_mpf_style='yahoo',up='#ff3300',down='#009900',inherit=True)
# Create a style based on `seaborn` using those market colors:
s = mpf.make_mpf_style(base_mpl_style='seaborn',marketcolors=mc,y_on_right=True,
gridstyle = 'solid' , mavcolors = ['#4d79ff','#d24dff']
)
# **kwargs
kwargs = dict(
type='candle',mav=(7,15),volume=True, figratio=(11,8),figscale=2,
title = 'Covid-19 Madagascar en traitement',ylabel = 'Total en traitement',
update_width_config=dict(candle_linewidth=0.5,candle_width=0.5),
ylabel_lower = 'Total'
)
# Plot my new custom mpf style:
mpf.plot(daily,**kwargs,style=s,scale_width_adjustment=dict(volume=0.4))
I get the final result
Yes, the plt.figure or plt.subplots gives you a figure object and then you can plot as many figures as you want. In fact if you use
import seaborn as sns
fmri = sns.load_dataset("fmri")
f,ax = plt.subplots(1,1,figsize=(10,7)) # make a subplot of 1 row and 1 column
g1 = sns.lineplot(x="timepoint", y="signal", data=fmri,ax=ax) # ax=axis object is must
g2 = sns.some_other_chart(your_data, ax=ax)
g3 = ax.some_matlotlib_chart(your_data) # no need to use ax=ax
Seaborn does not support Candlestick but you can plot using the matplotlib on the same axis.
from matplotlib.finance import candlestick_ohlc
candlestick_ohlc(ax, data.values, width=0.6, colorup='g', colordown='r') # just a dummy code to explain. YOu can see the ax object here as first arg
You can even use the pandas df.plot(data,kind='bar',ax=ax,**kwargs) to plot within the same axis object.
Note: Some of the seaborn charts do not support plotting on the same ax because they use their own grid such as relplot
Yes, mplfinance allows you to plot multiple data sets, on the same plot, or on multiple subplots, where each one can be any of candlestick, ohlc-bars, line, scatter, or bar chart.
For more information, see for example:
Adding Your Own Technical Studies to Plots
Subplots: Multiple Plots on a Single Figure, including:
The Panels Method
External Axes Method
Note, as a general rule, it is recommended to not use the "External Axes Method" if what you are trying to accomplish can be done otherwise with mplfinance in panels mode.

How to reposition title in seaborn.FacetGrid if tight_layout is applied?

I have a simple seaborn FacetGrid() with barplots inside.
I applied tight_layout() to my final plot, as xticks had to be properly positioned on the plot after rotation.
As result, when I want to add the title to the plot it is positioned in the wrong place, basically over the existing axes.
So, my question is how should the title be manipulated in order to be properly positioned in case tight_layout() is applied?
I reproduced the issue with the standard tips dataset:
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
tips = sns.load_dataset("tips")
days_dict = {day:day+', a long name' for day in tips['day'].unique()}
tips['day_long'] = tips['day'].map(lambda x: days_dict[x])
grid = sns.FacetGrid(tips,col='size',col_wrap=3,height=4,sharex=False)
grid.map(sns.barplot, 'day_long', 'total_bill').set_titles('{col_name}')
grid.fig.set_size_inches(10,10)
grid.fig.suptitle('Title (it should be placed higher)',fontsize=16)
for ax in grid.axes.flat:
for label in ax.get_xticklabels():
label.set_rotation(90)
plt.tight_layout()
plt.show()
Add (adjust the value to your taste)
grid.fig.subplots_adjust(top=0.90)
after tight_laout() to make some room at the top of the plot for the suptitle()

seaborn barplot with labels for x values (and no hue)

My dataframe contains two columns, I would like to plot their values in a barplot. Like this:
import seaborn as sns
# load sample data and drop all but two columns
tips = sns.load_dataset("tips")
tips= tips[["day", "total_bill"]]
sns.set(style="whitegrid")
ax = sns.barplot(x="day", y="total_bill", data=tips)
On top of this barplot, I would also like to add a legend with labels for each x value. Seaborn supports this, but as far as I can see, it works only when you specify a hue argument. Each label in the legend then corresponds to a hue value.
Can I create a legend with explanations for the x values?
This might be a confusing question. I don't want to rename the label for the axis or the ticks along the axis. Instead, I would like to have a separate legend with additional explanations. My bars give me some nice space to put this legend and the explanations would be too long to have them as ticks.
Is this what you want:
sns.set(style="whitegrid")
ax = sns.barplot(x="day", y="total_bill", data=tips)
ax.legend(ax.patches, ['1','2','3','Something that I can\'t say'], loc=[1.01,0.5])
Output:

adjust matplotlib subplot spacing after tight_layout

I would like to minimize white space in my figure. I have a row of sub plots where four plots share their y-axis and the last plot has a separate axis.
There are no ylabels or ticklabels for the shared axis middle panels.
tight_layout creates a lot of white space between the the middle plots as if leaving space for tick labels and ylabels but I would rather stretch the sub plots. Is this possible?
import matplotlib.gridspec as gridspec
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
fig = plt.figure()
gs = gridspec.GridSpec(1, 5, width_ratios=[4,1,4,1,2])
ax = fig.add_subplot(gs[0])
axes = [ax] + [fig.add_subplot(gs[i], sharey=ax) for i in range(1, 4)]
axes[0].plot(np.random.randint(0,100,100))
barlist=axes[1].bar([1,2],[1,20])
axes[2].plot(np.random.randint(0,100,100))
barlist=axes[3].bar([1,2],[1,20])
axes[0].set_ylabel('data')
axes.append(fig.add_subplot(gs[4]))
axes[4].plot(np.random.randint(0,5,100))
axes[4].set_ylabel('other data')
for ax in axes[1:4]:
plt.setp(ax.get_yticklabels(), visible=False)
sns.despine();
plt.tight_layout(pad=0, w_pad=0, h_pad=0);
Setting w_pad = 0 is not changing the default settings of tight_layout. You need to set something like w_pad = -2. Which produces the following figure:
You could go further, to say -3 but then you would start to get some overlap with your last plot.
Another way could be to remove plt.tight_layout() and set the boundaries yourself using
plt.subplots_adjust(left=0.065, right=0.97, top=0.96, bottom=0.065, wspace=0.14)
Though this can be a bit of a trial and error process.
Edit
A nice looking graph can be achieved by moving the ticks and the labels of the last plot to the right hand side. This answer shows you can do this by using:
ax.yaxis.tick_right()
ax.yaxis.set_label_position("right")
So for your example:
axes[4].yaxis.tick_right()
axes[4].yaxis.set_label_position("right")
In addition, you need to remove sns.despine(). Finally, there is now no need to set w_pad = -2, just use plt.tight_layout(pad=0, w_pad=0, h_pad=0)
Using this creates the following figure:

Categories

Resources