Repository: mwaskom/seaborn

Issue:
Performance regression with duplicate index 0.11.1->0.11.2
I am using `histplot` for plotting simple histogram plots for medium sized data-sizes.

I was using `histplot` for histogram determinations in my dataset using 0.11.1 and all worked very nicely and fast.

Then upon upgrading to 0.11.2 it suddenly never finishes and I am stuck in plotting stuff...

I have a plotting script looking like this:
```python
plt.figure()
sns.histplot(data=df, x="AllocCores", binwidth=1, discrete=True, log_scale=(False, True))
```

The data-frame has 789412 entries and the span of the data `df["AllocCores"].min / max` is `1, 1200`. So 1200 bins which I agree is very large but required in our analysis plots ;)

Using **0.11.1** I get this:
```bash
$> time python script.py
seaborn version = 0.11.1
Starting to plot...
Choosing binwidth = 120
Choosing binwidth = 1070
Choosing binwidth = 16
Choosing binwidth = 16
python script.py  40.34s user 0.83s system 155% cpu 26.479 total
```
note that this is plotting 16 files in less than a minute.


Using **0.11.2** I get this:
```bash
$> time python script.py
seaborn version = 0.11.2
Starting to plot...
```
it basically never ends... I have waited more than 10 minutes, but it isn't even done with the 1st plot.

When killing it `Ctrl+^C` I get this stack-trace:
```bash
  File "script.py", line 96, in <module>
    plot_alloccores(df)
  File ".../script_lib.py", line 98, in plot_alloccores
    sns.histplot(data=df, x="AllocCores", binwidth=1, discrete=True, log_scale=(False, True))
  File "/opt/gnu/9.3.0/python/3.8.5/lib/python3.8/site-packages/seaborn/distributions.py", line 1462, in histplot
    p.plot_univariate_histogram(
  File "/opt/gnu/9.3.0/python/3.8.5/lib/python3.8/site-packages/seaborn/distributions.py", line 428, in plot_univariate_histogram
    for sub_vars, sub_data in self.iter_data("hue", from_comp_data=True):
  File "/opt/gnu/9.3.0/python/3.8.5/lib/python3.8/site-packages/seaborn/_core.py", line 983, in iter_data
    data = self.comp_data
  File "/opt/gnu/9.3.0/python/3.8.5/lib/python3.8/site-packages/seaborn/_core.py", line 1057, in comp_data
    comp_col.loc[orig.index] = pd.to_numeric(axis.convert_units(orig))
  File "/opt/gnu/9.3.0/python/packages/3.8.5/pandas/1.0.5/lib/python3.8/site-packages/pandas-1.0.5-py3.8-linux-x86_64.egg/pandas/core/indexing.py", line 671, in __setitem__
    self._setitem_with_indexer(indexer, value)
  File "/opt/gnu/9.3.0/python/packages/3.8.5/pandas/1.0.5/lib/python3.8/site-packages/pandas-1.0.5-py3.8-linux-x86_64.egg/pandas/core/indexing.py", line 1055, in _setitem_with_indexer
    value = self._align_series(indexer, Series(value))
  File "/opt/gnu/9.3.0/python/packages/3.8.5/pandas/1.0.5/lib/python3.8/site-packages/pandas-1.0.5-py3.8-linux-x86_64.egg/pandas/core/indexing.py", line 1173, in _align_series
    ser = ser.reindex(obj.axes[0][indexer[0]], copy=True)._values
  File "/opt/gnu/9.3.0/python/packages/3.8.5/pandas/1.0.5/lib/python3.8/site-packages/pandas-1.0.5-py3.8-linux-x86_64.egg/pandas/core/series.py", line 4030, in reindex
    return super().reindex(index=index, **kwargs)
  File "/opt/gnu/9.3.0/python/packages/3.8.5/pandas/1.0.5/lib/python3.8/site-packages/pandas-1.0.5-py3.8-linux-x86_64.egg/pandas/core/generic.py", line 4543, in reindex
    return self._reindex_axes(
  File "/opt/gnu/9.3.0/python/packages/3.8.5/pandas/1.0.5/lib/python3.8/site-packages/pandas-1.0.5-py3.8-linux-x86_64.egg/pandas/core/generic.py", line 4558, in _reindex_axes
    new_index, indexer = ax.reindex(
  File "/opt/gnu/9.3.0/python/packages/3.8.5/pandas/1.0.5/lib/python3.8/site-packages/pandas-1.0.5-py3.8-linux-x86_64.egg/pandas/core/indexes/base.py", line 3153, in reindex
    indexer, missing = self.get_indexer_non_unique(target)
  File "/opt/gnu/9.3.0/python/packages/3.8.5/pandas/1.0.5/lib/python3.8/site-packages/pandas-1.0.5-py3.8-linux-x86_64.egg/pandas/core/indexes/base.py", line 4486, in get_indexer_non_unique
    indexer, missing = self._engine.get_indexer_non_unique(tgt_values)
  File "pandas/_libs/index.pyx", line 354, in pandas._libs.index.IndexEngine.get_indexer_non_unique
  File "<__array_function__ internals>", line 5, in resize
  File "/opt/gnu/9.3.0/python/packages/3.8.5/numpy/1.19.1/lib/python3.8/site-packages/numpy-1.19.1-py3.8-linux-x86_64.egg/numpy/core/fromnumeric.py", line 1417, in resize
    a = concatenate((a,) * n_copies)
  File "<__array_function__ internals>", line 5, in concatenate
KeyboardInterrupt
```

I hope that this can provide enough details for finding the regression, if not and you require some kind of data-set, let me know and I'll try to cook something out. There is some sensitive data that I can't share. But I may be able to fiddle with the entries if this may aid?

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

```diff
@@            Coverage Diff             @@
##           master    #2776      +/-   ##
==========================================
- Coverage   97.53%   91.64%   -5.89%     
==========================================
  Files          17       17              
  Lines        6440     6443       +3     
==========================================
- Hits         6281     5905     -376     
- Misses        159      538     +379     
```


| [Impacted Files](https://codecov.io/gh/mwaskom/seaborn/pull/2776?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom) | Coverage Δ | |
|---|---|---|
| [seaborn/\_core.py](https://codecov.io/gh/mwaskom/seaborn/pull/2776/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom#diff-c2VhYm9ybi9fY29yZS5weQ==) | `97.93% <100.00%> (-0.55%)` | :arrow_down: |
| [seaborn/matrix.py](https://codecov.io/gh/mwaskom/seaborn/pull/2776/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.41% <0.00%> (-53.43%)` | :arrow_down: |
| [seaborn/algorithms.py](https://codecov.io/gh/mwaskom/seaborn/pull/2776/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/\_docstrings.py](https://codecov.io/gh/mwaskom/seaborn/pull/2776/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom#diff-c2VhYm9ybi9fZG9jc3RyaW5ncy5weQ==) | `87.50% <0.00%> (-12.50%)` | :arrow_down: |
| [seaborn/regression.py](https://codecov.io/gh/mwaskom/seaborn/pull/2776/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% <0.00%> (-11.79%)` | :arrow_down: |
| [seaborn/\_statistics.py](https://codecov.io/gh/mwaskom/seaborn/pull/2776/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/2776/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom#diff-c2VhYm9ybi91dGlscy5weQ==) | `92.83% <0.00%> (-2.39%)` | :arrow_down: |
| [seaborn/distributions.py](https://codecov.io/gh/mwaskom/seaborn/pull/2776/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom#diff-c2VhYm9ybi9kaXN0cmlidXRpb25zLnB5) | `96.07% <0.00%> (-0.33%)` | :arrow_down: |
| [seaborn/axisgrid.py](https://codecov.io/gh/mwaskom/seaborn/pull/2776/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom#diff-c2VhYm9ybi9heGlzZ3JpZC5weQ==) | `95.78% <0.00%> (-0.29%)` | :arrow_down: |
| ... and [1 more](https://codecov.io/gh/mwaskom/seaborn/pull/2776/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/2776?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/2776?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom). Last update [6abd8b3...f85508e](https://codecov.io/gh/mwaskom/seaborn/pull/2776?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/doc/releases/v0.12.0.txt b/doc/releases/v0.12.0.txt
index 8f843771..7e9250ba 100644
--- a/doc/releases/v0.12.0.txt
+++ b/doc/releases/v0.12.0.txt
@@ -16,6 +16,8 @@ A paper describing seaborn was published in the `Journal of Open Source Software
 
   - In :func:`swarmplot`, the proportion of points that must overlap before issuing a warning can now be controlled with the `warn_thresh` parameter (:pr:`2447`).
 
+- |Fix| Fixed a regression in 0.11.2 that caused some functions to stall indefinitely or raise when the input data had a duplicate index (:pr:`2776`).
+
 - |Enhancement| |Fix| Improved integration with the matplotlib color cycle in most axes-level functions (:pr:`2449`).
 
 - |Fix| In :func:`lineplot, allowed the `dashes` keyword to set the style of a line without mapping a `style` variable (:pr:`2449`).
diff --git a/seaborn/_core.py b/seaborn/_core.py
index 32a0f6c7..4ba79211 100644
--- a/seaborn/_core.py
+++ b/seaborn/_core.py
@@ -1086,7 +1086,7 @@ class VectorPlotter:
                 if var not in self.variables:
                     continue
 
-                comp_col = pd.Series(index=self.plot_data.index, dtype=float, name=var)
+                parts = []
                 grouped = self.plot_data[var].groupby(self.converters[var], sort=False)
                 for converter, orig in grouped:
                     with pd.option_context('mode.use_inf_as_null', True):
@@ -1099,8 +1099,11 @@ class VectorPlotter:
                     comp = pd.to_numeric(converter.convert_units(orig))
                     if converter.get_scale() == "log":
                         comp = np.log10(comp)
-                    comp_col.loc[orig.index] = comp
-
+                    parts.append(pd.Series(comp, orig.index, name=orig.name))
+                if parts:
+                    comp_col = pd.concat(parts)
+                else:
+                    comp_col = pd.Series(dtype=float, name=var)
                 comp_data.insert(0, var, comp_col)
 
             self._comp_data = comp_data
diff --git a/seaborn/matrix.py b/seaborn/matrix.py
index 0b3ea62c..e80295cc 100644
--- a/seaborn/matrix.py
+++ b/seaborn/matrix.py
@@ -383,7 +383,7 @@ def heatmap(
         The mapping from data values to color space. If not provided, the
         default will depend on whether ``center`` is set.
     center : float, optional
-        The value at which to center the colormap when plotting divergant data.
+        The value at which to center the colormap when plotting divergent data.
         Using this parameter will change the default ``cmap`` if none is
         specified.
     robust : bool, optional


Test patch:
diff --git a/seaborn/tests/test_core.py b/seaborn/tests/test_core.py
index 0687224f..423c814c 100644
--- a/seaborn/tests/test_core.py
+++ b/seaborn/tests/test_core.py
@@ -1328,6 +1328,14 @@ class TestVectorPlotter:
         p._attach(ax)
         assert_array_equal(p.comp_data["x"], comp_data)
 
+    def test_comp_data_duplicate_index(self):
+
+        x = pd.Series([1, 2, 3, 4, 5], [1, 1, 1, 2, 2])
+        p = VectorPlotter(variables={"x": x})
+        ax = plt.figure().subplots()
+        p._attach(ax)
+        assert_array_equal(p.comp_data["x"], x)
+
     def test_var_order(self, long_df):
 
         order = ["c", "b", "a"]