4

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. 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 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):
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])

This image shows the hack'd method of forcing both catplots onto a single plot, it shows the deficiency of my implementation in that the labels, legends, and other children aren't coming for the ride/transfer

Tony A
  • 98
  • 1
  • 6

2 Answers2

5

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

enter image description here

ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712
1

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.

Inon Peled
  • 691
  • 4
  • 11
  • Thanks for you post. I looked at the link you put in, "How to plot multiple Seaborn Jointplot in Subplot". I took some inspiration from this and made some edits to my original post (added some code and another resultant figure). Any more suggestions to get the figure into a better condition, as I'm still missing the legends and axis-labels. – Tony A Mar 17 '19 at 23:11
  • 2
    As the author of that "solution", I would not recommend using it, because it has a lot of drawbacks. I think one needs to accept that seaborn facet grids are not meant to be used as subplots to other figures. At the end you will have spent more time tinkering the seaborn plot in shape than it would cost to plot it from scratch. – ImportanceOfBeingErnest Mar 17 '19 at 23:19