Repository: mwaskom/seaborn

Issue:
Multiple categorical function calls set categorical axis limits incorrectly
This pertains to the "new" categorical behavior (i.e. the updates to `stripplot` and `swarmplot` post v0.11.1 release).

```python
ax = sns.stripplot(x=["A", "B", "C"], y=[1, 3, 2])
ax = sns.stripplot(x=["E", "F"], y=[5, 2])
ax.set_xlim(-.5, 4.5)  # Should not be needed
```

Without setting the `xlim` explicitly, it shows only the first two categorical positions.

Note that the data and labels are correct – unlike with the old cateogorical plots. But the default axis limits are wrong.

Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/mwaskom/seaborn/pull/2577?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom) Report
> Merging [#2577](https://codecov.io/gh/mwaskom/seaborn/pull/2577?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom) (733482b) into [master](https://codecov.io/gh/mwaskom/seaborn/commit/4f79753a8a30aaa0a0f352f9b7caaca5bb05b731?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom) (4f79753) will **decrease** coverage by `5.77%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/mwaskom/seaborn/pull/2577/graphs/tree.svg?width=650&height=150&src=pr&token=SLuzHnA9Bl&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom)](https://codecov.io/gh/mwaskom/seaborn/pull/2577?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom)

```diff
@@            Coverage Diff             @@
##           master    #2577      +/-   ##
==========================================
- Coverage   97.27%   91.50%   -5.78%     
==========================================
  Files          17       17              
  Lines        6340     6353      +13     
==========================================
- Hits         6167     5813     -354     
- Misses        173      540     +367     
```


| [Impacted Files](https://codecov.io/gh/mwaskom/seaborn/pull/2577?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom) | Coverage Δ | |
|---|---|---|
| [seaborn/rcmod.py](https://codecov.io/gh/mwaskom/seaborn/pull/2577/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom#diff-c2VhYm9ybi9yY21vZC5weQ==) | `100.00% <ø> (ø)` | |
| [seaborn/\_core.py](https://codecov.io/gh/mwaskom/seaborn/pull/2577/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom#diff-c2VhYm9ybi9fY29yZS5weQ==) | `98.07% <100.00%> (-0.55%)` | :arrow_down: |
| [seaborn/categorical.py](https://codecov.io/gh/mwaskom/seaborn/pull/2577/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom#diff-c2VhYm9ybi9jYXRlZ29yaWNhbC5weQ==) | `98.31% <100.00%> (-0.08%)` | :arrow_down: |
| [seaborn/matrix.py](https://codecov.io/gh/mwaskom/seaborn/pull/2577/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom#diff-c2VhYm9ybi9tYXRyaXgucHk=) | `42.29% <100.00%> (-53.58%)` | :arrow_down: |
| [seaborn/regression.py](https://codecov.io/gh/mwaskom/seaborn/pull/2577/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom#diff-c2VhYm9ybi9yZWdyZXNzaW9uLnB5) | `86.10% <100.00%> (-11.07%)` | :arrow_down: |
| [seaborn/algorithms.py](https://codecov.io/gh/mwaskom/seaborn/pull/2577/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom#diff-c2VhYm9ybi9hbGdvcml0aG1zLnB5) | `87.50% <0.00%> (-12.50%)` | :arrow_down: |
| [seaborn/\_statistics.py](https://codecov.io/gh/mwaskom/seaborn/pull/2577/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom#diff-c2VhYm9ybi9fc3RhdGlzdGljcy5weQ==) | `96.55% <0.00%> (-3.45%)` | :arrow_down: |
| [seaborn/utils.py](https://codecov.io/gh/mwaskom/seaborn/pull/2577/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom#diff-c2VhYm9ybi91dGlscy5weQ==) | `91.83% <0.00%> (-2.73%)` | :arrow_down: |
| ... and [2 more](https://codecov.io/gh/mwaskom/seaborn/pull/2577/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/mwaskom/seaborn/pull/2577?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/mwaskom/seaborn/pull/2577?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom). Last update [3750373...733482b](https://codecov.io/gh/mwaskom/seaborn/pull/2577?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom).


Patch:
diff --git a/seaborn/_core.py b/seaborn/_core.py
index c901998f..2c948393 100644
--- a/seaborn/_core.py
+++ b/seaborn/_core.py
@@ -1086,6 +1086,7 @@ class VectorPlotter:
                 .copy(deep=False)
                 .drop(["x", "y"], axis=1, errors="ignore")
             )
+
             for var in "yx":
                 if var not in self.variables:
                     continue
@@ -1095,6 +1096,11 @@ class VectorPlotter:
                 for converter, orig in grouped:
                     with pd.option_context('mode.use_inf_as_null', True):
                         orig = orig.dropna()
+                        if var in self.var_levels:
+                            # TODO this should happen in some centralized location
+                            # it is similar to GH2419, but more complicated because
+                            # supporting `order` in categorical plots is tricky
+                            orig = orig[orig.isin(self.var_levels[var])]
                     comp = pd.to_numeric(converter.convert_units(orig))
                     if converter.get_scale() == "log":
                         comp = np.log10(comp)
@@ -1161,7 +1167,7 @@ class VectorPlotter:
 
         # -- Verify the types of our x and y variables here.
         # This doesn't really make complete sense being here here, but it's a fine
-        # place for it, given  the current sytstem.
+        # place for it, given  the current system.
         # (Note that for some plots, there might be more complicated restrictions)
         # e.g. the categorical plots have their own check that as specific to the
         # non-categorical axis.
diff --git a/seaborn/categorical.py b/seaborn/categorical.py
index e3fcf0d8..ea557d84 100644
--- a/seaborn/categorical.py
+++ b/seaborn/categorical.py
@@ -86,10 +86,17 @@ class _CategoricalPlotterNew(VectorPlotter):
         # _core variable assignment, we'll want to figure out how to express that.
         if self.input_format == "wide" and self.orient == "h":
             self.plot_data = self.plot_data.rename(columns={"x": "y", "y": "x"})
-            orig_x, orig_x_type = self.variables["x"], self.var_types["x"]
-            orig_y, orig_y_type = self.variables["y"], self.var_types["y"]
+            orig_x, orig_y = self.variables["x"], self.variables["y"]
             self.variables.update({"x": orig_y, "y": orig_x})
+            orig_x_type, orig_y_type = self.var_types["x"], self.var_types["y"]
             self.var_types.update({"x": orig_y_type, "y": orig_x_type})
+            orig_x_levels, orig_y_levels = self.var_levels["x"], self.var_levels["y"]
+            self.var_levels.update({"x": orig_y_levels, "y": orig_x_levels})
+
+        # Use order to restrict the possible levels of the categorical variable
+        if order is not None:
+            orig_levels = self.var_levels[self.cat_axis]
+            self.var_levels[self.cat_axis] = [x for x in orig_levels if x in order]
 
     def _hue_backcompat(self, color, palette, hue_order, force_hue=False):
         """Implement backwards compatability for hue parametrization.
@@ -162,22 +169,13 @@ class _CategoricalPlotterNew(VectorPlotter):
         if self.var_types[axis] != "categorical":
             return
 
-        data = self.plot_data[axis]
-        if self.facets is not None:
-            share_group = getattr(ax, f"get_shared_{axis}_axes")()
-            shared_axes = [getattr(ax, f"{axis}axis")] + [
-                getattr(other_ax, f"{axis}axis")
-                for other_ax in self.facets.axes.flat
-                if share_group.joined(ax, other_ax)
-            ]
-            data = data[self.converters[axis].isin(shared_axes)]
-
-        if self._var_ordered[axis]:
-            order = categorical_order(data, self.var_levels[axis])
-        else:
-            order = categorical_order(data)
-
-        n = max(len(order), 1)
+        # We can infer the total number of categories (including those from previous
+        # plots that are not part of the plot we are currently making) from the number
+        # of ticks, which matplotlib sets up while doing unit conversion. This feels
+        # slightly risky, as if we are relying on something that may be a matplotlib
+        # implementation detail. But I cannot think of a better way to keep track of
+        # the state from previous categorical calls (see GH2516 for context)
+        n = len(getattr(ax, f"get_{axis}ticks")())
 
         if axis == "x":
             ax.xaxis.grid(False)
@@ -210,10 +208,11 @@ class _CategoricalPlotterNew(VectorPlotter):
                 offsets = np.zeros(n_levels)
         return offsets
 
-    # Note that the plotting methods here aim (in most cases) to produce the exact same
-    # artists as the original version of the code, so there is some weirdness that might
-    # not otherwise be clean or make sense in this context, such as adding empty artists
-    # for combinations of variables with no observations
+    # Note that the plotting methods here aim (in most cases) to produce the
+    # exact same artists as the original (pre 0.12) version of the code, so
+    # there is some weirdness that might not otherwise be clean or make sense in
+    # this context, such as adding empty artists for combinations of variables
+    # with no observations
 
     def plot_strips(
         self,


Test patch:
diff --git a/seaborn/tests/test_categorical.py b/seaborn/tests/test_categorical.py
index 0a46a7e6..a7d9f26e 100644
--- a/seaborn/tests/test_categorical.py
+++ b/seaborn/tests/test_categorical.py
@@ -1610,6 +1610,13 @@ class SharedAxesLevelTests:
         self.func(data=long_df, x="a", y="y", color="C3", ax=ax)
         assert self.get_last_color(ax) == to_rgba("C3")
 
+    def test_two_calls(self):
+
+        ax = plt.figure().subplots()
+        self.func(x=["a", "b", "c"], y=[1, 2, 3], ax=ax)
+        self.func(x=["e", "f"], y=[4, 5], ax=ax)
+        assert ax.get_xlim() == (-.5, 4.5)
+
 
 class SharedScatterTests(SharedAxesLevelTests):
     """Tests functionality common to stripplot and swarmplot."""