how to overlay two sns.catplots

1.4k Views Asked by At

I need to overlay two sns.catplots One is of kind='box' and the other is kind='swarm' as follows:

gbox= sns.catplot( x="Emotion",y="Threshold",hue="Group", col = 'Task',
               data=indata,palette ["skyblue","salmon"], kind="box", showmeans='True', meanline = 'True', height=6, aspect=0.8, boxprops={'facecolor':'None'}, edgecolor='gray')

sns.catplot( x="Emotion",y="Threshold",hue="Group", col = 'Task',
             data=indata, palette=["skyblue","salmon"], kind="swarm",ax=gbox.axes)

I have tried taking the axes from one and feeding into the other catplot, but I receive an error as below. How can I fix this?

  ---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-74-7a05cc88a396> in <module>
     17 
     18 gbox =sns.catplot( x="Emotion",y="Threshold",hue="Group", col = 'Task',
---> 19             data=indata,palette=["skyblue","salmon"],kind="swarm",ax=gbox.axes)
     20 
     21 #plt.show(gbox)

~/anaconda3/lib/python3.6/site-packages/seaborn/categorical.py in catplot(x, y, hue, data, row, col, col_wrap, estimator, ci, n_boot, units, order, hue_order, row_order, col_order, kind, height, aspect, orient, color, palette, legend, legend_out, sharex, sharey, margin_titles, facet_kws, **kwargs)
   3753 
   3754     # Draw the plot onto the facets
-> 3755     g.map_dataframe(plot_func, x, y, hue, **plot_kws)
   3756 
   3757     # Special case axis labels for a count type plot

~/anaconda3/lib/python3.6/site-packages/seaborn/axisgrid.py in map_dataframe(self, func, *args, **kwargs)
    818 
    819             # Draw the plot
--> 820             self._facet_plot(func, ax, args, kwargs)
    821 
    822         # Finalize the annotations and layout

~/anaconda3/lib/python3.6/site-packages/seaborn/axisgrid.py in _facet_plot(self, func, ax, plot_args, plot_kwargs)
    836 
    837         # Draw the plot
--> 838         func(*plot_args, **plot_kwargs)
    839 
    840         # Sort out the supporting information

~/anaconda3/lib/python3.6/site-packages/seaborn/categorical.py in swarmplot(x, y, hue, data, order, hue_order, dodge, orient, color, palette, size, edgecolor, linewidth, ax, **kwargs)
   2989                        linewidth=linewidth))
   2990 
-> 2991     plotter.plot(ax, kwargs)
   2992     return ax
   2993 

~/anaconda3/lib/python3.6/site-packages/seaborn/categorical.py in plot(self, ax, kws)
   1444     def plot(self, ax, kws):
   1445         """Make the full plot."""
-> 1446         self.draw_swarmplot(ax, kws)
   1447         self.add_legend_data(ax)
   1448         self.annotate_axes(ax)

~/anaconda3/lib/python3.6/site-packages/seaborn/categorical.py in draw_swarmplot(self, ax, kws)
   1374         # Set the categorical axes limits here for the swarm math
   1375         if self.orient == "v":
-> 1376             ax.set_xlim(-.5, len(self.plot_data) - .5)
   1377         else:
   1378             ax.set_ylim(-.5, len(self.plot_data) - .5)

AttributeError: 'numpy.ndarray' object has no attribute 'set_xlim'

Thank you for your help! SJ

3

There are 3 best solutions below

0
On

You cannot pass an ax= parameter to catplot. You will need to create the FacetGrid "by hand".

Something like this: (untested because you did not provide data)

g = sns.FacetGrid(data=indata, hue="Group", col ='Task',
                  palette=["skyblue","salmon"])
g.map(sns.boxplot, x="Emotion", y="Threshold", showmeans='True', meanline = 'True',
                   height=6, aspect=0.8, boxprops={'facecolor':'None'}, edgecolor='gray')

g.map(sns.swarmplot, x="Emotion", y="Threshold")
0
On

First create the catplot that sets up the facet grid and then loop over the axes and plot the overlay plots on them individually. So roughly like this:

gbox = sns.catplot(
    data=indata,
    x="Emotion",
    y="Threshold",
    hue="Group",
    col='Task',
    palette["skyblue", "salmon"],
    kind="box",
    showmeans='True',
    meanline='True',
    height=6,
    aspect=0.8,
    boxprops={'facecolor': 'None'},
    edgecolor='gray',
)

for i, task in enumerate(indata['Task'].unique()):
    sns.swarmplot(                            # Note: just one swarmplot
        data=indata[indata['Task'] == task],  # select the relevant data subset
        ax=gbox.axes[i],                      # append to the relevant axis
        x="Emotion",
        y="Threshold",
        hue="Group",
        palette=["skyblue", "salmon"],
    )
0
On

In theory, using the .map and .map_dataframe methods should work, but

  • it fails when you do not want to use the same dimensionality across the plots that you are trying to overlay (for instance, using a different hue parameter for the first and second plot to show data split in different ways but overlaid)

EDIT it sort of works but only when sns.FacetGrid is first initiated independently with col/row argument, and the hue parameter is passed in individual .map_dataframe functions? I did not explore every combination, this is a bit hacky and inconsistently working in anyway.

  • it is impossible to control the zorder in which the two plots are overlaid (no matter in which order I call g.map(sns.pointplot, x, y1) and g.map(sns.pointplot, x, y2), the order between y1 and y2 will always be the same!!)

EDIT this is at least the case for pointplot, see issue #2339

Ideally, the catplot/lmplot/relplot/displot functions should allow a grid argument (equivalent to the ax argument of the other seaborn functions)!