Combo Seaborn plots don't line up properly - python

I'm trying to overlay a lineplot above a countplot in seaborn. They both work when they are seperated:
By put together they end up at opposite ends of the chart:
Does anybody know why this is?

You need to use the twinx() from matplotlib and your first graph needs to be just matplotlib, not seaborn. I'm not sure why seaborn has a problem with combo charts, but I got the exact same problem as you did. Here's my code with population data from kaggle:
#Create bar plot for annual growth by year
import pandas as pd
import matplotlib
import matplotlib.pyplot as plt
import seaborn as sns
#import dataframe for data
df = pd.read_csv('df.csv')
#Create combo chart
fig, ax1 = plt.subplots(figsize=(10,6))
color = 'tab:green'
#bar plot creation
ax1.bar(df['Year'],df['Population Growth'],color='y')
#specify we want to share the same x-axis
ax2 = ax1.twinx()
#lineplot creation
ax2 = sns.lineplot(x='Year', y='Percent Growth', data=df,color='#C33E3E')
plt.show()
With this code I get the following graph:

Related

Seaborn pairplot legend don't show colors and labels

I'm using seaborn 0.11.2 but I have troubles seeing the legend of the seaborn pairplot.
Here is the code: all is working fine except for the legend
for x in x1_categorical:
plt.figure()
sns.pairplot(data=x1[[x,'weight']],hue=x, palette='husl', height=4, aspect=4)
plt.title(x)
I cannot see neither color or labels. I have already tried what suggested here: Seaborn Pairplot Legend Not Showing Colors
I have no clue, thanks in advance!
If I understand it correctly, x1_categorical contains categorical column names. Taking seaborn's penguin dataset as an example, the current code would look like:
from matplotlib import pyplot as plt
import seaborn as sns
x1 = sns.load_dataset('penguins')
x1_categorical = ['species', 'island', 'sex']
for x in x1_categorical:
g = sns.pairplot(data=x1[[x, 'body_mass_g']], hue=x, palette='husl', height=4, aspect=3)
plt.title(x)
plt.tight_layout()
When I try this (seaborn 0.11.2), I get plots such as:
These seem to be kdeplots for the numerical column, using the categorical column as hue. Unfortunately, the legends are empty, also when plt.legend() is tried.
An alternative is to explicitly create the kdeplots, for example:
from matplotlib import pyplot as plt
import seaborn as sns
x1 = sns.load_dataset('penguins')
x1_categorical = ['species', 'island', 'sex']
fig, axs = plt.subplots(ncols=1, nrows=len(x1_categorical), figsize=(12, 4*len(x1_categorical)))
for ax, x in zip(axs, x1_categorical):
sns.kdeplot(data=x1, x='body_mass_g', hue=x, palette='husl', fill=True, common_norm=False, ax=ax)
sns.despine()
The example code creates one large figure, but if needed, separate plots could be created as well.
An alternative could use common_norm=True, multiple='stack':

Create an artificial legend in seaborn

I want to add an artificial legend to my plot. It is artificial because I didn't group my observation (see code below).It means I can't solve this problem with plt.legend() function: it requires grouped variables. Is there any way to handle it?
My code:
sns.set(rc={'figure.figsize':(11.7,8.27)})
sns.set_theme(style="white")
ax = sns.boxplot(data = data.values.tolist(),palette=['white', 'black'])
ax.set_xticklabels(labels, fontsize=14)
ax.tick_params(labelsize=14)
and plot looks like:
My desire is to add a legend (maybe it is not a legend at all just a drawing) where will be written something like (sorry for size):
You can create a legend from the artists created by Seaborn as follows:
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
sns.set_theme(style="white")
ax = sns.boxplot(data = np.random.randn(20,20), palette=['white', 'black'])
handles = ax.artists[:2]
handles[0].set_label("First")
handles[1].set_label("Second")
ax.legend(handles=handles)
plt.show()
As I do not have your data, I can not replicate your charts. However, you might try adding the following line at the end (after importing matplotlib.pyplot as plt).
plt.legend(['First','Second'])

How can I plot line graph with categorical and numeric (datum) axes?

Seaborn enables you to create a categorical plot using points
import seaborn as sns
tips = sns.load_dataste('tips')
sns.catplot(x='tip', y='sex', data=tips, jitter=False)
Is there a way to connect the points with a line for the same gender?
My goal is to create a plot that will be similar to the below figure (done in R's ggplot2). Reading the seaborn documentation I find nothing that would resemble this plot. The lineplot only takes in numeric values. Is currently there an obvious way to make this categorical plot this that I'm missing?
Group by the category and plot each line individually.
import numpy as np
import matplotlib.pyplot as plt
def cat_horizontal_plot(data, category, numeric, ax=None):
ax = ax or plt.gca()
for cat, num in data.groupby(category):
ax.plot(np.sort(num[numeric].values), [cat]*len(num),
marker="o", mec="k", mfc="none", linestyle="-", color="k")
ax.set_xlabel(numeric)
ax.set_ylabel(category)
ax.margins(y=0.4)
ax.figure.tight_layout()
Use it as
import seaborn as sns
tips = sns.load_dataset('tips')
cat_horizontal_plot(tips, "sex", "tip")
plt.show()

Overlaying box plot and line plot seaborn

I am trying to overlay a box plot (series of box plot based on another variable) and a line plot of medians of that variable, on the same box plot. A simple code like below works perfectly fine.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
dfx=pd.DataFrame({'S':np.random.randint(10,100,9)*10,'C':
['X','X','X','Y','Y','Y','Z','Z','Z']})
fig,ax=plt.subplots()
mx=dfx.groupby('C')['S'].median()
sns.boxplot(y='S',x='C',data=dfx,ax=ax)
sns.lineplot(y=mx.values,x=mx.index,ax=ax)
plt.show()
which gives
However, when I use the same code for this data I am reading from csv file, I just cannot the line plot to appear with the box plot.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
df=pd.read_csv('test.csv')
fig,ax=plt.subplots()
m=df.groupby('Start Date')['Score'].median()
sns.boxplot(y='Score',x='Start Date',data=df,ax=ax)
sns.lineplot(y=m.values,x=m.index,ax=ax)
plt.show()
gives this
It doesn't matter whether the lineplot command is before or after boxplot, only box plot is shown. I see the line only if boxplot line is commented out.
I do not understand what is different about this data I am reading from csv that I cannot overlay line and box
P.S: I know a simple workaround is replace the seaborn lineplot line with matplotlib line command
ax.plot(m.values,'r-o',linewidth=4)
and it gives the desired result:
I am just curious why seaborn lineplot is behaving the way it is.
I was facing a similar problem, I "solved it" by transforming my datetime column to string.
df_median.date = df_median.date.astype(str)
df_aux.date = df_aux.date.astype(str)
sns.set()
ax = sns.stripplot('date',
'value',
data=df_aux)
ax = sns.lineplot('date',
'value',
data=df_median,
ax=ax)
plt.xlabel("month")
plt.ylabel("values")
labels = ax.axes.get_xticklabels()
ax.axes.set_xticklabels(labels, rotation=45)
plt.show()

How to subplot seaborn catplot (kind='count') on-top of catplot (kind='violin') with sharex=True

So far I have tried the following code:
# Import to handle plotting
import seaborn as sns
# Import pyplot, figures inline, set style, plot pairplot
import matplotlib.pyplot as plt
# Make the figure space
fig = plt.figure(figsize=(2,4))
gs = fig.add_gridspec(2, 4)
ax1 = fig.add_subplot(gs[0, :])
ax2 = fig.add_subplot(gs[1, :])
# Load the example car crash dataset
tips = sns.load_dataset("tips")
# Plot the frequency counts grouped by time
sns.catplot(x='sex', hue='smoker',
kind='count',
col='time',
data=tips,
ax=ax1)
# View the data
sns.catplot(x='sex', y='total_bill', hue='smoker',
kind='violin',
col='time',
split='True',
cut=0,
bw=0.25,
scale='area',
scale_hue=False,
inner='quartile',
data=tips,
ax=ax2)
plt.close(2)
plt.close(3)
plt.show()
This seems to stack the categorial plots, of each kind respectively, on top of eachother.
What I want are the resulting plots of the following code in a single figure with the countplot in row one and the violin plot in row two.
# Import to handle plotting
import seaborn as sns
# Import pyplot, figures inline, set style, plot pairplot
import matplotlib.pyplot as plt
# Load the example car crash dataset
tips = sns.load_dataset("tips")
# Plot the frequency counts grouped by time
sns.catplot(x='sex', hue='smoker',
kind='count',
col='time',
data=tips)
# View the data
sns.catplot(x='sex', y='total_bill', hue='smoker',
kind='violin',
col='time',
split='True',
cut=0,
bw=0.25,
scale='area',
scale_hue=False,
inner='quartile',
data=tips)
The actual categorical countplot that I would like to span row one of a figure that also contains a categorical violin plot (Ref. Image 3):
The actual categorical violin plot that I would like to span row two of a figure that also contains a categorical countplot (Ref. Image 2):
I tried the following code which forced the plots to be in the same figure. The downside is that the children of the figure/axes did not transfer, i.e. axis-labels, legend, and grid lines. I feel pretty close with this hack but need another push or source for inspiration. Also, I'm no longer able to close the old/unwanted figures.
# Import to handle plotting
import seaborn as sns
# Import pyplot, figures inline, set style, plot pairplot
import matplotlib.pyplot as plt
# Set some style
sns.set_style("whitegrid")
# Load the example car crash dataset
tips = sns.load_dataset("tips")
# Plot the frequency counts grouped by time
a = sns.catplot(x='sex', hue='smoker',
kind='count',
col='time',
data=tips)
numSubs_A = len(a.col_names)
for i in range(numSubs_A):
for p in a.facet_axis(0,i).patches:
a.facet_axis(0,i).annotate(str(p.get_height()), (p.get_x()+0.15, p.get_height()+0.1))
# View the data
b = sns.catplot(x='sex', y='total_bill', hue='smoker',
kind='violin',
col='time',
split='True',
cut=0,
bw=0.25,
scale='area',
scale_hue=False,
inner='quartile',
data=tips)
numSubs_B = len(b.col_names)
# Subplots migration
f = plt.figure()
for i in range(numSubs_A):
f._axstack.add(f._make_key(a.facet_axis(0,i)), a.facet_axis(0,i))
for i in range(numSubs_B):
f._axstack.add(f._make_key(b.facet_axis(0,i)), b.facet_axis(0,i))
# Subplots size adjustment
f.axes[0].set_position([0,1,1,1])
f.axes[1].set_position([1,1,1,1])
f.axes[2].set_position([0,0,1,1])
f.axes[3].set_position([1,0,1,1])
It is in general not possible to combine the output of several seaborn figure-level functions into a single figure. See (this question, also this issue). I once wrote a hack to externally combine such figures, but it has several drawbacks. Feel free to use it if it works for you.
But in general, consider creating the plot you desired manually. In this case it could look like this:
import seaborn as sns
import matplotlib.pyplot as plt
sns.set()
fig, axes = plt.subplots(2,2, figsize=(8,6), sharey="row", sharex="col")
tips = sns.load_dataset("tips")
order = tips["sex"].unique()
hue_order = tips["smoker"].unique()
for i, (n, grp) in enumerate(tips.groupby("time")):
sns.countplot(x="sex", hue="smoker", data=grp,
order=order, hue_order=hue_order, ax=axes[0,i])
sns.violinplot(x='sex', y='total_bill', hue='smoker', data=grp,
order=order, hue_order=hue_order,
split='True', cut=0, bw=0.25,
scale='area', scale_hue=False, inner='quartile',
ax=axes[1,i])
axes[0,i].set_title(f"time = {n}")
axes[0,0].get_legend().remove()
axes[1,0].get_legend().remove()
axes[1,1].get_legend().remove()
plt.show()
seaborn.catplot does not accept an "ax" argument, hence the problem with your first code.
It appears that some hacking is needed to accomplish the x-sharing you aim for:
How to plot multiple Seaborn Jointplot in Subplot
As such, you could save the time and effort, and just manually stack the two figures from your second code.

Categories