Repository: mwaskom/seaborn

Issue:
Boxplot errors when `dodge=True` without `hue`
Hello,

I want to report a bug when using the latest version of Seaborn. I checked with 0.12.2 there was no error.The bug is:
  File "/mnt/c/Users/*****/Desktop/Gent_presentation_2023/1_Effect_of_Ti_addition/2-formation_energy/all_Tix_analysis/./boxplot.py", line 287, in <module>
    statplot_cols()
  File "/mnt/c/Users/******/Desktop/Gent_presentation_2023/1_Effect_of_Ti_addition/2-formation_energy/all_Tix_analysis/./boxplot.py", line 59, in statplot_cols
    pp = sns.boxplot(
  File "/home/******/miniconda3/lib/python3.10/site-packages/seaborn/categorical.py", line 1619, in boxplot
    p.plot_boxes(
  File "/home/******/miniconda3/lib/python3.10/site-packages/seaborn/categorical.py", line 637, in plot_boxes
    self._dodge(sub_vars, data)
  File "/home/******/miniconda3/lib/python3.10/site-packages/seaborn/categorical.py", line 391, in _dodge
    hue_idx = self._hue_map.levels.index(keys["hue"])
KeyError: 'hue'

Can you check why is that?


Discussion:
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/mwaskom/seaborn/pull/3605?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Comparison is base [(`1617be0`)](https://app.codecov.io/gh/mwaskom/seaborn/commit/1617be03dc8f6c2977dbb31714e3ea9029ea3c9a?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom) 98.52% compared to head [(`6e5d806`)](https://app.codecov.io/gh/mwaskom/seaborn/pull/3605?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom) 98.52%.

<details><summary>Additional details and impacted files</summary>


[![Impacted file tree graph](https://app.codecov.io/gh/mwaskom/seaborn/pull/3605/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://app.codecov.io/gh/mwaskom/seaborn/pull/3605?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    #3605   +/-   ##
=======================================
  Coverage   98.52%   98.52%           
=======================================
  Files          75       75           
  Lines       24704    24714   +10     
=======================================
+ Hits        24340    24350   +10     
  Misses        364      364           
```


| [Files](https://app.codecov.io/gh/mwaskom/seaborn/pull/3605?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom) | Coverage Δ | |
|---|---|---|
| [seaborn/categorical.py](https://app.codecov.io/gh/mwaskom/seaborn/pull/3605?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom#diff-c2VhYm9ybi9jYXRlZ29yaWNhbC5weQ==) | `98.92% <100.00%> (+<0.01%)` | :arrow_up: |
| [tests/test\_categorical.py](https://app.codecov.io/gh/mwaskom/seaborn/pull/3605?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom#diff-dGVzdHMvdGVzdF9jYXRlZ29yaWNhbC5weQ==) | `99.26% <100.00%> (+<0.01%)` | :arrow_up: |

</details>

Patch:
diff --git a/seaborn/categorical.py b/seaborn/categorical.py
index 2e12141a..ab9f9680 100644
--- a/seaborn/categorical.py
+++ b/seaborn/categorical.py
@@ -392,6 +392,11 @@ class _CategoricalPlotter(VectorPlotter):
 
     def _dodge(self, keys, data):
         """Apply a dodge transform to coordinates in place."""
+        if "hue" not in self.variables:
+            # Short-circuit if hue variable was not assigned
+            # We could potentially warn when hue=None, dodge=True, user may be confused
+            # But I think it's fine to just treat it as a no-op.
+            return
         hue_idx = self._hue_map.levels.index(keys["hue"])
         n = len(self._hue_map.levels)
         data["width"] /= n


Test patch:
diff --git a/tests/test_categorical.py b/tests/test_categorical.py
index 98764529..eaca2e78 100644
--- a/tests/test_categorical.py
+++ b/tests/test_categorical.py
@@ -999,6 +999,16 @@ class TestBoxPlot(SharedAxesLevelTests, SharedPatchArtistTests):
                 widths.append(np.ptp(coords))
         assert np.std(widths) == approx(0)
 
+    def test_dodge_without_hue(self, long_df):
+
+        ax = boxplot(long_df, x="a", y="y", dodge=True)
+        bxp, = ax.containers
+        levels = categorical_order(long_df["a"])
+        for i, level in enumerate(levels):
+            data = long_df.loc[long_df["a"] == level, "y"]
+            self.check_box(bxp[i], data, "x", i)
+            self.check_whiskers(bxp[i], data, "x", i)
+
     @pytest.mark.parametrize("orient", ["x", "y"])
     def test_log_data_scale(self, long_df, orient):