5

I search to draw a catplot with violin plots using seaborn with a broken y-axis ('cause I have a cause consequence process acting at two different scales: one between [0,0.2] and a second between [2,12] of my quantitative y-variable).

enter image description here

I understood from this answer that there is not implemented easy feature allowing this kind of plot in seaborn (yet?) So I tried different approaches, unsuccessful, to stack two plots of the same dataset but with two different scales.

Explored unsuccessful attempt:

Let's use the standard dataset 'exercise', I tried:

import seaborn as sns
import numpy as np
import matplotlib.pyplot as plt
exercise = sns.load_dataset("exercise")
f, (ax1, ax2) = plt.subplots(ncols=2, nrows=1, sharey=True)
f = sns.catplot(x="time", y="pulse", hue="kind",data=exercise, kind="violin",ax=ax1)
f = sns.catplot(x="time", y="pulse", hue="kind",data=exercise, kind="violin",ax=ax2)
ax1.set_ylim(0, 6.5)   # those limits are fake
ax2.set_ylim(13.5, 20)
plt.subplots_adjust(wspace=0, hspace=0)
plt.show()

I also tried to use facegrid but without success

import seaborn as sns
import numpy as np
import matplotlib.pyplot as plt
exercise = sns.load_dataset("exercise")
g = sns.FacetGrid(exercise, col="kind",row="time")
g.map(sns.catplot, x="time", y="pulse", hue="kind",data=exercise, kind="violin")
plt.show()

here it gives me the right base of grid of plots but plots happen in other figures.

sol
  • 1,389
  • 3
  • 19
  • 32

1 Answers1

5

If you want to draw on a subplot, you cannot use catplot, which is a figure-level function. Instead, you need to use violinplot directly. Also, if you want two different y-scales, you cannot use sharey=True when you create your subplots.

The rest is pretty much copied/pasted from matplotlib's broken axes tutorial

import seaborn as sns
import numpy as np
import matplotlib.pyplot as plt
exercise = sns.load_dataset("exercise")
f, (ax_top, ax_bottom) = plt.subplots(ncols=1, nrows=2, sharex=True, gridspec_kw={'hspace':0.05})
sns.violinplot(x="time", y="pulse", hue="kind",data=exercise, ax=ax_top)
sns.violinplot(x="time", y="pulse", hue="kind",data=exercise, ax=ax_bottom)
ax_top.set_ylim(bottom=125)   # those limits are fake
ax_bottom.set_ylim(0,100)

sns.despine(ax=ax_bottom)
sns.despine(ax=ax_top, bottom=True)

ax = ax_top
d = .015  # how big to make the diagonal lines in axes coordinates
# arguments to pass to plot, just so we don't keep repeating them
kwargs = dict(transform=ax.transAxes, color='k', clip_on=False)
ax.plot((-d, +d), (-d, +d), **kwargs)        # top-left diagonal

ax2 = ax_bottom
kwargs.update(transform=ax2.transAxes)  # switch to the bottom axes
ax2.plot((-d, +d), (1 - d, 1 + d), **kwargs)  # bottom-left diagonal

#remove one of the legend
ax_bottom.legend_.remove()
plt.show()

enter image description here

Diziet Asahi
  • 38,379
  • 7
  • 60
  • 75
  • to finalize, how to get only one 'pulse' label on y-axis? thank a lot for your answer and clear explanations. – sol Sep 04 '20 at 09:19
  • 1
    you'll have to remove the current labels and create a new one by hand. See here https://stackoverflow.com/questions/16150819/common-xlabel-ylabel-for-matplotlib-subplots – Diziet Asahi Sep 04 '20 at 09:42
  • 1
    You may also want to remove the floating xticks in the middle of the plot, with ax_top.set_xticks([]). This will only work if you do not use sharex=True in your violinplot() call (it isn't necessary in this case anyway). – vantom Nov 18 '22 at 16:23