Repository: scverse/scanpy

Issue:
Unit tests fail with latest version of seaborn
## Description

Using the `seaborn==0.11` makes `scanpy/tests/test_plotting.py:test_violin` and `scanpy/tests/notebooks/test_pbmc3k.py` fail. The warning

```bash
UserWarning: Vertical orientation ignored with only `x` specified.
```

is thrown by `seaborn` in the stack trace for the failed tests.

The problem seems to originate from [here](https://github.com/theislab/scanpy/blob/f704f724529def21769ee6407f9b47b5c161564c/scanpy/plotting/_anndata.py#L745) and [here](https://github.com/theislab/scanpy/blob/f704f724529def21769ee6407f9b47b5c161564c/scanpy/plotting/_anndata.py#L747), where `x` and `y` are not both specified.

See #1417 and especially [this](https://github.com/theislab/scanpy/pull/1417#issuecomment-693488428) comment by @fidelram for more details.




Discussion:
@WeilerP Any idea on how to solve the problem? I thought that setting `orient='vertical'` was going to solve the issue but we already do that.
I skimmed through the code only briefly yesterday but I think the problem might be that both `x` and `y` need to be specified: The error message seems to indicate so and the two lines seemingly responsible for the problem are the only ones where `x` and `y` are **not** both specified.

I'll have a closer look at it in a bit.
Looking at this function which is called by seaborn violin (https://github.com/mwaskom/seaborn/blob/cf8af7e1c4ffd5d0044d13323731fa5066c057c8/seaborn/_core.py#L1260) it seems that what we need to do is swap x and y.
Yes, I saw that too but I was hoping it could be prevented as it seems suboptimal to me and is probably confusing when looking at the code.

Is there a specific reason for using `seaborn.FacetGrid` instead of `seaborn.catplot` (see [here](https://seaborn.pydata.org/generated/seaborn.catplot.html#seaborn.catplot))? Replacing the lines

```python
# ...
kwds.setdefault('cut', 0)
kwds.setdefault('inner')

if multi_panel and groupby is None and len(ys) == 1:
    # This is a quick and dirty way for adapting scales across several
    # keys if groupby is None.
    y = ys[0]
    g = sns.FacetGrid(obs_tidy, col=x, col_order=keys, sharey=False)

    # don't really know why this gives a warning without passing `order`
    g = g.map(sns.violinplot, y, orient='vertical', scale=scale, order=keys, **kwds)
```

[here](https://github.com/theislab/scanpy/blob/f704f724529def21769ee6407f9b47b5c161564c/scanpy/plotting/_anndata.py#L736) by

```python
if multi_panel and groupby is None and len(ys) == 1:
    g = sns.catplot(y=y, data=obs_tidy, kind="violin", col=x, col_order=keys, sharey=False, order=keys, **kwds)
```

gives the desired plot (except for different size). The strip plot can be added on top by calling `seaborn.stripplot` afterwards:

```python
if multi_panel and groupby is None and len(ys) == 1:
    g = sns.catplot(y=y, data=obs_tidy, kind="violin", col=x, col_order=keys, sharey=False, order=keys, **kwds)
    if stripplot:
        sns.stripplot(y=y, data=obs_tidy, jitter=jitter, color="black")
```

At the moment, I am just unsure how to plot to each subplot of the new `g`. It might be possible to loop through the plot grid so not to add everything on top of the last plot.

![master_violin_multi_panel](https://user-images.githubusercontent.com/28675704/93460801-16626300-f8e4-11ea-8f46-ed7ff64d8efb.png)


Besides the not yet solved strip plot problem, would that be a valid alternative?
There is not particular reason to use FacetGrid. If catplot does the job then it is ok. 
The following code produces the desired plot (up to permutation of the subplots and the image size) in case of only one row of subplots:

```python
if multi_panel and groupby is None and len(ys) == 1:
    # This is a quick and dirty way for adapting scales across several
    # keys if groupby is None.
    y = ys[0]

    g = sns.catplot(y=y, data=obs_tidy, kind="violin", col=x, col_order=keys, sharey=False, order=keys, **kwds)

    if stripplot:
        grouped_df = obs_tidy.groupby(x)
        for ax_id, key in zip(range(g.axes.shape[1]), keys):
            sns.stripplot(y=y, data=grouped_df.get_group(key), jitter=jitter, size=size, color="black", ax=g.axes[0, ax_id], **kwds)
```

![master_violin_multi_panel](https://user-images.githubusercontent.com/28675704/93485925-e5922600-f903-11ea-9edb-0f7523a67c0d.png)


Seems a bit hacky to me. What do you think @fidelram?
@WeilerP probably there is a more direct way to overlay the stripplot but I don't think that it makes any big difference. 

Can you make a PR to see how the tests work?

Resolving patch:
diff --git a/scanpy/plotting/_anndata.py b/scanpy/plotting/_anndata.py
--- a/scanpy/plotting/_anndata.py
+++ b/scanpy/plotting/_anndata.py
@@ -731,28 +731,37 @@ def violin(
         x = groupby
         ys = keys
 
-    # set by default the violin plot cut=0 to limit the extend
-    # of the violin plot (see stacked_violin code) for more info.
-    kwds.setdefault('cut', 0)
-    kwds.setdefault('inner')
-
     if multi_panel and groupby is None and len(ys) == 1:
         # This is a quick and dirty way for adapting scales across several
         # keys if groupby is None.
         y = ys[0]
-        g = sns.FacetGrid(obs_tidy, col=x, col_order=keys, sharey=False)
-        # don't really know why this gives a warning without passing `order`
-        g = g.map(sns.violinplot, y, orient='vertical', scale=scale, order=keys, **kwds)
+
+        g = sns.catplot(
+            y=y,
+            data=obs_tidy,
+            kind="violin",
+            scale=scale,
+            col=x,
+            col_order=keys,
+            sharey=False,
+            order=keys,
+            cut=0,
+            inner=None,
+            **kwds,
+        )
+
         if stripplot:
-            g = g.map(
-                sns.stripplot,
-                y,
-                orient='vertical',
-                jitter=jitter,
-                size=size,
-                order=keys,
-                color='black',
-            )
+            grouped_df = obs_tidy.groupby(x)
+            for ax_id, key in zip(range(g.axes.shape[1]), keys):
+                sns.stripplot(
+                    y=y,
+                    data=grouped_df.get_group(key),
+                    jitter=jitter,
+                    size=size,
+                    color="black",
+                    ax=g.axes[0, ax_id],
+                    **kwds,
+                )
         if log:
             g.set(yscale='log')
         g.set_titles(col_template='{col_name}').set_xlabels('')
@@ -760,6 +769,11 @@ def violin(
             for ax in g.axes[0]:
                 ax.tick_params(axis='x', labelrotation=rotation)
     else:
+        # set by default the violin plot cut=0 to limit the extend
+        # of the violin plot (see stacked_violin code) for more info.
+        kwds.setdefault('cut', 0)
+        kwds.setdefault('inner')
+
         if ax is None:
             axs, _, _, _ = setup_axes(
                 ax=ax,
@@ -771,7 +785,7 @@ def violin(
             axs = [ax]
         for ax, y, ylab in zip(axs, ys, ylabel):
             ax = sns.violinplot(
-                x,
+                x=x,
                 y=y,
                 data=obs_tidy,
                 order=order,
@@ -782,7 +796,7 @@ def violin(
             )
             if stripplot:
                 ax = sns.stripplot(
-                    x,
+                    x=x,
                     y=y,
                     data=obs_tidy,
                     order=order,


Test patch:
diff --git a/scanpy/tests/_images/master_violin_multi_panel.png b/scanpy/tests/_images/master_violin_multi_panel.png
Binary files a/scanpy/tests/_images/master_violin_multi_panel.png and b/scanpy/tests/_images/master_violin_multi_panel.png differ
diff --git a/scanpy/tests/notebooks/pbmc3k_images/violin.png b/scanpy/tests/notebooks/pbmc3k_images/violin.png
Binary files a/scanpy/tests/notebooks/pbmc3k_images/violin.png and b/scanpy/tests/notebooks/pbmc3k_images/violin.png differ